#1245: Question: Review manifest-first Web Install API

Visit on Github

Opened Jul 14, 2026

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

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).
  • 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.

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.

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.

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), 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 -- for zero-JS usage, with the following properties -

The declarative entry point inherits the Permission Element (PEPC) 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) -- 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

    "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, and are active in the PWA community

    "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 -- 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 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 about diminished user agency over installation. As described under Additional abuse mitigations, 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 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, 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. 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, 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 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. 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). 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
  • 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

Discussions