[w3ctag/design-reviews] Question: Review manifest-first Web Install API (Issue #1245)

LiaHiscock created an issue (w3ctag/design-reviews#1245)

# **Brand *newer*** TAG Review Update

Hello TAG!👋 @marcoscaceres @torgo @jyasskin

We've been hard at work the last 12 months gathering feedback from the community, addressing your suggestions, and iterating on our proposal. We've made substantial changes and are ready for a new review. Let me know if you'd prefer I open a new issue!

**We acknowledge that we may have a fundamental difference of opinion on whether the web should have the capability to
install. In the interest of productivity, we'd like to explicitly request *technical architecture* feedback on this proposal**.

## Updated explainers

- **Web Install API (manifest-url design):** [MSEdgeExplainers/WebInstall/explainer.md](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/WebInstall/explainer.md)
- **`<install>` element (manifest-url design):** [WICG/install-element/explainer-manifest-url.md](https://github.com/WICG/install-element/blob/main/explainer-manifest-url.md)

## What changed

The redesigned API is built around minimizing the cross-origin attack surface, which was a point of interest in the original API.

| Aspect | Original design (`install_url`) | Updated design (`manifest` URL) |
| --- | --- | --- |
| **Install target** | A full HTML document - `navigator.install(https://foo.com)` | The Web App Manifest, fetched directly as JSON - `navigator.install(https://foo.com/manifest.json)` |
| **What gets loaded** | HTML parsed, scripts executed, subresources fetched from the target origin | A single JSON file |
| **Redirect handling** | Brittle. Lacked a strong, secure solution for redirects | Not needed. Manifest is fetched directly |
| **Entry points** | Imperative `navigator.install()` only | Imperative `navigator.install()` **and** a declarative method, as explicitly requested by TAG - the `<install>` element |

**What stayed the same:**

- The core goal is unchanged: give sites a standard, discoverable way to trigger installation of a web app.
- Integration with existing platform primitives (the Web App Manifest and the [Permissions API](https://www.w3.org/TR/permissions/)).
- `install_sources` and `navigator.getInstalledApps()` are still removed.

### Major change 1 - the manifest-first install model
The new API fetches the manifest directly as a JSON resource -- no HTML document is loaded, parsed, or executed.

```js
navigator.install({ manifest: "https://app.example/manifest.json" });
```

**As an added convenience**, web developers may omit the dictionary of options - `navigator.install();` - in which case the currently loaded page's manifest is targeted for installation via `beforeinstallprompt`'s [discovery algorithm](https://github.com/w3c/manifest/pull/1206).

In the interest of security, if the JSON at `manifest` does not contain `id`, the developer must pass the expected, computed manifest id. See explainer section - [A note on manifest id](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/WebInstall/explainer.md#a-note-on-manifest-id).

```js
navigator.install({
  manifest: "https://app.example/manifest.json",
  manifestId: "https://app.example/home"
});
```

**Security/privacy properties:**

- **`manifest` must be same-origin with its `start_url`.** CDN-served manifest URLs will not be initially supported, to reduce spoofing risk.
- **No credential leakage.** The direct manifest fetch omits cookies and credentials. No cross-origin request context is disclosed to the target server.
- **No executable surface.** Unlike the prior `install_url` design (which required loading a full HTML document), the manifest-URL design never executes scripts or loads subresources from the target origin during the install flow.
- **Minimal information exposure to the caller.** The API promise resolves/rejects with coarse outcomes only (`DataError`, `AbortError`, `TypeError`, etc). No manifest contents, or persistent app identity are leaked to the calling origin.

### Major change 2 - the `<install>` element

Responding directly to TAG's suggestion to consider a declarative approach (and the `rel` discussion from @jyasskin's [earlier feedback](https://github.com/w3ctag/design-reviews/issues/1051#issuecomment-2685948570)), we've developed two complementary entry points:

1. The imperative `navigator.install()` API -- for developers who need control over UX, promise-based outcomes, and integration with custom-rendered environments (WebXR, WebGL).
2. The declarative [`<install>` element](https://github.com/WICG/install-element) -- for zero-JS usage, with the following properties -

The declarative entry point inherits the [Permission Element (PEPC)](https://wicg.github.io/PEPC/permission-elements.html) security model:
- **UA controls all rendered content** -- the button text, icon, and styling are determined by the browser. Sites cannot spoof or mislead users about what the button does.
- **Visibility and occlusion checks** -- the element must be visible, unobscured, and meet contrast requirements. Clickjacking is structurally prevented.
- **Temporal cooldowns** -- rapid repeated activations are throttled.
- **Consent comes from the click.** The user activating the visible, UA-controlled button *is* the consent signal -- no separate permissions dialog, no prompt fatigue, no unrecoverable "blocked" state.

The imperative `navigator.install()` path, by contrast, gates cross-origin installation behind a `web-app-installation` permission (integrating with the [Permissions API](https://www.w3.org/TR/permissions/)) -- the appropriate trade-off for a JS-driven flow where the UA cannot make the same assumptions about user intent that a visible, validated element provides.

### Additional abuse mitigations (both entry points)

- **Transient user activation required** -- no silent/background installs.
- **Blocked in cross-origin subframes.**
- **Not available in private/Incognito browsing.** Failure signaling must avoid creating a private-mode detection channel.
- **UAs may implement cooldowns or throttling** on install requests per origin.

## Community feedback

Since your last review, we've also been actively engaged with the web developer community, and have received strong positive signals from early adopters:

- **Progressier** (PWA toolkit used by thousands of developers) has [integrated Web Install API support](https://progressier.com/pwa-capabilities/web-install-api)
  > *"I’ve implemented it on the installation pages for all my customers at Progressier, and it really does make the install process much smoother. Previously, I had to redirect users to the domain of installation -- but with navigator.install(), it's much more direct."*

  > *"It’s a fantastic improvement, and I really hope this feature makes it to a full public release soon!"*

- **PWAStore.io** has also [integrated Web install API support](https://www.pwastore.io/web-install-api), and are [active in the PWA community](https://www.reddit.com/r/PWA/comments/1o1excp/comment/nih3c63/?context=3&share_id=2od-QVO3ifwIRUvPKRfR_&utm_content=1&utm_medium=ios_app&utm_name=ioscss&utm_source=share&utm_term=1)
   > *"Seems like there’s some limits in it now as it’s still being tested but from my limited testing, it feels magical ✨"*

- Google Chat has a use case to promote app install from the app's site, as well as from closely related Google properties (e.g. Gmail) that are cross-origin.

- A partner gave feedback after testing that the former `install_url` version was too brittle, due to the frequent utilization of redirects, which is addressed by the switch to `manifest` URL.

- **Open feedback threads:** [WICG/install-element#23](https://github.com/WICG/install-element/issues/23) -- comparing imperative vs. declarative approaches

## Re. previous TAG concerns

We want to be transparent about where things stand. This review was previously closed with an *Unsatisfied* resolution: the same-origin user need was considered clear and strongly supported, but the cross-origin capabilities remained a significant concern, and the recommendation was to ship same-origin first and revisit cross-origin later.

After a further year of developer feedback and two origin trials, we have re-framed this proposal around a single, coherent capability -- **the ability to install web apps** -- which we believe necessarily spans both same-origin *and* cross-origin installation. We remain committed to cross-origin as the real, unmet gap in the platform, and we welcome any **technical** feedback you may have on our new proposal.

### Centralization & gatekeeping

The TAG was [concerned](https://github.com/w3ctag/design-reviews/issues/1051#issuecomment-2943539903) that cross-origin installation could create centralization or gatekeeping effects -- for example, if a new app store needed the approval of each app before it could install it, that would build a moat around incumbents.

We previously agreed this would be harmful, and have not brought back the mechanism that created it: **`install_sources` is still gone.** There is no site-controlled allowlist of who may install an app, so a newly created store is on equal footing with an established one --
no per-app approval, no registry, no gatekeeper.

### User agency & control

The TAG was [concerned](https://github.com/w3ctag/design-reviews/issues/1051#issuecomment-2943539903) about diminished user agency over installation. As described under [Additional abuse mitigations](#additional-abuse-mitigations-both-entry-points),
every install requires transient user activation and explicit, UA-controlled consent, is blocked in cross-origin subframes and sandboxed contexts, and is unavailable in private browsing. The user is always the one who decides whether an install happens.

### Privacy of cross-origin install signals

The TAG [raised](https://github.com/w3ctag/design-reviews/issues/1051#issuecomment-2943539903) the privacy implications of cross-origin install signals -- in particular, exposing whether apps are installed (or later uninstalled) back to a caller. As covered under [Major change 1](#major-change-1---the-manifest-first-install-model), the design minimizes this surface: `navigator.getInstalledApps()` remains
removed, the manifest fetch is credential-less (`credentials: "omit"`), and only coarse promise outcomes are returned -- so no install state, manifest contents, or persistent app identity leak to the calling origin.

### On limiting the proposal to same-origin only

We acknowledge that the TAG advised limiting the proposal to same-origin. We have heard this clearly. However, we continue to believe cross-origin installation addresses a genuine, unmet need: app directories, search/discovery surfaces, and suites of related apps (same site, cross origin) all depend on one origin being able to offer installation of an app hosted elsewhere. This came through repeatedly in direct developer feedback during the developer trial, and two origin trials we ran.

### The manifest `id` requirement

The TAG noted it did not feel the `id` override mechanism was necessary. Our motivation is that only ~4% of web apps (as of 2024) declare an `id` in their manifest, yet `id` is essential to avoid duplicate, un-updatable installs, and ensure users don't miss critical site security updates. For apps that declare an `id`, the caller may omit it; the override exists only so that apps *without* a declared `id` can still be installed reliably.

### Duplication of `beforeinstallprompt`

This API is not a duplicate of [`beforeinstallprompt`](https://wicg.github.io/manifest-incubations/#beforeinstallpromptevent-interface).
`beforeinstallprompt` can only prompt to install the *currently loaded* web app, requires subscribing to an event and managing its timing, has no cross-origin or declarative story, and is not on a standards track. The Web Install API instead provides an explicit, developer-invoked entry point that can target *any* app by its manifest URL -- a superset of what `beforeinstallprompt` offers, delivered through a stable, promise-based (and declarative) surface.

This distinction is borne out by developers who have shipped both. Jeremy Keith, who has [used both APIs in production](https://adactio.com/journal/22444), summarizes the comparison bluntly: `beforeinstallprompt` "relies on you capturing and delaying an event that may or not fire at all," whereas with `navigator.install()`, "based on a user interaction -- like a click on a button -- the browser initiates the installation process. The user still has to confirm… You know how geolocation or web notifications work? It's like that." His verdict:

> *"I've used both APIs in production, so I'd like to offer my balanced feedback
> on both: `BeforeInstallPromptEvent` sucks. `navigator.install` rocks."*

### On the "try-before-you-buy" / declarative approach discussion

TAG [previously](https://github.com/w3ctag/design-reviews/issues/1051#issuecomment-2685948570) expressed a preference for users to interact with the target site before being prompted to install, and suggested `<a rel="install">` as a mechanism that would naturally navigate to the app in non-supporting browsers.

We took this feedback seriously and evaluated both paths. We chose `<install>` over `<a rel="install">` (a developer-styled link) for the reasons detailed [in the explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/WebInstall/explainer.md#declarative-install-with-a). We want to be transparent that this is a different shape than what TAG suggested, but we believe
the security tradeoffs are worth it.

That said, we want to highlight a gap in the "navigate first, then install" model: **once a user arrives on the target page, installation success rates remain low** because the web platform currently provides no standard, discoverable installation entry point on that page. Users must independently discover browser-specific UI (an address bar icon, a menu item, or an ambient prompt), and empirical data from the Origin Trial shows many users never find it. This is the core problem the Web Install API is designed to solve -- and it applies equally whether the user arrived organically or via an `<a>` tag navigation.

To put numbers to this, browser-controlled installation entry points see very low engagement: Chrome's ambient omnibox install icon has roughly a **0.01%** click-through rate, and even browser-initiated install prompts sit around **1.2%** ([Chrome data, shared by @dmurph in WebKit/standards-positions#619](https://github.com/WebKit/standards-positions/issues/619)). We read this as a discoverability failure rather than a lack of user interest: when installation depends on users locating browser-specific chrome, the overwhelming majority never do. A standard, site-surfaced entry point is what lets developers place an install affordance where it is actually contextually relevant.

In other words: navigating to the app *is* valuable for building trust, but it doesn't solve the installation discoverability problem on its own. The `<install>` element and `navigator.install()` exist precisely to close that gap -- giving both the originating page *and* the target page a reliable, standard way to trigger installation.

We acknowledge that `<install>` doesn't enforce a "visit first" model at the platform level. However:
- UAs are free to implement richer pre-install UX (previews, app cards, "peek" windows) as an implementation detail.
- UAs may also render additional information in `<install>` itself, such as the app's origin or icon. See `<install>`'s [Open Questions](https://github.com/WICG/install-element/blob/main/explainer-manifest-url.md#open-questions)
- Developers who *want* the "visit first" pattern can still use a plain `<a>` link -- this API doesn't prevent that workflow, it just doesn't mandate it.

## Conclusion

We'd welcome TAG's re-review and technical feedback on the updated design. We believe the manifest URL approach and the declarative element address the core architectural concerns raised previously. Happy to answer any questions or provide additional detail.

cc @christianliebel @mikewest @dmurph

<!-- Content below this is maintained by @w3c-tag-bot -->
---

Track conversations at https://tag-github-bot.w3.org/gh/w3ctag/design-reviews/1245


-- 
Reply to this email directly or view it on GitHub:
https://github.com/w3ctag/design-reviews/issues/1245
You are receiving this because you are subscribed to this thread.

Message ID: <w3ctag/design-reviews/issues/1245@github.com>

Received on Tuesday, 14 July 2026 18:43:41 UTC