scope_extensions: extending a PWA's scope across origins
In one line: The scope_extensions manifest member “is used to extend the scope of a web
app to include other origins, allowing multiple domains to be presented as a single web app,”
per MDN, which flags it as experimental technology.
What it’s for
Section titled “What it’s for”MDN lists the typical multi-origin situations this targets: different subdomains for content
or functionality (support.example.com, shop.example.com), different subdomains for
language or locale (uk.example.com, de.example.com), and related independent domains, such
as a partner site. The WICG explainer describes why this matters for navigation: in
Chromium browsers, an installed app window shows an out-of-scope UI bar when it navigates
outside the manifest scope, and the explainer notes other implementations may instead open a
new tab or browsing context in a regular browser window — behavior the explainer says a
developer may not want for first-party content that just happens to live on another origin.
Declaring extended origins
Section titled “Declaring extended origins”Per MDN, the main app lists the origins it wants to include, each as an object with a type and
an origin:
{ "scope_extensions": [ { "type": "origin", "origin": "https://support.example.com" }, { "type": "origin", "origin": "https://shop.example.com" } ]}Per MDN, type is “a string defining the type of scope extension. This is currently always
origin,” and origin is “a string representing an origin that the web app wishes to extend
its scope to.”
The origin-association verification file
Section titled “The origin-association verification file”Listing an origin in the manifest is only half the handshake. Each extended origin must also
host .well-known/web-app-origin-association, confirming it accepts the association back to
the app. Sources do not agree on one settled JSON shape for this file yet. The WICG explainer’s
“Related Proposals” section, describing how this differs from the earlier url_handlers
proposal, shows entries keyed by the web app’s identifier with a scope value:
{ "https://example.com/": { "scope": "/foo" } }Chrome’s developer documentation, however, currently shows a different shape — a web_apps
array of objects keyed by web_app_identity:
{ "web_apps": [{ "web_app_identity": "https://example.com/" }] }Check the live WICG explainer and your target engine’s current implementation before hard-coding either shape into a production association file.
Detecting effective scope extension
Section titled “Detecting effective scope extension”The WICG explainer describes scope_extensions only as a manifest member and an
origin-association file read by the browser; it defines no companion JavaScript property or
runtime API for reading back whether a navigation was honored as in-scope. Checking
display-mode does not detect scope_extensions support — per MDN, display-mode only
reports which display mode (standalone, minimal-ui, window-controls-overlay, fullscreen,
picture-in-picture, or the default browser) a page is currently rendered in, and MDN’s
display-mode documentation does not mention scope_extensions or which origins it covers.
Because the explainer defines no runtime detection mechanism for scope_extensions, link to an
associated origin the same way regardless of whether the browser honors scope_extensions, and
let the browser apply its own outcome. Per the WICG explainer, Chromium browsers show an
out-of-scope UI bar when the navigation lands outside the manifest scope, and other
implementations may instead open a new tab or browsing context — both are the browser’s own
handling and require no application code:
function openRelatedOrigin(url) { location.assign(url);}Use display-mode only to adjust unrelated presentation details, never to change this navigation
logic:
const installedModes = ["standalone", "minimal-ui", "window-controls-overlay"];const looksInstalled = window.matchMedia && installedModes.some((mode) => window.matchMedia(`(display-mode: ${mode})`).matches);if (!looksInstalled) { // Rendered in browser, fullscreen, or picture-in-picture, not one of the // installed-style modes. This says nothing about scope_extensions // support — it only adjusts presentation, e.g. showing a hint that a // related-origin link may open a new tab: document.querySelectorAll("[data-related-origin-link]").forEach((el) => { el.title = "Opens a related site; it may open in a new tab."; });}Design the UI copy around the associated-origin link so a user is not confused if, per the behavior the WICG explainer describes for Chromium and some other implementations, that navigation lands on a chrome-visible page or a new tab instead of staying inside the app-like window.
Where it is supported
Section titled “Where it is supported”MDN marks scope_extensions as experimental and advises checking its browser-compatibility
table before production use.
Practical checklist
Section titled “Practical checklist”- Treat
scope_extensionsas experimental — MDN explicitly flags it and tells readers to check the compatibility table before shipping it in production. - Host
.well-known/web-app-origin-associationon every origin listed inscope_extensions— the association is a two-way handshake, and a missing or malformed file on the extended origin means the extension does not take effect for that origin. - Do not assume the association file’s format is fixed — the WICG explainer shows one shape, Chrome’s developer documentation currently shows another; check both before shipping.
- Design any cross-origin link so its UI copy still makes sense on engines that ignore
scope_extensions— the WICG explainer describes Chromium’s un-extended result as an out-of-scope bar, and notes some other implementations may open a new tab instead. - Do not treat a
display-modecheck asscope_extensionssupport detection — the WICG explainer defines no JavaScript API for confirming whether the browser honoredscope_extensions, and MDN’sdisplay-modedocumentation does not mentionscope_extensions. Keep the associated-origin navigation working the same way regardless. - When using the WICG explainer’s keyed-by-identity association shape, use its
scopevalue to restrict an extended origin to only the paths it actually wants covered, rather than its whole origin.