Manifest · Manifest member
Manifest scope: your PWA's navigation boundary
Published Updated
In one line: Per the Web Application Manifest specification, scope “is a string that represents
the navigation scope of this web application’s application context”; per MDN it specifies “the
top-level URL path that contains your web application’s pages and subdirectories”, pages within it
“provide an app-like interface”, and outside it browsers “display UI elements like the URL bar to
indicate the change in context”.
How to use it
Section titled “How to use it”scope is a string holding a URL — absolute, or relative and resolved against the manifest file’s
URL (MDN). The safe shape is an explicit scope that start_url sits inside:
{ "name": "My Hiking Web App", "start_url": "https://hikingapp.com/store/", "scope": "https://hikingapp.com/store/"}With that setup, per MDN’s own example, “pages like https://hikingapp.com/store/products are part
of your web app, but https://hikingapp.com/company/ is out of your web app’s scope.”
In-scope vs out-of-scope
Section titled “In-scope vs out-of-scope”- In scope. Per MDN, “a URL is considered to be ‘within scope’ if its path begins with the URL
path defined in
scope” — withscopeset to/app/, the URLs/app/,/app/page.htmland/app/dashboard/index.htmlare all within scope, while/and/page.htmlare not. - Out of scope. The app-like interface broadly remains, but “browsers display additional UI elements like the URL bar”, which “helps users understand that they’re viewing pages outside the app’s defined scope”.
- Scope is not a wall. MDN is explicit: “The
scopemember doesn’t prevent users from navigating to app pages outside of the defined scope. Off-scope navigations are not blocked by browsers.” How an out-of-scope document is presented is up to the user agent; what you can rely on is that it is not blocked.
The specification states the matching rule exactly: a URL target is within scope of a URL scope
if the two are same origin and target’s path starts with scope’s path.
Prefix matching is string matching
Section titled “Prefix matching is string matching”Per MDN, “string matching for the scope URL uses a simple prefix match, not the path structure”:
a scope of /prefix “will match URLs starting with /prefix, including /prefix-of/index.html and
/prefix/index.html”. That is why MDN recommends “to define a scope ending with a /” — setting
/prefix/ “ensures it will match only the pages within the /prefix/ directory”.
The fallback scope
Section titled “The fallback scope”scope is optional, and an invalid one is discarded rather than honoured. Per MDN, if scope is not
specified or the value is invalid — “not a string, not a valid URL, or start_url is not within the
specified scope” — “the effective scope will be set to the start_url value after removing its
filename, query, and fragment”. The specification describes the same default: the start URL, “but
with its filename, query, and fragment removed”.
MDN’s worked examples:
start_url |
Effective scope when scope is missing or invalid |
|---|---|
https://example.com/app/index.html?user=123#home |
https://example.com/app/ |
/pages/welcome.html |
/pages/ on the same origin |
/pages/ (the trailing slash is important) |
/pages/ |
MDN’s advice is not to lean on it: “To avoid issues with scope determination in this way, it’s
recommended to explicitly specify scope in your manifest file.”
How to detect it at runtime
Section titled “How to detect it at runtime”To check a URL against your own scope, read the declared value out of the manifest and apply the
specification’s within-scope rule. The helper returns null when it has no usable declared scope to
report — that is not an assertion that a URL is out of scope, only that it could not answer.
// The specification's within-scope rule: same origin, and the target's path// starts with the scope's path.function isWithinScope(target, scope) { if (!scope) return false; const targetUrl = new URL(target, location.href); const scopeUrl = new URL(scope, location.href); if (targetUrl.origin !== scopeUrl.origin) return false; return targetUrl.pathname.startsWith(scopeUrl.pathname);}
// Reads the DECLARED scope. It does not reproduce the browser's own fallback:// when `scope` is absent or invalid, the browser derives an effective scope// from `start_url` instead.async function declaredScope() { const link = document.querySelector('link[rel="manifest"]'); if (!link || !('fetch' in window)) { // Fallback: no manifest to read — treat the scope as unknown. return null; } try { const manifest = await fetch(link.href).then((response) => response.json()); if (typeof manifest.scope !== 'string' || manifest.scope === '') return null; const scope = new URL(manifest.scope, link.href).href; // MDN: an undefined or invalid `start_url` defaults to the page that links // to the manifest; and the scope is invalid if `start_url` is not within it. let startUrl = location.href; if (typeof manifest.start_url === 'string' && manifest.start_url !== '') { try { const parsed = new URL(manifest.start_url, link.href); if (parsed.origin === location.origin) startUrl = parsed.href; } catch { // Keep the document URL, the way the browser would. } } return isWithinScope(startUrl, scope) ? scope : null; } catch { return null; // Unreachable manifest, unparseable JSON, or an invalid URL. }}Deep links and Android link capture
Section titled “Deep links and Android link capture”Per MDN, “other applications can deep link directly to specific pages of your web app. The scope
member affects how these deep-linked pages are displayed, but it is not required for deep linking to
work.” A shared in-scope link opens “in the app’s interface without browser controls”; an out-of-scope
one opens in the app-like interface “but with the website address and browser controls visible”.
On Android the same string drives link capture. Per web.dev, an installed PWA “will register a set of
intent filters for all URLs within the scope of the app”, and scope “tells Android to only open
your web app if the URL matches the origin + scope”, which “gives you control over which URLs
will be handled by your app, and which should be opened in the browser”. With "scope": "/app/",
web.dev’s example captures https://example.com/app/read/book but not https://example.com/help/.
Practical checklist
Section titled “Practical checklist”- End the scope with
/—/prefixalso matches/prefix-of/index.html(MDN). - Keep
start_urlinsidescope. If it is not, the scope is invalid and the browser falls back to thestart_url-derived default, silently ignoring what you wrote. - Declare
scopeexplicitly rather than relying on the fallback, as MDN recommends. - Remember a relative
scoperesolves against the manifest’s URL, not the page’s. - Do not treat scope as a navigation control: per MDN, “off-scope navigations are not blocked by browsers”.
- On Android, scope is what becomes intent filters — anything outside it opens in the browser rather than the installed app (web.dev).
- Test a shared in-scope deep link on an installed app and confirm browser controls stay hidden.
Where to go next
Section titled “Where to go next”- Manifest start_url — the member the fallback scope is derived from.
- Scope extensions.
- PWAs on Chrome and Android — how scope turns into Android intent filters at install time.
Specifications
| Specification | Status |
|---|---|
| Web App Manifest: scope | W3C |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | Yes | 73 | medium | source | — |
| Chrome (Desktop) | Yes | 73 | medium | source | — |
| Edge (Desktop) | Yes | 79 | medium | source | — |
| Safari (iOS) | Yes | 16.4 | medium | source | 1 |
| Safari (macOS) | Yes | 17 | medium | source | 2 |
| Firefox (Desktop) | No | — | medium | source | 3 |
| Samsung Internet | Yes | 6.2 | medium | source | — |
- Honored for home-screen web apps; off-scope navigation surfaces a banner rather than a full address bar.
- Applies to Dock-installed web apps.
- No manifest-based install path, so scope is not applied.
Ecosystem & commercial policy
| Entity | Type | Context | Status | Sponsored | Notes |
|---|---|---|---|---|---|
| Google Play (TWA) | distribution_channel | Google Play TWA | Yes | No | assetlinks.json origin must match the manifest scope origin, or the TWA degrades to a Custom Tab with browser chrome. |
| Microsoft Store (PWA) | distribution_channel | Microsoft Store | Yes | No | Scope defines the in-app boundary store reviewers preview at submission. |