# Install banners and beforeinstallprompt

> Build an install banner around beforeinstallprompt, preserve browser install UI, and provide a useful path when the event is absent.

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](/compatibility/install-prompt/) 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

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 |

:::caution
MDN's [`prompt()` reference](https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent/prompt) says the method must run in a user-action handler and may be called once for a given event. Reusing the stored object or calling it from a timer does not satisfy that contract. Clear it immediately after the call settles.

Chromium's [`app-banner-event-prompt.html`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/web_tests/app_banner/app-banner-event-prompt.html) expects a call without a user gesture to reject with `The prompt() method must be called with a user gesture`. Use the message for Chromium diagnosis, not as a cross-browser contract.
:::

## Examples

The banner starts hidden in the markup. JavaScript reveals it only after the browser hands the page a valid event object.

### 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.

```html
<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

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.

```html
<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

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.

```html
<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](/compatibility/install-prompt/) supplies the engine-level support split, while the [iOS install API lab note](/lab/behaviour/install-apis-on-ios/) records measured iOS behavior separately.

## Correction (2026-10-31)

This correction aligns the description of an `"accepted"` outcome with the cited sources. MDN's [`prompt()` reference](https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent/prompt) defines `"accepted"` as the user installing the application, and [web.dev's in-app install guidance](https://web.dev/articles/customize-install) says `userChoice` reports whether the user installed through the in-app interface. The original `publishedAt` date remains unchanged.

## See also

- [Manifest Incubations: installation prompt](https://wicg.github.io/manifest-incubations/) (wicg.github.io)
- [How to provide your own in-app install experience](https://web.dev/articles/customize-install) (web.dev)
- [beforeinstallprompt event](/reference/installation/install-prompt/)
- [Install prompt UX](/reference/installation/install-prompt-ux/)
- [iOS Add to Home Screen](/reference/installation/ios-add-to-home-screen/)