# id manifest member

> id identifies an installed PWA independently of start_url, so the launch URL can change without the browser treating the manifest as a second app.

`id` is the string that identifies the web application. Before the member existed, each
browser derived identity from something else, usually `start_url`, so changing the launch URL
made the manifest describe a new app and a later install sat beside the old one. With `id`
set, the identity and the launch URL are separate values: `start_url` can move, the identity
stays.

Chrome 96, Edge 96, Samsung Internet 17.0, Safari on iOS 16.4, and Safari 17 on macOS process
the member (BCD `html.manifest.id`). Firefox 157 parses it without effect, because Firefox on
desktop installs no web apps from the manifest. Browsers that ignore `id` keep their previous
identity rule, so adding the member never breaks an install elsewhere.

## Member

- **Type**: string, parsed as a URL against the origin of `start_url`. A value that resolves
  to another origin is rejected; Chromium logs `property 'id' ignored, should be same origin
  as document.` and falls back to the default.
- **Default**: the resolved `start_url`. An absent, empty, or unparsable `id` couples the
  identity to the launch URL, which is the situation the member exists to avoid.
- **Example value**: `"/?homescreen=1"`, which for an app on `https://example.com` resolves
  to `https://example.com/?homescreen=1`.

The resolved value is compared with the identity of every installed app when a manifest is
processed. A match means "update this app"; no match means "a new app". Because the comparison
is on the full resolved URL, `"/"` and `"/?v=2"` are different apps, and a value should be
chosen once and left alone. Chromium's update check runs once a day for an installed app and
applies most field changes only after every window of the app closes (web.dev, "How Chrome
handles updates to the web app manifest"); a changed `start_url` is accepted as an update only
when `id` is present and unchanged.

`navigator.getInstalledRelatedApps()` does not read `id`: it reports related native apps and,
on Chrome 80+, a PWA listed in `related_applications` with `platform: "webapp"`, matched by
manifest URL rather than by `id`.

:::observed
Chrome 155 on macOS 26 (English UI), DevTools > Application > Manifest, **Identity** section,
row **App Id**: for a manifest without `id` the panel shows the computed value followed by
`Note: id is not specified in the manifest, start_url is used instead. To specify an App Id
that matches the current identity, set the id field to <computed value>.` with a copy button.
Adding `"id"` with exactly that value changes nothing for existing installs; adding any other
value makes the next install a separate app.
:::

## Examples

The manifests below show the value to pick before the first release, the change `id` makes
safe, and how to recover an identity for an app that shipped without one.

### Fixing the identity before the first install

A short root-relative path is enough; the member resolves against the origin of `start_url`,
so a scheme and host add nothing. The launch URL carries an analytics parameter that may
change later without touching the identity.

```json
{
  "name": "Ledger",
  "id": "/",
  "start_url": "/app/?source=pwa",
  "scope": "/app/",
  "display": "standalone"
}
```

Chrome 96+ and Safari 16.4+ record the identity as `https://example.com/`; a later manifest
with `"start_url": "/dashboard/?source=pwa"` updates the installed app instead of creating a
second one.

### Moving start_url without a duplicate install

With the identity fixed, a redesign that changes the launch path only edits `start_url` and
`scope`. The identity line is the one that must not move.

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

On Chrome 155 the installed app picks up the new `start_url` at the next daily manifest check
and applies it after the last app window closes; on a browser that ignores `id`, the same
change still works as long as that browser never keyed the identity on `start_url`.

### Adopting id for an app that shipped without one

An app installed before `id` was declared has the identity Chromium computed from its
`start_url`. To keep those installs, set `id` to exactly the computed value shown in DevTools;
the script below reads the manifest the page links and prints the value to copy, falling back
to a note when the manifest fetch fails.

```js
async function suggestedId() {
  const link = document.querySelector('link[rel="manifest"]');
  if (!link || !('fetch' in window)) return null; // Nothing to inspect.
  const manifest = await fetch(link.href).then((r) => r.json()).catch(() => null);
  if (!manifest) return null;
  if (manifest.id) return new URL(manifest.id, location.origin).href;
  return new URL(manifest.start_url ?? '/', link.href).href; // Chromium's default identity.
}

const id = await suggestedId();
console.log(id ? `Set "id" to ${new URL(id).pathname + new URL(id).search}` : 'Manifest not readable');
```

For `"start_url": "/app/?source=pwa"` the printed value is `/app/?source=pwa`; declaring that
string as `id` freezes the identity existing users already have, after which `start_url` is
free to change.

## See also

- [start_url manifest member](/reference/manifest/start-url/)
- [How installed PWAs pick up manifest changes](/reference/manifest/manifest-updates/)
- [The N+1 install problem: one PWA, multiple independent installs](/reference/installation/n-plus-one/)
- [getInstalledRelatedApps(): detecting an installed native or PWA counterpart](/reference/installation/get-installed-related-apps/)
- [Web Application Manifest: id member](https://www.w3.org/TR/appmanifest/#id-member) (w3.org)
- [Uniquely identifying PWAs with the web app manifest id property](https://developer.chrome.com/docs/capabilities/pwa-manifest-id) (developer.chrome.com)
- [WebKit Features in Safari 16.4](https://webkit.org/blog/13966/webkit-features-in-safari-16-4/) (webkit.org)