#1051: (brand new ✨) Web Install API

Visit on Github

Opened Feb 17, 2025

Hola (again) TAG!

I'm requesting a TAG review of the Web Install API.

The Web Install API provides a way to democratise and decentralise web application acquisition, by enabling "do-it-yourself" developers to have control over the application discovery and distribution process, providing them with the tools they need to allow a web site to install a web app. This means end users have the option to more easily discover new applications and experiences that they can acquire with reduced friction.

Further details:

  • [ x ] I have reviewed the TAG's Web Platform Design Principles
  • Previous early design review, if any: #946/#888
  • Relevant time constraints or deadlines:
  • The group where the work on this specification is currently being done: W3C Web Applications WG
  • The group where standardization of this work is intended to be done (if different from the current group):
  • Major unresolved issues with or opposition to this specification:
  • This work is being funded by: @MicrosoftEdge

You should also know that...

This is an updated design based on previous reviews and feedback from multiple parties, including TAG. The design has been streamlined to be consistent across all calls.

Discussions

Log in to see TAG-private discussions.

Comment by @zcorpan Feb 21, 2025 (See Github)

A comment on the self-review:

How does your feature handle non-"fully active" documents? This feature does not interact with non-"fully active" documents.

This should be considered when writing the spec. It's possible to have a reference to the navigator object of a non-"fully active" document, and call install() on it. It should probably do something like https://html.spec.whatwg.org/#dom-navigation-navigate step 3 and 7

Discussed Feb 24, 2025 (See Github)

Jeffrey: I left a comment saying "they haven't addressed"... I think we can postpone...

Dan: breakout on this at the f2f?

Jeffrey: their non-goals .. a bunch of capabilities around how you pick applications... Talking about the whole problem space makes sense... They are clearly looking at this whole space. Our perspective should be about the space.

Dan: they think they have addressed some of the concerns that we have raised...

Jeffrey: I could leave a comment asking them ...

Dan: Yes please...

jeffrey to leave comment

Comment by @jyasskin Feb 26, 2025 (See Github)

Hi! First, thank you for adjusting the proposal in response to some of our previous feedback, in particular removing install_sources and postponing navigator.getInstalledApps(). I don't see adjustments or explanations in response to some of the other comments:

We don't expect to review this again in detail before our comments from last time are more fully addressed.

Comment by @diekus Feb 27, 2025 (See Github)

Hola @jyasskin ✨

Thanks for your prompt review. I have modified the explainer to address the two points you mention are still pending, and I'd like to expand on them in this issue as well.

First and foremost, I want to express that I firmly believe that both solutions are desired. As stated in the explainer, we believe that a declarative implementation is a simple and effective solution, and should be considered as an additional method of installation in the future.

For the current solution, we want to go with an imperative implementation since it allows more control over the overall installation UX:

  • Allows the source to detect if an installation occurred with ease. (resolves/rejects a promise).
  • Supports install_url. The current reality of the web is such that some websites intentionally do not expose their manifest nor user experience until the user has authenticated. We believe that having the ability to offer an install_url (one where the only content may simply be a link to the manifest) is a key advantage for both developers and end-users. The declarative version does not cleanly support this concept (as a fallback navigation to an install_url that was specified as the href on non-supporting UA's might leave the user confusingly stranded on a blank page.
  • Code can be used to detect if an origin has permission to install apps (and/or if the UA supports the API at all), and UX can be tailored to change accordingly (for example, remove a button or display a link instead).
  • The developer ergonomics of handling a promise are better than responding to an a tag navigation.
  • Keeps the user in the context, which can be beneficial in certain scenarios (if the developer wants to take the user out of the current context, they can do so with a a tag).

Overall it gives developers more control to create really compelling UX experiences for end users, which are the ones that will benefit from frictionless app acquisition in the end.

Regarding the idea of a try-before-you-buy scenario, it's a really good call and great idea! I have added a section in the explainer as well to mention it. I do think that is an implementation detail, up to the UA. Some browsers might want to show a prompt, some a confirmation dialog, some might want to do a rich-install-prompt... and some might even load the app for the users to see before confirming that they want to install it! (like the neat link you sent about Arc's peek). All this is up to the browser and what they consider is the best UX to present to the user before installing an app.

I hope these points address your concerns and we can continue to discuss the Web Install feature!

Discussed Mar 31, 2025 (See Github)

dan summarizes

Jeffrey: I think we should allow browsers to experiment in this area without damaging other browsers... that's where the declarative comment came from... I haven't gone through the update yet. Want to find them a way forward.

Dan: new concept they have introduced... active tab..

Jeffrey: MS have done more to explore Install...

we agree to continue to review and come back to it next week

Discussed Apr 14, 2025 (See Github)

Hadley: @torgo, @marcoscaceres not present Matthew: There are several comments on the private thread Hadley: We will leave this until the assignees can attend

Discussed Apr 21, 2025 (See Github)

Marcos: the whole democratization thing ... bothers me. The web is a quite democratic platform already. this provides an API for something that most browsers can do. This doesn't change anything just adds a JavaScript API for existing functionality. Need to see what's changed.

Martin: Long discussion.. most of us here were relatively OK with a web app calling an API to install itself. Browser could choose to ignore, or it could raise UI for installaiton. The problem we had was online catalogs and installing someone else's webapp.

Marcos: Yes and I already provided a bunch of feedback.. Numerous ways of doing a this thing... Goal is what?

Dan: a key point is the background document...

Marcos: it's complicated. The whole thing of having URLs... overriding ID seems like a foot gun. Developers do want to have their own install button... because some browsers don't have a good UI to install... What's interesting here from a TAG PoV - we've been discussing this API for well over a decade. We've examined the challenges. Browser developers have not made sufficient strides to address the usability problems of installation. So yes this is a warranted API. User needs are not adaquately addressed by the current browser UIs. Having said that, the rest of the stuff around install URLs, manifest paramaters, seems like overreach.

Dan: I think it's totally reasonable to go back to our previous guidance and say "We're still concerned about the cross-origin cases. Please move ahead with same origin install - we like it - then once we see how this plays in the ecosystem then let's try to solve the X-origin cases at that point."

Marcos: yes ... it's that and the argument I made to Diego ... does my bank's web site need to have this capability? Why does any web site in the world need to be an app store? They might not be playing favourites, but they add this creepy ability to every website.

Lola: I feel indifferent on this. I don't know enough, but Marcos' point about providing this ability to every website, while I agree that this might not be the best thing, think about user expectations and how they use the web. Because of the shift to app-based browsing, many users do expect that sites have some installable app.

Marcos: good point and it's why we designed web manifests; the idea is that it is a progressive enhancement, so that any website can be installed, [but those with manifests are better when installed]. The user needs to be in control, it needs to be their choice. This is why Safari resisted the API. The manifest then provides the nicer experience. This is why the store thing is problematic is because it starts to shift that control away from users. You get install buttons everywhere. Also the cross-origin thing doesn't make much sense. People could go to the site and install it from there.

Torgo: We have this situation where sites popup prompts for installing the app, not the site. The site can't action the installation of a webapp instead. You get the native app instead. Lots of sites nag constantly about that native app. An option to install the web site might reduce the desire to do that.

Marcos: Been doing this so long, that conflates different problems. NYT/WaPo have the same issue. it's not related to developers doing that, it's a browser issue. if the meta tag that prompts the user is dismissed, that prompt doesn't come back. In safari, the site doesn't learn that the user chose not to engage.

Torgo: Very close to app vs. web, but this could do good. Do we have consensus that we have an OK for same origin, please hold cross-origin.

Christian: install method ... will launch in chrome ... 3 flavours...

Martin: shipping or an experiment?

Christan: it's an origin trial so an experiment...

Martin: the logical extension is .. if I can install it through a web app store then I can uninstall it through a web appp store... there are certain sites on the web that have signifigant power such as search engines, social networks...

Marcos: that's why some browsers are insistent that install is a user decision...

Xiaocheng: we need to do things in a more webby approach... A decentralzed store for web apps..

Marcos: that doesn't change the core principle...

Hi Diego,

We'd be "satisfied" here if this were limited to the same origin case for reasons we previously indicated. But we are leaving this as "unsatisfied" on the basis that it includes the cross origin capability. We refer you to our previous comments on that for the details.

ACTION: Dan to wordsmith and propose a closing comment

Discussed Apr 28, 2025 (See Github)

Martin: Without the privacy-invasive APIs, the cross-origin part is a bit incomplete. We should acknowledge that they tried.

Marcos: Wish we could get them back to just showing the install dialog, and that's it. That's the 98% use case.

Martin: Let the website dedicate the amount of screen it thinks is appropriate.

Martin: not sure what else we can suggest in terms of text.

Jeffrey: we should encourage them to do the same site

Martin: you mean same origin ?

Jeffrey: no, that's not division they are using. There are background documents, etc.

Jeffrey: navigator.install(install_url) can install background documents; navigator.install() can't.

Martin: the central concern is the spooky action at a distance thing.

Jeffrey: So navigator.install(same_site_url) is fine for now.

Marcos: As long as it doesn't involve any additional magic, like ID overrides. This just opens the UI. Don't want it being an update mechanism or bringing in any extra scope.

Action on Marcos to suggest text; ask Torgo to follow up.

Discussed May 12, 2025 (See Github)

(Martin) Torgo needs to revise the proposed text based on the ensuing discussion.

Torgo: I do.

Christian: I'd like to discuss the API shape, when you've done that.

Dan: then let's keep it agenda+ and come back it next week.

Discussed May 26, 2025 (See Github)

DanA: Had hoped to have a discussion with Marcos about it before posting the draft comment. Suggest waiting for that.

Discussed Jun 2, 2025 (See Github)

Marcos gives an update: Make WWDC go away and we can resume normal operation.

Dan: Hoping to have Marcos post the comment. It's not that I disagree with the criticism, but I see the upsides of the proposal and probably would prefer not to post myself. Happy with the consensus of the group though.

Marcos: OK. What we wrote was good. I'll check and post or come back.

[Returning to this, later in the meeting]

marcos: I added a little bit of text around the ID override mechanism is necessary, but this is something that the working group might consider in more detail. I considered feedback from Christian, who suggested "decline", rather than "unsatisfied". ... simple, based on real-world experience, and implementer interest. Only Microsoft are interested in the cross-origin stuff.

Beyond that, I'm satisfied with what's there.

Dan: I'm happy with the comment.

Martin: I'm happy with it too. I'd be unhappy with a decline -- we ARE making a decision here.

Marcos: Ok. Let's change it back to "unsatisfied".

Dan: I think it has to be one of those two. "Decline" is not a negative comment from us. Why does Christian think we should decline?

Marcos: I guess because we are satisfied with part of it, but not the other part -- was his rationale. We can make that clear in the text.

Martin: I think if the objections to the cross origin thing wasn't so strong, we chould use "satisfied with concerns." But the parts we don't like are most of it.

Marcos: I'll add that.

Martin: Yes, we liked the same origin install thing. If it was just that, we'd be "satisfied".

Marcos: it's the ID stuff

Martin: They don't need the ID stuff for the same origin one. only for the cross origin one

Marcos: no, they also want it for the same origin one.

Martin: Oh, OK

Marcos: done

Resolved, shipped, sealed, delivered, with a bow on it

Comment by @LiaHiscock Jul 13, 2026 (See Github)

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

Comment by @marcoscaceres Jul 14, 2026 (See Github)

@LiaHiscock, sorry, would you mind filling a new issue for this? Just keeps things organized for us (and our tools). Excited to take a look at the new updates.