1. Introduction
The Sub Apps API allows a parent application to programmatically install, list, and remove auxiliary applications (Sub Apps) that:
Appear to the operating system and user as fully distinct applications (separate launcher icons, distinct taskbar/shelf windows, and individual OS integrations).
Share the underlying resources, origin identification, storage, permissions, and update lifecycle of the parent application.
This API is restricted to isolated contexts to ensure security and data integrity.
2.
Extensions
to
the
Window
interface
[Exposed =Window ,SecureContext ,IsolatedContext ]partial interface Window { [SameObject ]readonly attribute SubApps ; };subApps
2.1.
subApps
attribute
When
getting,
the
subApps
attribute
always
returns
the
same
instance
of
the
SubApps
object.
3.
SubApps
interface
dictionary {SubAppsAddResponse record <USVString ,USVString >;installedApps record <USVString ,DOMException >; };failedApps dictionary {SubAppsRemoveResponse sequence <USVString >;removedApps record <USVString ,DOMException >; };failedApps dictionary {SubAppsListResult required DOMString ; }; [appName Exposed =Window ,SecureContext ,IsolatedContext ]interface {SubApps Promise <SubAppsAddResponse >(add sequence <USVString >);install_paths Promise <SubAppsRemoveResponse >(remove sequence <USVString >);manifest_ids Promise <record <USVString ,SubAppsListResult >>(); };list
Each
application
has
a
primary
application
context
(the
parent
app).
A
Document
is
a
sub-app
document
if
it
is
not
loaded
within
the
primary
application
context
of
the
parent
app.
A
string
path
is
a
valid
relative
path
for
a
Document
document
if
all
of
the
following
conditions
are
met:
path is not a valid absolute URL.
path starts with
"/".path is not empty.
path does not start with
"//".The result of parsing path with document ’s origin as the base URL is not failure.
3.1.
add()
method
The
add(install_paths)
method
steps
are:
Let promise be a new promise .
If the relevant global object ’s associated Document is not allowed to use the policy-controlled feature named " sub-apps ", reject promise with a "
SecurityError"DOMExceptionand return promise .If the relevant global object ’s associated Document is a sub-app document , reject promise with a "
NotSupportedError"DOMExceptionand return promise .Let parsedUrls be an empty list.
For
now, seeeach installPath ininstall_paths:If installPath is not a valid relative path for the
explainerrelevant global object ’s associated Document , reject promise with a "TypeError"DOMExceptionand return promise .Let absoluteUrl be the result of parsing installPath with the parent app’s origin as the base URL.
If absoluteUrl is failure, reject promise with a "
TypeError"DOMExceptionand return promise .Append absoluteUrl to parsedUrls .
Let parentApp be the parent app.
Let currentSubAppsCount be the number of sub-apps currently installed for parentApp .
If currentSubAppsCount +
install_paths’s size is greater than 20, reject promise with a "QuotaExceededError"DOMExceptionand return promise .Let userConsent be the result of requesting user consent for installing the sub-apps in
install_paths(e.g. by presenting a unified installation dialog).If userConsent is denied, reject promise with a "
NotAllowedError"DOMExceptionand return promise .Run the following steps in parallel :
Let installedApps be an empty map.
Let failedApps be an empty map.
For each absoluteUrl in parsedUrls :
Let installPath be the relative path portion of absoluteUrl .
Fetch the resource at absoluteUrl and parse its web manifest [APPMANIFEST] .
-
See https://garykac.github.io/procspec/If fetching or parsing the web manifest fails, perform the following steps:Add a mapping from installPath to a new "
DataError"DOMExceptionto failedApps .Continue.
Let manifest be the parsed web manifest.
Let manifestId be manifest ’s id . If it is not defined, fall back to manifest ’s start_url (without reference/hash fragment).
If parentApp already has a sub-app with manifestId installed, perform the following steps:
Add a mapping from installPath to a new "
InvalidStateError"DOMExceptionto failedApps .Continue.
If the scope of manifest overlaps with the scope of parentApp , or the scope of manifest overlaps with any currently installed sub-app’s scope under parentApp , or the absoluteUrl points to parentApp ’s manifest itself, perform the following steps:
Add a mapping from installPath to a new "
ConstraintError"DOMExceptionto failedApps .Continue.
Try to install the sub-app on the platform’s application launcher.
If the installation fails due to a system or database error:
Add a mapping from installPath to a new "
OperationError"DOMExceptionto failedApps .Continue.
Add a mapping from installPath to manifestId to installedApps .
Let response be a new
SubAppsAddResponsedictionary with:installedAppsset to installedApps .failedAppsset to failedApps .
Queue a global task on the relevant global object of this to resolve promise with response .
Return promise .
3.2.
remove()
method
The
remove(manifest_ids)
method
steps
are:
Let promise be a new promise .
If the relevant global object ’s associated Document is not allowed to use the policy-controlled feature named " sub-apps ", reject promise with a "
SecurityError"DOMExceptionand return promise .If the relevant global object ’s associated Document is a sub-app document ,
https://dlaliberte.github.io/bikeshed-intro/index.htmlreject promise with a "NotSupportedError"DOMExceptionand return promise .Let parsedManifestIds be an empty list.
For each manifestId in
manifest_ids:If manifestId is not a valid relative path for the relevant global object ’s associated Document , reject promise with a "
TypeError"DOMExceptionandhttps://speced.github.io/bikeshed/return promise .Let parsedUrl be the result of parsing manifestId with the parent app’s origin as the base URL.
If parsedUrl is failure, reject promise with a "
TypeError"DOMExceptionand return promise .Append parsedUrl to parsedManifestIds .
Run the following steps in parallel :
Let removedApps be an empty sequence.
Let failedApps be an empty map.
For each parsedUrl in parsedManifestIds :
Let manifestId be the relative path portion of parsedUrl .
If manifestId does not exist under the parent app, perform the following steps:
Add a mapping from manifestId to a new "
NotFoundError"DOMExceptiontoget startedfailedApps .Continue.
Attempt to uninstall the sub-app with ID manifestId from the system launcher and registry.
If uninstallation fails due to a system error, perform the following steps:
Add a mapping from manifestId to a new "
OperationError"DOMExceptionto failedApps .Continue.
Append manifestId to removedApps .
Let response be a new
SubAppsRemoveResponsedictionary with:removedAppsset to removedApps .failedAppsset to failedApps .
Queue a global task on
your specification.the relevant global object of this to resolve promise with response .
Return promise .
3.3.
list()
method
The
list()
method
steps
are:
Let promise be a new promise .
If the relevant global object ’s associated Document is not allowed to use the policy-controlled feature named " sub-apps ", reject promise with a "
SecurityError"DOMExceptionand return promise .If the relevant global object ’s associated Document is a sub-app document , reject promise with a "
NotSupportedError"DOMExceptionand return promise .Run the following steps in parallel :
Let listResult be an empty map.
Retrieve the list of all currently installed sub-apps for the parent app from the platform registry.
If retrieving the list fails due to a platform error, Queue a global task on the relevant global object of this to reject promise with an "
OperationError"DOMException, and abort these steps.For each installed sub-app subApp :
Let manifestId be subApp ’s manifest ID.
Let appName be the name of the sub-app as extracted from its web manifest.
Let resultEntry be a new
SubAppsListResultdictionary withappNameset to appName .Add a mapping from manifestId to resultEntry to listResult .
Queue a global task on the relevant global object of this to resolve promise with listResult .
Return promise .
4. Security and Privacy Considerations
Installing and managing auxiliary applications is a powerful feature . A user agent MUST NOT allow a web application to install or manage sub-apps without express permission .
This section outlines the threats considered and the normative requirements for user agents to mitigate them.
4.1. Shared Origin Identity
A sub-app does not possess a separate security origin. It shares the exact same origin and local data stores (such as Cookies, IndexedDB, LocalStorage, and Cache Storage) with its parent app. Standard web security boundaries (such as the Same-Origin Policy) treat the parent and all its sub-apps as a single entity.4.2. Permission Inheritance
All permissions are shared across the parent app and its sub-apps. Granting a permission (e.g., camera, file system access, USB) to a sub-app automatically grants it to the parent, and vice versa.
To
access
the
Sub
Apps
API,
the
parent
app’s
document
must
explicitly
declare
the
permission
policy
sub-apps
.
Permission
policies
declared
for
a
sub-app
have
no
effect.
4.3. Explicit User Consent
User consent MUST be obtained for a specific origin . When
add()
is
called,
the
user
agent
MUST
present
a
unified
installation
dialog
to
the
user
displaying
all
requested
sub-apps.
If
multiple
sub-apps
are
added
at
once,
they
should
be
presented
inside
a
single
prompt
to
avoid
dialog
spam.
The user agent MUST display a permission prompt that clearly indicates which origin is requesting access and provides the user with enough information to make an informed decision (for example, by displaying the names and icons of the sub-apps being installed).
4.4. Identity Spoofing Risks
Since developers can customize the names and icons of sub-apps, there is a risk that a malicious application could create a sub-app that mimics system dialogs or trusted third-party applications. To mitigate this risk, the Sub Apps API is restricted to isolated contexts , which guarantee integrity and signature verification.4.5. OS Integration Extension Risk
Sub-apps have the ability to register their own OS integrations (such as protocol handlers or file type associations). This means an application could potentially extend its reach into the OS far beyond what was declared in the parent app’s primary manifest. This is mitigated by the fact that most operating system integrations require explicit user approval (for example, choosing the sub-app as the default application for a file type) before they become active.4.6. Quota and Limits
To protect the host operating system and the user’s application launcher from potential exhaustion or abuse, the platform enforces a hard limit of 20 installed sub-apps per parent application. If a batch installation call exceeds the platform limit, the entire
add()
call
rejects
with
a
"
QuotaExceededError
"
DOMException
.5. Integrations
5.1. Permissions Policy
This
specification
defines
a
feature
that
controls
whether
the
methods
exposed
by
the
subApps
attribute
on
the
Window
object
may
be
used.
The feature name for this feature is " sub-apps ".
The
default
allowlist
for
this
feature
is
'self'
.