# scope manifest member

> The scope member is the URL prefix that marks which pages belong to an installed web app; navigation outside it keeps the app window but brings browser UI back.

`scope` is a URL string naming the navigation scope of an installed web app: the set of pages
that get the app-like window without browser controls. A URL is within scope when it is same
origin as the scope URL and its path begins with the scope URL's path; everything else is out of
scope and is still reachable, but the browser restores its own UI to say so.

Chrome 73 (Android and desktop), Edge 79, Samsung Internet 6.2, Safari 16.4 on iOS, and Safari 17
on macOS apply the member to installed apps (BCD `html.manifest.scope`). Firefox 157 on desktop
has no manifest-based install path, so the value has no consumer there. On Android the same
string also decides which links an installed WebAPK captures from other apps.

## Member

- **Type**: string holding a URL, absolute or relative; a relative value resolves against the
  manifest file's URL, not the document's.
- **Default**: `start_url` with its filename, query, and fragment removed. `start_url` of
  `/app/index.html?user=1#home` gives an effective scope of `/app/`.
- **Example value**: `"/app/"`.

Two rules drop the member and fall back to the default. The scope must be same origin as the
document (`property 'scope' ignored, should be same origin as document.`), and `start_url` must
be within the scope (`property 'scope' ignored. Start url should be within scope of scope URL.`).
Both messages come from Chromium's `manifest_parser.cc`.

Matching is a plain string prefix on the path, not a directory test. `"/app"` matches
`/app-admin/` and `/application.html` as well as `/app/`; end the value with `/` to confine it
to one directory. Scope is not a security boundary: an out-of-scope navigation is not blocked,
and the service worker's scope is a separate value set at `register()` time (MDN,
`ServiceWorkerRegistration.scope`) that can be wider or narrower than the manifest's.

What out-of-scope looks like differs by platform. Chrome on Android opens the page in a custom-tab
style view with the URL shown at the top; Chrome and Edge on desktop add a toolbar with the origin
and an "open in browser" control; Safari 16.4 on iOS shows a banner inside the home-screen app
rather than a full address bar (BCD note). Cross-origin pages can be pulled into scope only with
`scope_extensions`.

:::observed
Chrome 155 on macOS 26 (English UI), DevTools > Application > Manifest, with
`"start_url": "/"` and `"scope": "/app/"`: the **Errors and warnings** section reads
`property 'scope' ignored. Start url should be within scope of scope URL.` and the
**Presentation** section shows **Start URL** `/` with no scope row, which is how the panel
reports that the default scope (`/`) is in force.
:::

## Examples

Each example pairs a manifest with the page-level consequence it has.

### Confining the app to a subpath

A dashboard served under `/app/` on a site whose marketing pages live at the root. Links to
`/pricing/` open in the app window with browser controls restored, so the visitor can tell they
have left the app.

```json
{
  "name": "Ledger",
  "start_url": "/app/",
  "scope": "/app/",
  "display": "standalone"
}
```

The trailing slash matters: `"scope": "/app"` would also claim `/app-status/` and
`/apply.html` because matching is a path-prefix comparison.

### Letting the default scope derive from a start_url with a query

A manifest that tags launches with a query string and declares no `scope`. The browser strips the
filename, query, and fragment from `start_url`, so the effective scope is `/app/` and the whole
directory is in scope, including the untagged `/app/` URL the user reaches by navigation.

```json
{
  "name": "Ledger",
  "start_url": "/app/index.html?source=homescreen",
  "display": "standalone"
}
```

If the same manifest had `"start_url": "/app/index.html?source=homescreen"` and
`"scope": "/dashboard/"`, the scope would be dropped (start URL not within it) and `/app/` would
again be the effective scope; the mistake is silent except for the DevTools line quoted above.

### Detecting an out-of-scope context at runtime

The manifest's scope is not exposed to script, but the `display-mode` media feature reports
whether the current page is being shown with browser UI. In an installed app that means the page
is out of scope (or the user opened it in a tab); in an engine without the feature the query
matches nothing and the code takes the same fallback branch as a browser tab.

```js
function isWithinScope(target, scope) {
  const t = new URL(target, location.href);
  const s = new URL(scope, location.href);
  return t.origin === s.origin && t.pathname.startsWith(s.pathname);
}

const APP_SCOPE = '/app/';
const inAppWindow = matchMedia('(display-mode: standalone)').matches;

if (!inAppWindow || !isWithinScope(location.href, APP_SCOPE)) {
  // Browser tab, unsupported engine, or an out-of-scope page: show the full site header.
  document.documentElement.dataset.chrome = 'site';
} else {
  document.documentElement.dataset.chrome = 'app';
}
```

Keep `APP_SCOPE` in one constant shared with the manifest build so the two cannot drift; the
browser gives no warning when they do.

## See also

- [start_url manifest member](/reference/manifest/start-url/)
- [scope_extensions manifest member](/reference/manifest/scope-extensions/)
- [display manifest member](/reference/manifest/display/)
- [Service worker registration and scope](/reference/service-worker/registration-scope/)
- [PWAs on Chrome and Android (WebAPK, TWA)](/reference/platforms/chrome-android/)
- [Web Application Manifest: scope member](https://www.w3.org/TR/appmanifest/#scope-member) (w3.org)
- [WebAPKs on Android](https://web.dev/articles/webapks) (web.dev)