This document is a starting point for engaging the community and standards bodies in developing collaborative solutions fit for standardization.
- This document status: Incubating
- Expected venue: W3C Web Incubator Community Group
- Current version: this document
- Authors
- Participate
- Status of this Document
- Introduction
- Relationship to other proposals
- User-Facing Problem
- Use Cases
- Proposed Approach
- Results, errors, and debuggability
- Alternatives Considered
- Open Questions
- Accessibility, Localization, Privacy, and Security Considerations
- Stakeholder Feedback / Opposition
- References & Acknowledgements
The <install> element is a declarative HTML element that allows web developers
to offer installation of web applications directly from a page. It renders a
user-agent-controlled button whose text and iconography are determined by the
browser, providing a strong signal of user intent and protection against spoofing.
The element is part of the Permission Element
family, sharing the same security model, styling restrictions, and validation
infrastructure.
- Web Install API (MSEdgeExplainers) -- defines the backing install algorithm, manifest fetch and validation pipeline, consent UI contract, error taxonomy, and cross-origin / sandbox / activation gates. Normative for backend behavior. This document references but does not re-specify those algorithms.
- Permission Element spec (WICG) -- defines the base infrastructure
that
<install>is built on:HTMLCapabilityElementBase,InPagePermissionMixin, the blocker system, intersection observer visibility checks, and styling restrictions. This infrastructure ships in production via the<geolocation>(Chrome 144) and<usermedia>elements. - This document -- defines install-specific properties/behaviors on top of the CapabilityElementBase and WebInstall backend.
| If you're asking… | See |
|---|---|
| How do I add an install button to my page? | This doc |
| Why won't the element activate? | Permission Element spec |
| What styling am I allowed to apply? | Permission Element spec |
| Why did the manifest fail to fetch or parse? | Web Install API |
| What events fire after activation? | This doc |
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:
- Each browser has different, often hidden, entry points for installation (address bar icons, menu items, prompts). Users may not understand what the icon/prompt in the browser's address bar does, or how to deep search several layers of browser UX to add the app to their devices.
- Users may not know that a web app exists for the site they're visiting, for a related origin they're reading about, or that "installation" is even possible on the web -- and many users don't use app stores to discover new app experiences.
- Developers have no standard declarative mechanism to present an install action to users.
- Cross-origin installation (e.g. an app catalog installing apps from other sites) has no built-in web platform support.
Element-specific:
- Provide a declarative way to install web applications, requiring no JavaScript for basic usage.
- Give the user agent control over the button's content and presentation, providing a trustworthy signal of user intent.
- Offer progressive enhancement through fallback content for browsers that don't support the element.
- Integrate with the existing Permission Element infrastructure for consistent security, styling, and validation behavior.
Shared with navigator.install:
- Enable a site to install a web app identified by its manifest URL, subject to user consent.
- Extend the functionality of beforeinstallprompt, which cannot install content other than the current loaded web application.
- Keep the consent UI clearly attributable: the user sees which site is asking and which app is being installed.
- Avoid creating a cross-origin probing surface: the calling site should learn as little as possible about install state, manifest contents, or app identity.
Element-specific:
- Replace navigator.install(). The imperative and declarative APIs serve complementary use cases and share a backend implementation.
Shared with navigator.install:
- Define what "installation" means. This varies by platform and browser.
- Silent or unattended installation. User consent is always required.
- Installing arbitrary web content that is not an app. The target must have a manifest file. See historical context
- Installing native apps, browser extensions, or other non-PWA artifacts.
- Reporting install state of arbitrary apps back to the caller.
A web developer 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 attributes are needed:
<install></install>A web-based app store or catalog can install apps from other origins by supplying the manifest URL of each listed app:
<install manifest="https://music.youtube.com/manifest.webmanifest"
</install>A productivity suite can install related apps from the same origin:
<install manifest="https://suite.example/mail/manifest.webmanifest">Install Email</install>
<install manifest="https://suite.example/calendar/manifest.webmanifest">Install Calendar</install>
<install manifest="https://suite.example/tasks/manifest.webmanifest">Install Tasks</install>As noted in the related proposals section, this approach assumes familiarity with the permission element spec, which outlines in detail the element's behavior, including styling and activation restrictions, error handling, etc.
A declarative <install> element that renders a button whose content
and presentation is controlled by the user agent. Similar to other
permission elements (e.g. <geolocation>),
the user agent's control over the element's content means that it can
make plausible assumptions about a user's contextual intent. Users who
click on a button labeled "Install" are unlikely to be surprised if an
installation flow begins.
The element renders standardized text and iconography controlled by the user agent:
manifest-- URL of the web app manifest to install.manifestId-- The manifest id of the app to install.
Both attributes are optional. The developer may omit manifestId, if and
only if the JSON at manifest contains an id field.
As an added convenience, the developer may omit manifest, in which case the
currently loaded page's manifest is targeted for install.
<!-- Install the current page. -->
<install></install>
<!-- Install a specific app by its manifest URL. -->
<install manifest="https://app.example.com/manifest.webmanifest"></install>
<!-- Install a specific app whose manifest does not declare an `id` -->
<install manifest="https://app.example.com/manifest.webmanifest"
manifestId="https://app.example.com/?source=catalog"></install>If the user agent doesn't support installation, fallback content can be rendered:
<install manifest="https://music.youtube.com/manifest.webmanifest"
manifestId="https://music.youtube.com/?source=pwa">
<a href="https://music.youtube.com/" target="_blank">
Launch YouTube Music
</a>
</install>On activation, the element invokes the install algorithm defined in
Web Install API with the optionally supplied manifest and manifestId.
The backend's algorithms for manifest fetch, validation, consent UI, and error
mapping apply unchanged.
Example consent UI for users to review security-sensitive fields such as the app name, origin, and icon before installation proceeds:
Where navigator.install() uses promise rejections with DOMException names,
the <install> element surfaces outcomes through two mechanisms: pre-click
validation via the InPagePermissionMixin, and post-click results via an
asynchronous, bubbling InstallResultEvent (see
Results, errors, and debuggability below).
The UA can choose to render the element as a "Launch" button and activation would follow established launch handler algorithms. UAs must ensure installation status is not exposed to side-channel attacks. See Privacy for more details.
As a convenience, the UA could hide the element in installed windows. There is also an active proposal for a media feature that answers the question "am I in an installed app window?" that allows developers to easily customize the look and feel of their installed app experience.
The element surfaces errors through two distinct mechanisms, reflecting the difference between pre-activation validation and post-activation installation results.
The element inherits the standard InPagePermissionMixin validation
interface (isValid, invalidReason, onvalidationstatuschange) from the
Permission Element spec. These cover presentation restrictions
common to all capability elements (visibility, styling, occlusion, temporal
cooldowns) and are not install-specific.
A violation of these restrictions prevents the element from being activated,
either temporarily or permanently. Developers can inspect isValid and
invalidReason, and user agents may also surface the problem in developer
tooling.
Install-data problems (a malformed manifest URL or manifestId, a manifest
that fails to fetch or parse, etc.) are not surfaced here. They are reported
after activation as an "invalid_data" installresult (see below).
Note: The mixin also exposes
initialPermissionStatus,permissionStatus,onpromptaction, andonpromptdismisson<install>, but these members are not actionable for this element. The permission status does not determine whether installation can proceed, and install outcomes are reported throughinstallresult.
Outcomes that occur after the user clicks are surfaced by a dedicated event
named installresult. Its event object is an InstallResultEvent, which
carries a result attribute reporting one of three values:
result |
Meaning |
|---|---|
"success" |
The app was installed. |
"aborted" |
The user cancelled, or a browser-side condition prevented the install from completing. No action needed. |
"invalid_data" |
A developer error: the element was given install data it can't act on. See note below. |
Note:
"invalid_data"is a developer error, not a user action. It covers a malformedmanifestURL ormanifestId, a manifest that can't be fetched or parsed, amanifestIdthat doesn't match the UA-computed id, or amanifestIdwith nomanifestto install from. It does not disable the element: the developer can correct the markup and the user can retry. (An earlier design disabled the element and surfaced aninstall_data_invalidinvalidReason; that behavior was removed in favor of theinstallresultevent.)
For same-origin installs, user agents can supplement the coarse "invalid_data"
result with actionable diagnostics in developer tooling, such as whether no
manifest was found, the manifest could not be fetched or parsed, or required
fields were missing or invalid. These diagnostics should not expose cross-origin
manifest details or change the result visible to the page.
enum InstallResult { "success", "aborted", "invalid_data" };
[RuntimeEnabled=InstallElement, Exposed=Window]
interface InstallResultEvent : Event {
constructor(DOMString type, optional InstallResultEventInit eventInitDict = {});
readonly attribute InstallResult result;
};
dictionary InstallResultEventInit : EventInit {
InstallResult result;
};The result is on the event, not the element, because the install flow is asynchronous. A result on the element could be overwritten by a later attempt before the handler runs. The event is enqueued for async dispatch so timing stays consistent across all outcome paths.
Developers can listen with addEventListener:
const el = document.querySelector('install');
el.addEventListener('installresult', (event) => {
switch (event.result) {
case 'success':
// The user accepted the install (the UA should take a reasonable action, such as launching, if the app is already installed).
break;
case 'aborted':
// The user cancelled, or a browser-side condition stopped the install. No action needed.
break;
case 'invalid_data':
// The manifest / manifestId was invalid. Fix the attributes and retry.
break;
}
});The oninstallresult content attribute works too:
<install manifest="https://app.example.com/manifest.webmanifest"
oninstallresult="report(event.result)"></install>As does the matching IDL property (el.oninstallresult = ...).
When the user agent dispatches installresult it sets bubbles = true, so a
page with several <install> elements can subscribe once on a common ancestor
and read event.target to tell them apart.
// One delegated listener on the catalog container handles every <install>
// inside it; event.target identifies which app the result belongs to.
document.getElementById('app-catalog').addEventListener('installresult', (event) => {
console.log(`${event.target.id}: ${event.result}`);
});The initial result set is deliberately coarse and may be refined over time
(e.g. narrowing "aborted" to specifically mean user cancellation). How much a
cross-origin installer should learn about an outcome is tracked in
Open Questions.
See Web Install API alternatives for full analysis.
An alternative design where the element takes a page URL (installurl) instead
of a manifest URL, and loads the page in the background to discover its
<link rel="manifest">.
This has the advantage of simpler developer ergonomics for catalogs (page URLs may be more stable than manifest URLs), but it introduces a variety of security, privacy, and performance concerns.
<a href="manifest_url" rel="install">
The Web Install API proposal considered a different declarative approach. This
gives the user agent less control over the content and presentation but has the
advantage of built-in progressive enhancement. The <install> element approach
was chosen because it gives the user agent full control over the button's
rendering, consistent with the PEPC model.
Names like <pwa> or <webapp> could more broadly describe the range of behavior
(install and launch). <install> was preferred because launching is a
privacy-preserving fallback rather than the primary purpose.
See Web Install API's Open Questions for shared open questions. Below are element-specific.
Rendering the app name, origin, or icon in the element would provide a stronger signal of user intent but introduces performance, UX, security, and accessibility complications (e.g. long app names, icon contrast ratios, button layout changes).
No, this is a known limitation of the element-based approach.
The HTML in Canvas proposal makes this possible, but additional consideration is needed to avoid privacy/security leaks. See #9.
Currently the element can only be activated in top-level browsing contexts for security purposes. Same-origin iframes are unlikely to pose a risk and may be supported in the future.
Currently the element is disabled in all sandboxed contexts. If a use case for installing from a sandbox presents itself, a strict allow-list can be implemented. This decision is open to reevaluation.
The button's rendering is implementation-defined. User agents may combine an action verb with the application's origin, render the app's name if trusted, or use other approaches. It is worth discussing what considerations user agents should pay attention to, but specifying the content too precisely would be unhelpful.
User agents will need to consider how to handle very long words, including appropriate resizing, eliding, and truncation logic (similar to what the installation dialog already implements). User agents should apply the same considerations they use elsewhere for displaying origins and names.
The InstallResultEvent result values and navigator.install() promise rejections
share the same underlying question: how much should the installing origin learn
about the outcome of an install attempt, particularly in the cross-origin case?
This is a shared backend concern. See the Web Install API explainer's privacy section.
Like all permission elements, <install> renders as a button and inherits
standard button accessibility semantics, including keyboard navigation and
focus management. The default tabindex is 0. It should have a "Button" role in
the accessibility tree, and screen readers should announce the element's text
content (controlled by the user agent). The element should also be legible in
Dark Mode, High Contrast, etc, and work with system keyword CSS keywords.
The element observes the lang attribute to select localized text for the button
label (e.g. "Install" in the page's language). App names from manifests may also
be localized based on browser language.
A large portion of the privacy concerns are shared with navigator.install, and can be found in the API's privacy section.
Anything related to the element, but not <install>-specific can be found in
PEPC privacy.
<install>-specific concerns -
- The element's rendering must not reveal whether an app is installed. If
UAs chose to render the "Launch" state, they must consider/mitigate the
following concerns:
- Width differences between "Install" and "Launch" states
- Any detectable state via the DOM, CSS content, etc
- SVG foreignObject considerations
- Tainting the canvas for readback
- Limit or completely restrict install functionality
Similar to privacy, only the element-specific security concerns are listed here. For full details reference the API security section.
- The element inherits PEPC presentation restrictions: visibility checks, contrast ratio requirements, font size bounds, occlusion detection, and temporal cooldowns. These prevent clickjacking and ensure the user can see and understand what they're clicking.
- The element cannot be activated in cross-origin subframes, fenced frames, and all sandboxed contexts.
- W3C TAG Review: PENDING
- Browser Standards Positions:
- Chromium: Supportive/Implementing
- Mozilla: mozilla/standards-positions#1179
- WebKit: WebKit/standards-positions#463
This proposal builds on the Permission Element (PEPC) infrastructure and the Web Install API.
Many thanks for valuable feedback and advice from:
- Marcos Cáceres
- Diego Gonzalez
- Lu Huang
- Alex Russell
- Arthur Sonzogni
- Daniel Murphy
- Mike West



