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 |
why it exists
An installed IWA can contain several applications, but a single launcher identity obscures them. The Sub Apps explainer's use cases include an office suite with separate email, document, and spreadsheet entry points that share one bundle and storage, and a virtual-desktop portal that gives streamed apps (such as a terminal and editor) their own launcher icons and windows. Sub apps provide those distinct OS entry points under one parent installation, instead of requiring separate heavyweight IWAs or one indistinguishable app window.
Source: WICG Sub Apps explainer — Use cases.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 (sub-apps permissions 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 |
Each policy is expanded below from the definition the Chrome Enterprise policy list publishes for it (policy ids 1448, 1449, 1450). Three facts are common to all three: they are defined for ChromeOS 150 and later only (supported_on: chrome_os:150-), they are per profile, and they support dynamic policy refresh, so a changed value applies without a browser restart. Precedence is fixed by the definitions: SubAppsWithoutPromptsBlockedForOrigins overrides SubAppsWithoutPromptsAllowedForOrigins, which overrides DefaultSubAppsWithoutPromptsSetting — so an origin listed in both lists is never prompt-free. The examples below are the managed-policy JSON form of each definition's own published example_value; the definitions publish a value domain and an example value but no per-platform deployment path, so none is claimed here.
DefaultSubAppsWithoutPromptsSetting
Caption from the definition: “Control use of the Sub Apps Add API”. This is the global default for whether an IWA origin may add sub apps at all, and whether the confirmation prompt described at add(install_paths) is shown.
| Value grammar | Integer enum — published type int-enum, schema { "type": "integer", "enum": [1, 2, 3] } |
|---|---|
| Accepted values | 1 = AllowSubApps, “Allow Isolated Web App origins to add Sub Apps without user prompt”; 2 = BlockSubApps, “Block Isolated Web App origins from adding Sub Apps”; 3 = AskSubApps, “Allow Isolated Web App origins to add Sub Apps but require user prompt” |
| Unset | No default value is published (default: null); the definition states that leaving the policy unset behaves as 3 — IWA origins may add sub apps upon user confirmation |
Effect on add() | At 3 or unset, the single system prompt documented at add(install_paths) is shown and declining it rejects that call with NotAllowedError. At 1, the definition says the origin may “bypass user gesture or user confirmation requirements”. At 2, IWA origins are blocked from adding sub apps |
| Precedence | Overridden per origin by SubAppsWithoutPromptsAllowedForOrigins and by SubAppsWithoutPromptsBlockedForOrigins |
| Supported on | ChromeOS 150 and later. No other platform is listed under supported_on; chrome.* appears only under future_on, so Windows, macOS, Linux, Android and iOS support is not claimed for this policy |
| Refresh and scope | Dynamic Policy Refresh (a change applies without a restart); Per Profile |
| Not specified | The definition publishes a closed enum and no behavior for a value outside [1, 2, 3], and it does not name the DOMException an add() call rejects with when 2 blocks the origin. Both are recorded as open questions in this page's critique rather than guessed |
// Managed-policy JSON form of the published example_value (3)
{ "DefaultSubAppsWithoutPromptsSetting": 3 } // AskSubApps - prompt the user (same as unset)
{ "DefaultSubAppsWithoutPromptsSetting": 1 } // AllowSubApps - prompt-free for every IWA origin
{ "DefaultSubAppsWithoutPromptsSetting": 2 } // BlockSubApps - IWA origins cannot add sub apps
SubAppsWithoutPromptsAllowedForOrigins
Caption from the definition: “Allow Isolated Web Apps to use Sub Apps Add API without user prompt”. The definition states that by default IWA origins can add sub apps provided the user confirms the prompt interactively, and that this policy controls which IWA origins may add sub apps without the user being prompted.
| Value grammar | Array of strings — published type list, schema { "type": "array", "items": { "type": "string" } } |
|---|---|
| Accepted values | IWA origins. Both published example values use the isolated-app:// scheme (reproduced below). No wildcard or URL-pattern grammar is published |
Effect on add() | A listed IWA origin may add sub apps without the user being prompted; an origin that is not listed keeps the confirmation prompt documented at add(install_paths) |
| Precedence | Overrides DefaultSubAppsWithoutPromptsSetting, and is itself overridden by SubAppsWithoutPromptsBlockedForOrigins — an origin present in both lists is not prompt-free |
| Supported on | ChromeOS 150 and later. No other platform is listed under supported_on; chrome.* appears only under future_on |
| Refresh and scope | Dynamic Policy Refresh (a change applies without a restart); Per Profile |
| Not specified | No origin-matching rule beyond the string itself, no behavior for a malformed entry (an invalid origin, a wrong scheme, or an empty list), and no statement of whether the exemption also reaches a sub-app update — the definition speaks only of adding. Recorded as open questions in this page's critique rather than guessed |
// Managed-policy JSON form of the definition's own published example_value
{
"SubAppsWithoutPromptsAllowedForOrigins": [
"isolated-app://egoxo6biqdjrk62rman4vvr5cbq2ozsyydig7jmdxcmohdob2ecaaaic",
"isolated-app://anayaszofsyqapbofoli7ljxoxkp32qkothweire2o6t7xy6taz6oaacai"
]
}
SubAppsWithoutPromptsBlockedForOrigins
Caption from the definition: “Block Isolated Web Apps from using Sub Apps Add API without user prompt”. It controls which IWA origins are blocked from adding sub apps without the user being prompted interactively — the deciding policy wherever it overlaps the other two.
| Value grammar | Array of strings — published type list, schema { "type": "array", "items": { "type": "string" } } |
|---|---|
| Accepted values | IWA origins. Both published example values use the isolated-app:// scheme (reproduced below). No wildcard or URL-pattern grammar is published |
Effect on add() | A listed IWA origin may only add sub apps with the user prompted interactively, even where DefaultSubAppsWithoutPromptsSetting is 1 or the origin also appears in SubAppsWithoutPromptsAllowedForOrigins |
| Precedence | The definition states it “overrides the behavior of DefaultSubAppsWithoutPromptsSetting and SubAppsWithoutPromptsAllowedForOrigins” — the highest-priority of the three |
| Supported on | ChromeOS 150 and later. No other platform is listed under supported_on; chrome.* appears only under future_on |
| Refresh and scope | Dynamic Policy Refresh (a change applies without a restart); Per Profile |
| Not specified | No origin-matching rule beyond the string itself and no behavior for a malformed entry (an invalid origin, a wrong scheme, or an empty list). Recorded as open questions in this page's critique rather than guessed |
// Managed-policy JSON form of the definition's own published example_value
{
"SubAppsWithoutPromptsBlockedForOrigins": [
"isolated-app://egoxo6biqdjrk62rman4vvr5cbq2ozsyydig7jmdxcmohdob2ecaaaic",
"isolated-app://anayaszofsyqapbofoli7ljxoxkp32qkothweire2o6t7xy6taz6oaacai"
]
}
Source: DefaultSubAppsWithoutPromptsSetting, SubAppsWithoutPromptsAllowedForOrigins and SubAppsWithoutPromptsBlockedForOrigins on the Chrome Enterprise policy list — captions, descriptions, value schemas, enum members, example values, supported_on, dynamic refresh and per-profile flags, and the precedence statements — as published in the policy definition data that list renders (policy ids 1448, 1449, 1450; fetched 2026-10-06).
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. The second id is deliberately not one of this
// parent IWA's sub apps, so the response shows BOTH of its members: removedApps
// for the ids it uninstalled, failedApps for the ids it could not (NotFoundError).
const all = await window.subApps.list(); // record<ManifestId, { appName }>
const calcId = Object.keys(all).find((id) => all[id].appName === "Calculator");
if (calcId) {
const { removedApps, failedApps: notRemoved } =
await window.subApps.remove([calcId, "stale-manifest-id"]);
console.log("removed:", removedApps);
for (const [manifestId, exception] of Object.entries(notRemoved)) {
console.error(`not removed: ${manifestId} — ${exception.name}`);
}
}
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 |