The install prompt: beforeinstallprompt and customizing install
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.
How the event flow works
Section titled “How the event flow works”beforeinstallpromptfires once the page is installable. CallpreventDefault()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.userChoiceresolves to{ outcome: 'accepted' | 'dismissed' }. The saved event can be used only once. appinstalledfires 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;});Installability criteria
Section titled “Installability criteria”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. Most Chromium browsers also require a registered
service worker (a fetch handler is recommended, though pure-offline behavior is no
longer strictly enforced everywhere). Miss any requirement and no event fires.
Browser & ecosystem support
Section titled “Browser & ecosystem support”| Browser / Platform | Support | Since | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | ✅ yes | 68 | high | ref | — |
| Chrome (Desktop) | ✅ yes | 73 | high | ref | — |
| Edge (Desktop) | ✅ yes | 79 | high | ref | — |
| Safari (iOS) | ❌ no | — | high | ref | No beforeinstallprompt; install is manual via the Share sheet → Add to Home Screen. |
| Safari (macOS) | ❌ no | — | high | ref | Install is via the File → Add to Dock menu, not the web event. |
| Firefox (Desktop) | ❌ no | — | high | ref | No manifest-based desktop install path. |
| Samsung Internet | ✅ yes | 9.0 | medium | ref | — |
How iOS and Safari differ
Section titled “How iOS and Safari differ”- 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) orwindow.matchMedia('(display-mode: standalone)')to avoid prompting already-installed users.
When to surface a custom install button
Section titled “When to surface a custom install button”| 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 |
Practical checklist
Section titled “Practical checklist”- Verify HTTPS, a linked manifest, 192px + 512px icons, and a registered service worker.
- Call
e.preventDefault()inbeforeinstallpromptand stash the event. - Only call
prompt()from inside a click/tap handler (user gesture). - Treat the deferred event as single-use; clear it after
userChoiceresolves. - Listen for
appinstalledto 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.