# start_url manifest member

> start_url is the same-origin URL an installed PWA opens from the home screen, the place to tag launches for analytics, and one input to the app identity.

`start_url` is the URL the installed app opens when the user taps its icon, as opposed to the
page that happened to be open when they installed it. It is resolved against the manifest
URL, must share the manifest's origin, and must fall within `scope`; it is also the fallback
source of the app's identity when `id` is absent.

Every engine with an install path reads it: Chrome 39, Edge 79, Samsung Internet 4.0, Firefox
79 on Android, Safari 11.3 on iOS and 17 on macOS (BCD `html.manifest.start_url`). Chromium
additionally treats a valid `start_url` as an installability requirement.

## Member

- **Type**: string, parsed as a URL relative to the manifest URL.
- **Default**: the URL of the document that linked the manifest. Chromium also falls back to
  that document URL after dropping a cross-origin value with `property 'start_url' ignored,
  should be same origin as document.` (`manifest_parser.cc`).
- **Example value**: `"/app/?utm_source=homescreen"`.

Two rules interact with other members. If `start_url` is not within the declared `scope`,
Chromium keeps `start_url` and drops `scope` instead, logging `property 'scope' ignored.
Start url should be within scope of scope URL.`, so the app silently receives the default
scope (the manifest URL's directory). And because the processed `id` defaults to
`start_url` without its query and fragment, changing the path of `start_url` on an app without an
`id` creates a second, separate install rather than updating the first (Chrome
for Developers, "Uniquely identify PWAs with the web app manifest id property").

The launch URL is the default entry, not the only one: notifications, `shortcuts`,
`share_target`, and `file_handlers` all open other in-scope URLs, so every in-scope route has
to be a valid cold start.

:::observed
Chrome 155 on Android 16 (English UI), `chrome://webapks`: each installed WebAPK lists a
**Manifest Start URL** row next to **URI**, **Scope**, and **Manifest URL**, so the value the
installed app will launch can be read without opening DevTools. On desktop, DevTools >
Application > Manifest shows the same value in the **Presentation** section as **Start URL**,
and an unreachable value surfaces under **Installability** as `Manifest start URL is not
valid`.
:::

## Examples

The examples use one manifest served from `https://app.example/manifest.webmanifest`.

### Relative and absolute forms that resolve to the same launch page

Relative values resolve against the manifest URL, not against the page that linked it. A
manifest at `/manifest.webmanifest` with `"start_url": "app/"` therefore launches
`https://app.example/app/`, the same as the absolute form.

```json
{
  "start_url": "app/",
  "scope": "/app/"
}
```

The absolute form `"https://app.example/app/"` is equivalent; a value on another origin such
as `"https://cdn.example/app/"` is discarded and the document URL takes its place.

### Tagging launches for analytics and reading the tag at startup

A query string on `start_url` is the only way to tell an installed-app launch from a browser
visit without a dedicated API. The script reads the parameter once, reports it, and removes
it so in-app links do not carry it forward; when the parameter is missing nothing is sent.

```json
{
  "start_url": "/app/?utm_source=homescreen&utm_medium=pwa"
}
```

```js
const params = new URLSearchParams(location.search);
if (params.get('utm_source') === 'homescreen') {
  navigator.sendBeacon('/analytics', JSON.stringify({ launch: 'installed-app' }));
  params.delete('utm_source');
  params.delete('utm_medium');
  history.replaceState(history.state, '', `${location.pathname}${params.size ? `?${params}` : ''}`);
}
```

The query string is part of what Chromium compares when it checks the manifest for updates
(web.dev, "How Chrome handles updates to the web app manifest"), so changing the tag later
is picked up on the next manifest update check rather than at the next launch.

### Moving the launch page without creating a second install

Before changing `start_url` on a shipped app, pin `id` to the value the browser already
derived (the old `start_url` without its query). With `id` present, the new `start_url` is
applied as an update; without it, Chromium treats the manifest as a different app.

```json
{
  "id": "/app/",
  "start_url": "/app/home/?utm_source=homescreen",
  "scope": "/app/"
}
```

Once `id` is set it stays fixed, whatever happens to `start_url` afterwards.

## See also

- [id manifest member](/reference/manifest/id/)
- [scope manifest member](/reference/manifest/scope/)
- [How installed PWAs pick up manifest changes](/reference/manifest/manifest-updates/)
- [Installability criteria: what makes a PWA installable](/reference/installation/installability-criteria/)
- [Web Application Manifest: start_url member](https://www.w3.org/TR/appmanifest/#start_url-member) (w3.org)
- [Uniquely identify PWAs with the web app manifest id property](https://developer.chrome.com/docs/capabilities/pwa-manifest-id) (developer.chrome.com)