JavaScript PWA Install Table
| Piece | What it does | Field note |
|---|---|---|
beforeinstallprompt event | The install moment | Fires when installable - capture it, DO NOT show a banner instantly |
e.preventDefault() | Own the prompt | Stops the mini-infobar - your button triggers e.prompt() later |
userChoice.outcome | accepted / dismissed | Report it - and DISMISSED MEANS STOP ASKING for a while |
getInstalledRelatedApps | Already installed? | Detect the native app - do not offer installing the web twin |
Requires manifest+SW+HTTPS | The eligibility | Manifest with icons+name, service worker, secure context |
appinstalled event | Post-install | Hide the button, maybe show a welcome - installed is a state |
iOS: no prompt event | The platform gap | iOS = share-sheet instructions - detect platform, show how-to |
Never on load | The ask discipline | Ask after value demonstrated - the notifications law again |
The install flow done right: capture beforeinstallprompt and preventDefault - suppressing the browser's mini-infobar - then fire e.prompt() from YOUR button after the user has seen the app's value. userChoice reports accepted or dismissed.
Bottom line: DISMISSED MEANS STOP - re-asking on every load is the fastest route to users ignoring installs or leaving. Eligibility is manifest (icons, name, start_url) + service worker + HTTPS, iOS has no prompt event (share-sheet instructions instead, platform-detected), and appinstalled hides your button - installed is a state, not a milestone.
The honest part: install prompts are the loudest ask a site can make, and they compete with notifications for the same trust budget. The discipline that works: one well-timed ask, ever; a dismissible state that lasts weeks; and an app good enough that users go looking for the install button themselves.
How to use
- Capture and hold: window.addEventListener('beforeinstallprompt', e => { e.preventDefault(); deferredPrompt = e; showOwnButton(); }) - the browser's banner never appears.
- Fire from your button: deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice - accepted hides the button; dismissed starts the cool-down.
- Handle the iOS gap: detect iOS Safari and show a one-time instruction ('Share, then Add to Home Screen') - there is no prompt event to capture there.
Frequently asked questions
Why capture beforeinstallprompt instead of letting the browser show it?
Timing and placement. The default mini-infobar appears as soon as eligibility is met - often seconds after first visit, before the user has any reason to install. Capturing it (preventDefault) moves the ask to YOUR moment: after the user completed a task, hit a usage threshold, or clicked a persistent 'Install app' button. The data across the industry is consistent: install prompts shown contextually after value convert far better than banners shown on arrival - and they protect the trust budget that notifications will want later. Owning the prompt is owning the ask.
What does 'dismissed means stop' mean in practice?
The user answered the question. userChoice.outcome === 'dismissed' is not a failed conversion to retry next load - it is a decision, and re-prompting every session trains users to ignore or resent the site (and some browsers eventually stop firing the event for nagging sites). The working discipline: store the dismissal with a timestamp (localStorage), cool down for weeks or forever, and never nag within a session. If the install matters, make the button permanently available but quiet - users who want it will click a visible 'Install' button; nobody wants to be asked what they already declined.
What makes a PWA eligible for the install prompt at all?
Three pillars, all verifiable: a web app manifest with name, icons (192px and 512px minimum), start_url and display mode; a registered service worker with a fetch handler (the offline-capability signal); and HTTPS. Miss any and beforeinstallprompt never fires - the silent failure that confuses teams ('the event just never happens'). The debugging order: devtools Application panel checks the manifest errors, confirms the service worker is activated, and verifies the HTTPS origin. iOS skips this whole machinery - its 'Add to Home Screen' works from the share sheet regardless, without install prompts at all.
What should change after appinstalled fires?
The install UI retires and the experience adapts. Hide the install button (installed is a state - re-celebrating it reads as a bug), and consider a one-time welcome-for-installed flow: the app now launches standalone (display: standalone in the manifest), possibly with more screen real estate and no browser UI. Use display-mode: standalone in CSS or matchMedia to adjust navigation (hide the 'you are in a browser' hints). The one thing NOT to do: prompt again or show 'install our app' messages to a user running the installed app - detecting the standalone display mode and suppressing install UI is the polish that separates maintained PWAs from set-and-forget ones.