v152 · shipped · isolated web apps
Sub apps
Sub apps let a single Isolated Web App (IWA) installation present multiple distinct applications to the OS — each with its own name, icons, shelf window, and integrations such as protocol and file handlers — while sharing the parent IWA's origin, storage, permissions, Signed Web Bundle resources, and update lifecycle.
The Sub Apps API ships enabled by default in Chrome 152 (chromestatus milestone listing; it was in developer trial in Chrome 150). Even enabled by default, it is only exposed inside installed Isolated Web Apps. The documented setup (per the official sample) is:
- Use a ChromeOS device — the only platform where Isolated Web Apps are currently available to end users. The API runs on other desktops but is not officially supported there; minor issues are possible.
- In
chrome://flagsenable#enable-isolated-web-apps,#enable-isolated-web-app-dev-mode, and#enable-sub-apps. - Open
chrome://web-app-internalsand install the app as a signed Isolated Web App (“Install IWA from Update Manifest”, or via Dev Mode Proxy) — the interface is exposed with[SecureContext, IsolatedContext], sowindow.subAppsonly exists inside an installed IWA. - Declare the permission policy
Permissions-Policy: sub-apps=(self); without it every method rejects withSecurityError.
Implementation/debug note: the runtime-enabled feature is SubApps (status: stable at trunk in runtime_enabled_features.json5), so --enable-blink-features=SubApps also gates the binding — but the flags-and-chrome://web-app-internals flow above is the documented complete setup.
at a glance
| Milestone listing | Chrome 152 — Enabled by default (chromestatus listing, verified 2026-07-28; the feature was previously listed under Chrome 150 as In developer trial) |
|---|---|
| Default-on milestone | Chrome 152, per two agreeing sources: the milestone listing (Enabled by default) and the feature detail (chromestatus API, updated 2026-07-24 12:57 UTC — shipping target desktop 152; active stage “Prepare to ship”, desktop_first 152). The feature's status text remains “Proposed” |
| Interface | SubApps, reached via window.subApps |
| Platform | Implemented on desktop; end-user availability currently limited to platforms supporting Isolated Web Apps — presently ChromeOS (other desktops unofficial) |
| Context | Installed Isolated Web Apps only (secure, isolated context) |
| Permission policy | sub-apps=(self) required on the parent IWA; policies declared for a sub app have no effect |
| Dev-trial flags | #enable-isolated-web-apps, #enable-isolated-web-app-dev-mode, #enable-sub-apps, then install via chrome://web-app-internals |
| Runtime flag (impl. evidence) | SubApps in runtime_enabled_features.json5 (status stable at trunk) |
| Standards body | WICG — Sub Apps (unofficial draft) |
| ChromeStatus | 6260680824061952 — Sub apps |
Syntax
The API hangs off Window as a single read-only attribute returning a SubApps instance with three asynchronous batch methods. IDL from the WICG draft:
[Exposed=Window, SecureContext, IsolatedContext]
partial interface Window {
[SameObject] readonly attribute SubApps subApps;
};
// Represents the https://w3c.github.io/manifest/#id-member
typedef USVString ManifestId;
dictionary SubAppsAddResponse {
record<USVString, ManifestId> installedApps;
record<USVString, DOMException> failedApps;
};
dictionary SubAppsRemoveResponse {
sequence<ManifestId> 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<ManifestId> manifest_ids);
Promise<record<USVString, SubAppsListResult>> list();
};
Source: WICG Sub Apps draft — IDL index
add(install_paths)
Installs a batch of sub apps. Each install path is a relative URL (it must start with /) to the sub app's entry index.html, which must reference a web manifest. A single system prompt lists every requested sub app in one dialog (scrollable when batched).
| Part | Detail |
|---|---|
| Parameters | install_paths — sequence<USVString> of same-origin absolute paths to sub-app entry documents |
| Returns | Promise<SubAppsAddResponse> — installedApps maps each install path to its new ManifestId; failedApps maps each failed path to a DOMException |
| Batch rejection | TypeError (invalid relative paths), SecurityError (policy missing), NotSupportedError (called inside a sub app), NotAllowedError (user declined the prompt), QuotaExceededError (platform limits exceeded), OperationError |
| Per-app failure | ConstraintError (scope prefix conflict, or path points at the parent's own manifest), DataError (manifest invalid/unparseable), InvalidStateError (already installed), OperationError |
remove(manifest_ids)
Uninstalls a batch of previously installed sub apps, identified by the ManifestId values returned from add() or list().
| Part | Detail |
|---|---|
| Parameters | manifest_ids — sequence<ManifestId> |
| Returns | Promise<SubAppsRemoveResponse> — removedApps lists the removed ManifestId values; failedApps maps each failed id to a DOMException |
| Batch rejection | TypeError, SecurityError, NotSupportedError, OperationError |
| Per-app failure | NotFoundError (no such sub app under this parent IWA, or already removed), OperationError |
list()
Returns the sub apps currently installed under this parent IWA as a record keyed by ManifestId, so dictionary lookups (e.g. membership checks) work directly on the result. Each value is a SubAppsListResult whose appName is the name from the sub app's web manifest.
| Part | Detail |
|---|---|
| Returns | Promise<record<USVString, SubAppsListResult>> |
| Rejection | SecurityError (policy missing), NotSupportedError (called inside a sub app), OperationError |
Sub app identity, resources, and limits
A sub app is not a separate security principal. It shares the parent IWA's origin, SignedWebBundleId, cookies, IndexedDB, localStorage, Cache Storage, and all granted permissions — the same-origin policy treats parent and sub apps as one entity. What distinguishes a sub app is its presentation and OS integration:
| Identifier | Rule |
|---|---|
| Origin / bundle id | Identical to the parent IWA |
start_url | Required in the sub app's manifest; must be unique across sub apps and the parent |
| Scope | Sub-app scopes must not be prefixes of one another, nor of the parent scope — otherwise install/update fails with ConstraintError |
ManifestId | The manifest id member; falls back to start_url without the fragment. Used by remove() and list() |
Sub-app manifests can carry their own OS integrations — protocol_handlers, file_handlers, and launch_handler act per sub app. Resources can be provisioned statically (bundled in the parent IWA, updated when the parent updates) or dynamically (a service worker serves the sub app's HTML and manifest, so sub apps update without a parent bundle update).
Two platform quotas guard against launcher flooding: at most 50 installed sub apps per parent IWA, and at most 20 sub apps per installation prompt. Exceeding either rejects the whole add() call with QuotaExceededError.
Enterprise policies
Three Chrome enterprise policies govern whether sub-app installs require a user prompt:
| Policy | Effect |
|---|---|
| DefaultSubAppsWithoutPromptsSetting | Globally sets whether IWA sub-app installs require a user permission prompt |
| SubAppsWithoutPromptsAllowedForOrigins | Lists IWA URLs that may add or update sub apps without a prompt |
| SubAppsWithoutPromptsBlockedForOrigins | Lists IWA URLs that may only add sub apps with a prompt |
Examples
// Inside an installed IWA with Permissions-Policy: sub-apps=(self)
const { installedApps, failedApps } = await window.subApps.add(["/calc/", "/docs/"]);
for (const [installPath, manifestId] of Object.entries(installedApps)) {
console.log(`installed: ${installPath} -> ${manifestId}`);
}
for (const [installPath, exception] of Object.entries(failedApps)) {
console.error(`failed: ${installPath} — ${exception.name}`);
}
// Later: enumerate and remove
const all = await window.subApps.list(); // record<ManifestId, { appName }>
const calcId = Object.keys(all).find((id) => all[id].appName === "Calculator");
if (calcId) {
const { removedApps } = await window.subApps.remove([calcId]);
console.log("removed:", removedApps);
}
Source: WICG Sub Apps explainer — examples
Browser compatibility
There is no browser-compat-data (BCD) entry for this API yet, so the table below is an interim view built from ChromeStatus ship data and public vendor signals — not from BCD.
| Browser | Status | Notes |
|---|---|---|
| Chrome | 150 (developer trial) | Isolated Web Apps only — presently ChromeOS for end users. Default-on target desktop 152, per the agreeing v152 milestone listing (Enabled by default) and feature detail (API, 2026-07-24); status text “Proposed” |
| Edge | Not separately reported | No Edge position is recorded on ChromeStatus; no public support evidence (Chromium engine equivalence is not a support commitment) |
| Firefox | No signal | Recorded on ChromeStatus |
| Safari | No signal | Recorded on ChromeStatus |
api/SubApps entry as of July 2026)
Specifications
| Specification | Status |
|---|---|
| Sub Apps | WICG unofficial draft (incubation) |
| Web Application Manifest | W3C Working Draft — sub apps reuse its id, start_url, scope, and OS-integration members |