Here for Origin Trials? The Web Install API is currently available as an Origin Trial in Chrome and Microsoft Edge versions 143-150. See Origin Trial Instructions to learn more.
The Origin Trial exposes the earlier
install_url-based shape of the API, which is being replaced. For background on that earlier design, see the archived install-url-version/ explainers.
This document is a starting point for engaging the community and standards bodies in developing collaborative solutions fit for standardization. As the solutions to problems described in this document progress along the standards-track, we will retain this document as an archive and use this section to keep the community up-to-date with the most current standards venue and content location of future work and discussions.
The Web Install API provides a way to democratise and decentralise web application acquisition, by enabling "do-it-yourself" end users and developers to have control over the application discovery and distribution process. It provides the tools needed 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.
This document is the main explainer for web app installation initiated by a website. It defines:
navigator.install() JavaScript entry point.A second, declarative entry point is incubating in parallel:
<install> element (WICG) -- a
user-agent-styled button that invokes the same install algorithm defined
here. It implements the same permission element
specification as <geolocation> and <usermedia>.<install> element should treat this document as the
normative source for backend behavior.Think about all the websites you use regularly - email, online shopping, social media, streaming sites, etc. For most users, this requires launching a browser and clicking or typing to get to those sites every time. Web applications enable developers to provide native, "app-like" experiences to end users while building on the trust set by their browser. However, for end users there's no standard, cross-platform way to acquire web applications. The process of distributing and installing web apps is both fragmented and limited:
A site can ergonomically trigger the user agent's installation flow, eliminating the need to subscribe to events, or try and direct users through potentially several layers of browser UX to discover the installation entry point on their own.
This is the simplest case -- no parameters are needed:
navigator.install();

A family of software applications -- productivity or photography suite, for example -- where each application is a separate web app. The developer can offer installation of any sibling app from the suite's home page, without redirecting users to platform-specific stores.
navigator.install({
manifest: "https://suite.example/mail/manifest.json"
});
navigator.install({
manifest: "https://suite.example/calendar/manifest.json"
});
navigator.install({
manifest: "https://suite.example/tasks/manifest.json"
});
A search engine or content site can offer a frictionless way to install an app that a user is searching for, surfacing app candidates the user might not otherwise have known existed.
A site whose purpose is to list and recommend web apps can offer one-click install for the apps it catalogs, enabling cross-device, cross-platform app directories that don't depend on any single store.
navigator.install({
manifest: "https://music.youtube.com/manifest.webmanifest",
manifestId: "https://music.youtube.com/?source=pwa",
});

The solution is a promise-based extension of the navigator interface that is simple and ergonomic for developers. A developer calls
navigator.install({ manifest: <url> [, manifestId: <url>] }).
The developer may omit manifestId if and only if the JSON at manifest
contains an id.
As an added convenience, the developer may omit the dictionary object entirely, in which case the currently loaded page's manifest is targeted for install.
The API's promise will:
WebInstallResult dictionary for
future-proofing.DOMException
value of:
AbortError: The user aborted the installation flow.DataError: There was a problem with the data provided. Notable
failures include:
manifest URL, failure to fetch the manifest, malformed
manifest file, etc.manifestId parameter when there was none
declared in the manifest filemanifestId parameter, but it did not match
the one computed by the browser.DOMException rejection codes.1. Install me! (No argument signature)
The developer can offer installation of their own site (ie. the currently loaded page) with minimal JavaScript:
const button = document.querySelector('#install');
button.addEventListener('click', async () => {
await navigator.install();
});
Reminder! Use of this signature requires you to add an
idfield to your site's manifest.json.
2. Suite of web apps. (Dictionary signature)
The developer can offer installation of a different site (that may or may not
contain an id):
const button = document.querySelector('#install');
button.addEventListener('click', async () => {
try {
await navigator.install({
// If relative, URLs resolve against the currently loaded document.
manifest: 'https://foo.com/manifest.json',
manifestId: 'https://foo.com/home',
});
// Success: promise resolved!
} catch (err) {
if (err.name === 'DataError' || err.name === 'TypeError') {
// Action needed: illegal/invalid parameters; invalid
// manifest data; etc.
} else if (err.name === 'AbortError') {
// User exited the installation flow. No action needed.
} else {
// Installation failed for an unexpected reason. Notify
// the user, or ask if they want to try again?
ShowRecoveryUX();
}
}
});
idAccording to the manifest spec,
if there is no id member present, the computed string resolves to that of the
start_url.
We acknowledge that only around 4% (as of 2024) of web apps have defined ids
in their manifest. We also know that ids are a critical part of
avoiding situations of multiple same applications with no path to being
updated -- if the developer ever changes their start_url without an id
declared, their app appears to UAs as a distinct, new application, and existing
installs are orphaned.
For apps that have an id defined in their manifest, the id may be
omitted from the API call. For apps that do not define the id field, the
API caller must include the expected, computed id*.
*The computed id is easily accessible in the 'Application' tab of Developer Tools in Chromium-based browsers.
install() is called.NotAllowedError.InvalidStateError.DataError.id field defined, continue. Else reject with
DataError.AbortError.Note: if the application is already installed, the UA can choose to display UX to launch the application. The UA should follow the same error behavior, resolving the promise if the user accepts the launch, and rejecting with
AbortErrorotherwise.
manifest onlyinstall({manifest: <url>}) is called.NotAllowedError.InvalidStateError.<url> is a valid URL, continue. Else reject with TypeError.<url> is cross-origin with the current document, the UA asks for
permission to install apps from other origins (if not previously granted).
If permission is granted, continue. Else reject with AbortError.<url> with credentials mode "omit" (no cookies
sent). If the fetch succeeds, continue. Else reject with DataError.id, continue. Else reject with
DataError.AbortError.manifest and manifestIdinstall({manifest: <url>, manifestId: <manifest_id>}) is called.NotAllowedError.InvalidStateError.<url> is a valid URL, continue. Else reject with TypeError.<url> is cross-origin with the current document, the UA asks for
permission to install apps from other origins (if not previously granted).
If permission is granted, continue. Else reject with AbortError.<url> with credentials mode "omit" (no cookies
sent). If the fetch succeeds, continue. Else reject with DataError.<manifest_id>, continue. Else reject with DataError.AbortError.install_urlWe publicly trialed a version of the API that accepted a document URL instead of manifest URL. The target document was fetched and loaded in the background, and its linked manifest was retrieved before proceeding with the same installation steps.
<a>Allow a new target type of _install to the HTML anchor tag. It could also
use the rel
attribute to hint to the UA that the url in the link should be installed.
<a href="https://airhorner.com" target="_install">honk</a>
<a href="https://airhorner.com" rel="install">honk</a>
Pros:
Cons:
a) like the target mean there is an increased
set of scenarios that might have unintended consequences for end users. For
example, how does a target of _ top differ from _blank? While we could look
at ignoring the target attribute if a rel attribute is present, the idea
is to use acquisition mechanisms that are already present in UAs.While there is certainly merit to an <a> based approach, we believe an element
<install> offers UAs better control over the presentation and end to end UX.
See <install> proposal.
When installation is initiated from a manifest URL, the target app's page will not be loaded before install completes. That can delay service worker registration and prevent online install/offline launch scenarios.
Current decision: Deferred. UAs can mitigate this in the meantime by automatically launching the app after installation.
Some apps serve manifests from CDNs on a different origin than the app's
start_url. Should this API require the manifest URL to be same-origin
with start_url, or can cross-origin manifests be supported with
additional trust checks that do not involve loading the entire document?
Current decision: manifest URL must be same origin with start_url.
Related to the above. Manifests may contain relative URLs, which are specified to resolve against the loaded document's URL. However, this proposal does not load the target document, so how should relative manifest URLs be resolved?
Current decision: resolve relative URLs using the original manifest URL,
since cross-origin manifest URLs are currently not supported.
Apps that version manifest filenames (for example, manifest-1.0.2.json) can
leave previously published navigator.install calls pointing at stale URLs.
Should the platform define guidance or a stable convention (for example,
indirection via a fixed URL) to reduce breakage?
Current decision: Rely on DataError to inform callers of broken URLs and await developer feedback.
The install consent UI is browser-rendered, using existing accessible PWA install surfaces.
Generally, we want to expose as little information as possible, while still maintaining usability for developers. Currently, that looks like -
DataError for any data/manifest-related failures.
AbortError for user cancellations.
Additionally, the promise can also reject with
TypeError: arguments were invalid; incorrect type; incorrect URL scheme; etcNotFoundError: missing documentNotAllowedError: missing user activationInvalidStateError: invoked outside the main frame, or invalid script stateSee #1341.
Side-channel considerations: Regardless of which option is chosen, the calling origin can partially infer outcomes through observable side channels. Focus/blur events reveal whether a secondary dialog was shown. These side channels exist independently of the promise result and limit the privacy benefit of withholding explicit results.
The content installed using the navigator.install does not inherit or auto-grant permissions from the installation origin. This API does not break the same-origin security model of the web. Every different domain has its own set of independent permissions bound to their specific origin.
When fetching the manifest and any of its resources, UAs must ensure that no cross-origin cookies or identifiable information are leaked to the target server.
This feature is not available in private browsing or off-the-record profiles, as web apps are not generally installable there. UAs must ensure failure signaling does not create a reliable private-mode detection channel (for example, not failing immediately in private modes).
A new "installation" permission is required if manifest is from a different
origin than the requesting site. This permission is associated with the
requesting origin.
This results in a new integration with the Permissions API.
The install API will make available the "web-app-installation"
PermissionDescriptor
as a new powerful feature.
This would make it possible to know programmatically if install would be blocked.
/* example of querying for the state of an installation permission using the Permission API */
const { state } = await navigator.permissions.query({
name: "web-app-installation"
});
switch (state) {
case "granted":
case "prompt":
navigator.install('https://productivitysuite.com');
case "denied":
// navigator.install will always abort. Developers
// can hide the install UI, or offer a redirect to
// the desired app's page.
break;
}
Note: For background documents, a permission prompt will appear for origins that do not have the capability to install apps. Even if the installation is of a "background document" in the same origin, for consistency the origin must have the permission to install apps. The only cases that will not prompt for the permission are the installation of the "current document" or a "background document" in an origin that already has installation permissions.
Example:
The app located in https://productivitysuite.com displays in its homepage 3 buttons that aim to install 3 different apps (notice all apps are in the same origin):
https://productivitysuite.com/texthttps://productivitysuite.com/slideshttps://productivitysuite.com/spreadsheetThe end user goes to the homepage in the https://productivitysuite.com's origin and clicks on the button to install the presentation application. As this is a background document (not the current document the user is interacting with) and the origin does not have permission to install apps, a permission prompt will appear. If this permission is granted for the origin, it can now install apps. After this permission prompt, the second prompt where the user confirms the installation appears.
The end user then tries to install the text processor, and since the origin has been granted the permission, then the UA will skip the permission prompt and skip directly to confirm installation with a prompt indicating that "productivity suite wants to install text processor". The installation permission is bound to an origin.
If the user were to deny the permission to install for the origin, they could browse to the app itself and once there, they could install the application. In this case, there wouldn't be any permission prompt required as this would now be a current document installation.