Skip to content

beforeinstallprompt and custom install prompts

Published Updated

In one line: Chromium browsers fire beforeinstallprompt when your PWA meets the installability criteria; you preventDefault() to suppress the mini-infobar, stash the event, and later call prompt() from a user gesture to show the install dialog. Safari fires no such event — iOS users install via a manual Add to Home Screen.

Flow diagram of the install prompt: once a site meets the installability criteria a Chromium browser fires beforeinstallprompt; the page calls preventDefault(), keeps the event and shows its own install button. On a user gesture it calls prompt(), the browser shows its install dialog and userChoice resolves to accepted, which fires appinstalled, or dismissed. Safari and Firefox never fire the event and install from a browser menu instead.
From installability criteria to appinstalled: the beforeinstallprompt flow in Chromium browsers.
  • beforeinstallprompt fires once the page is installable. Call preventDefault() to stop the browser’s default mini-infobar, then save the event reference.
  • Trigger from a gesture. Browsers only honor prompt() inside a user-activation context (a click/tap handler). Calling it on page load is ignored.
  • Read the outcome. await deferredPrompt.userChoice resolves to { outcome: 'accepted' | 'dismissed' }. The saved event can be used only once.
  • appinstalled fires after a successful install (from your prompt or the browser’s own UI). Use it to hide your install button and log the conversion.
let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
installButton.hidden = false;
});
installButton.addEventListener('click', async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
deferredPrompt = null;
installButton.hidden = true;
});
window.addEventListener('appinstalled', () => {
installButton.hidden = true;
});

For beforeinstallprompt to fire, the page must serve over HTTPS (localhost excepted) and link a web app manifest that declares at minimum a name (or short_name), a start_url, a display value other than browser, and icons including a 192px and a 512px PNG. A registered service worker is no longer a hard requirement in Chrome: per the Chrome team’s “Changes to the installability criteria”, the requirement for a service worker with a fetch() handler was removed for installation from the browser menu in Chrome 108 on Android and Chrome 112 on desktop, with Chrome supplying a default offline page for sites that do not implement one. The same post notes that the algorithm behind beforeinstallprompt still looked for a fetch() handler at the time, so a site without a service worker may be installable from the menu yet never receive the event. Miss a manifest requirement and no event fires.

  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Android)Yes68mediumsource—
Chrome (Desktop)Yes73mediumsource—
Edge (Desktop)Yes79mediumsource—
Safari (iOS)No—mediumsource1
Safari (macOS)No—mediumsource2
Firefox (Desktop)No—mediumsource3
Samsung InternetYes9.0mediumsource—
  1. No beforeinstallprompt; install is manual via the Share sheet → Add to Home Screen.
  2. Install is via the File → Add to Dock menu, not the web event.
  3. No manifest-based desktop install path.

Source data: /compatibility/install-prompt.json · Global usage: 72 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-06-24 · Confidence: medium (computed from sources)

  • No beforeinstallprompt. Safari (iOS and macOS) never fires the event and exposes no programmatic install API, so a custom in-page button cannot trigger installation.
  • Manual Add to Home Screen. iOS users tap Share, then “Add to Home Screen”. You can only guide them with instructions and a visual hint pointing at the Share icon.
  • Detect standalone mode with navigator.standalone (iOS) or window.matchMedia('(display-mode: standalone)') to avoid prompting already-installed users.
Situation Recommended action Rationale
beforeinstallprompt has fired and saved Show a discreet install affordance, not a blocking modal The browser confirmed installability; respect user flow
User just landed, no engagement yet Wait — don’t prompt on first paint Cold prompts are dismissed and may burn your one chance
User completed a meaningful action Surface the install button now Intent is high; conversion is far more likely
Already installed (standalone) Hide all install UI Prompting an installed app is confusing noise
iOS / Safari Show manual Add to Home Screen instructions No programmatic prompt exists; instruct instead

The companion demo at /demo/#install captures beforeinstallprompt, reports the current display-mode, calls getInstalledRelatedApps() where it exists, and triggers the deferred prompt from a button. On iOS and Safari, where the event never fires, it shows the Add to Home Screen steps instead.

  • Verify HTTPS, a linked manifest, 192px + 512px icons, and a registered service worker.
  • Call e.preventDefault() in beforeinstallprompt and stash the event.
  • Only call prompt() from inside a click/tap handler (user gesture).
  • Treat the deferred event as single-use; clear it after userChoice resolves.
  • Listen for appinstalled to hide your button and record the conversion.
  • Detect standalone mode and suppress install UI for already-installed users.
  • Provide an iOS/Safari fallback with explicit Add to Home Screen guidance.