Concept in Installation
Install banners and beforeinstallprompt
Published Updated
The beforeinstallprompt event supplies Chromium pages with a one-use object that can open the browser’s install prompt from an application-provided banner or button. A page can defer that prompt, but it should keep its own install control hidden until the event proves that this path is available.
BCD api.BeforeInstallPromptEvent records Chrome support from 44 and Samsung Internet Android support from 5.0; Edge is recorded as "mirror". The compatibility data presents the site targets. BCD records no support in Firefox or Safari. Its api.BeforeInstallPromptEvent.prompt entry records Chrome 44 through 75 as a partial implementation whose promise resolves without a value; the result object is recorded from Chrome 76. The event is non-standard and absent in Firefox and Safari, so an install banner needs an explicit no-event path.
How it works
Section titled “How it works”Listen on window, call preventDefault(), save the event, and reveal the install control. Call prompt() inside the install control’s click handler. MDN states that prompt() requires a user-action handler and can be called once on each BeforeInstallPromptEvent. Clear the saved event after the prompt flow settles. Read userChoice when the code must cover Chrome 44 through 75 because those versions do not return a result object from prompt().
| State | Banner action | Evidence available to the page |
|---|---|---|
| No event has fired | Keep the programmatic install button hidden | The page does not have a prompt object |
beforeinstallprompt fires |
Save the event and reveal the button | The browser determined that the app can use this install flow |
prompt() settles |
Hide the button and discard the event | userChoice supplies "accepted" or "dismissed"; prompt() itself returns that result object from Chrome 76 |
appinstalled fires |
Hide install promotion and clear stored state | The browser reports that the app was installed |
| The event API is absent | Offer browser-specific help without calling prompt() |
BCD records no BeforeInstallPromptEvent in Firefox or Safari |
preventDefault() controls the event’s default install promotion on supporting browsers. Chrome’s mini-infobar update says Chrome 76 began honoring that call to suppress the mini-infobar. The page’s banner does not install the app by itself; it gives the user a control that later calls the browser-owned prompt() method.
MDN defines an "accepted" outcome as the user installing the application. Listen for appinstalled too, so promotion is removed when installation completes through browser UI outside the saved event flow. The MDN install guide also clears the stored event after calling prompt() because that event cannot be reused.
Treat the event object and the banner as one piece of state. Navigation discards the JavaScript object, so a later document waits for its own event. A dismissed prompt also ends that object’s useful life. The MDN event example hides the button and resets the saved reference after prompt() returns; the appinstalled example performs the same cleanup when installation finishes through another browser surface. These transitions prevent a visible control from pointing at a missing or consumed event.
The browser remains responsible for confirmation. The page can choose when to reveal its own promotion and when to call prompt(), while userChoice reports the user’s decision in browser UI across the recorded Chrome support range. Keep application analytics downstream of that result, and do not replace browser confirmation with a page-styled success state. When the outcome is "dismissed", remove the banner for that event and let a future beforeinstallprompt event create fresh state.
BCD records version_added: false for Firefox and Safari. A feature test can select explanatory copy, but it cannot manufacture a browser prompt. Keep the fallback short, and let the browser’s own menu or install surface remain available.
| Goal | Chromium event path | No-event path |
|---|---|---|
| Show an in-page install action | Reveal it after beforeinstallprompt |
Keep it hidden |
| Start browser confirmation | Call the saved event’s prompt() from a click |
Explain where the browser exposes installation |
| Remove stale promotion | Handle appinstalled and clear the event after use |
Recheck installed display mode when the page loads |
| Handle dismissal | Hide the action and wait for another event | No prompt outcome exists to inspect |
Examples
Section titled “Examples”The banner starts hidden in the markup. JavaScript reveals it only after the browser hands the page a valid event object.
Detecting support
Section titled “Detecting support”Test for the event handler property before waiting for beforeinstallprompt. The supported branch keeps the install button hidden until the event fires; the fallback branch reveals browser-install instructions instead. BCD records no BeforeInstallPromptEvent support in Firefox or Safari.
<button data-event-install type="button" hidden>Install</button><p data-event-fallback hidden>Use your browser's install command if it provides one.</p>
<script> var installButton = document.querySelector('[data-event-install]'); var installFallback = document.querySelector('[data-event-fallback]');
if ('onbeforeinstallprompt' in window) { window.addEventListener('beforeinstallprompt', function (event) { event.preventDefault(); installButton.hidden = false; }); } else { installButton.hidden = true; installFallback.hidden = false; }</script>The click handler in the next example supplies the supported branch’s prompt behavior.
Deferring the prompt behind an install banner
Section titled “Deferring the prompt behind an install banner”This complete HTML example preserves the browser event until a click, reads the broadly supported userChoice promise instead of dereferencing the return value from prompt(), and clears the banner when appinstalled fires.
<aside data-install-banner hidden> <p>Install this app for quicker access.</p> <button type="button">Install</button></aside><p><output data-install-status aria-live="polite"></output></p>
<script> (function () { var installEvent = null; var banner = document.querySelector('[data-install-banner]'); var status = document.querySelector('[data-install-status]'); var installButton = banner && banner.querySelector('button'); if (!banner || !status || !installButton) return;
window.addEventListener('beforeinstallprompt', function (event) { event.preventDefault(); installEvent = event; installButton.disabled = false; banner.hidden = false; });
installButton.addEventListener('click', function () { if (!installEvent) return; var pendingEvent = installEvent; installEvent = null; installButton.disabled = true;
pendingEvent.prompt().then(function () { return pendingEvent.userChoice; }).then(function (choice) { status.textContent = 'Install prompt result: ' + choice.outcome; banner.hidden = true; }, function (error) { status.textContent = 'The install prompt could not open: ' + error.name; banner.hidden = true; }); });
window.addEventListener('appinstalled', function () { installEvent = null; banner.hidden = true; status.textContent = 'The app was installed.'; }); }());</script>The button never appears merely because the property exists. It appears when the event fires, which keeps the UI aligned with the browser’s decision for that visit.
Handling browsers without the event
Section titled “Handling browsers without the event”The failure path chooses instructions instead of waiting for an event that BCD does not record in Firefox or Safari. Installed display mode hides the message after the app opens in a standalone window.
<p data-install-help hidden> Use your browser's install command if it provides one.</p><p data-installed-state hidden>This app is already open as an installed app.</p>
<script> var help = document.querySelector('[data-install-help]'); var installedState = document.querySelector('[data-installed-state]'); var installed = window.matchMedia('(display-mode: standalone)').matches;
if (help && installedState) { installedState.hidden = !installed; help.hidden = installed || 'onbeforeinstallprompt' in window; }</script>The fallback does not call prompt() because no BeforeInstallPromptEvent exists on this branch. Adapt its copy to the browser UI you have tested. The install prompt compatibility data supplies the engine-level support split, while the iOS install API lab note records measured iOS behavior separately.
Correction (2026-10-31)
Section titled “Correction (2026-10-31)”This correction aligns the description of an "accepted" outcome with the cited sources. MDN’s prompt() reference defines "accepted" as the user installing the application, and web.dev’s in-app install guidance says userChoice reports whether the user installed through the in-app interface. The original publishedAt date remains unchanged.
See also
Section titled “See also”- Manifest Incubations: installation prompt (wicg.github.io)
- How to provide your own in-app install experience (web.dev)
- beforeinstallprompt event
- Install prompt UX
- iOS Add to Home Screen
Specifications
| Specification | Status |
|---|---|
| Install Prompt (beforeinstallprompt) | WICG draft |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Desktop)# | Yes | 44 | high | source | — |
| Chrome (Android)# | Yes | 44 | high | source | 1 |
| Edge (Desktop)# | Yes | 79 | high | source | 2 |
| Firefox (Desktop)# | No | — | high | source | 3 |
| Firefox (Android)# | No | — | high | source | 45 |
| Safari (macOS)# | No | — | high | source | 6 |
| Safari (iOS)# | No | — | high | source | 78 |
| Samsung Internet# | Yes | 5.0 | high | source | — |
| WebView (Android)# | Yes | 44 | high | source | 9 |
- Derived by browser-compat-data mirroring from Chrome.
- Derived by browser-compat-data mirroring from Chrome.
- No Firefox support is recorded in browser-compat-data.
- No Firefox for Android support is recorded in browser-compat-data.
- Derived by browser-compat-data mirroring from Firefox.
- No Safari support is recorded in browser-compat-data.
- No Safari on iOS support is recorded in browser-compat-data.
- Derived by browser-compat-data mirroring from Safari.
- Derived by browser-compat-data mirroring from Chrome Android.
Try it
Run this capability in the OpenPWA demo app: /demo/#install