LEARNING OBJECTIVES ⌵
- Understand the browser heuristics and criteria required to trigger the PWA installability flow.
- Intercept the
beforeinstallpromptevent, cancel the default browser mini-infobar, and save the event object in memory. - Trigger the native OS installation dialog programmatically via
deferredPrompt.prompt()and capture the user's decision usingdeferredPrompt.userChoice. - Track successful application installations using the
appinstalledlifecycle event and CSS display-mode detection.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine browsing an online store. The moment you land on the homepage, a sales representative blocks your view with a clipboard, shouting: "Sign this lifetime membership contract right now!" You would immediately close the tab in frustration. However, if the representative waits until you have customized three items, added them to your cart, and expressed satisfaction before gently saying, "Would you like to install our app on your home screen for instant tracking and 1-tap checkout?", you are far more likely to accept.
Browsers historically showed an intrusive, automated banner (the "mini-infobar") the instant a site met basic PWA criteria.
Modern PWA architecture empowers frontend engineers to intercept that aggressive default banner (e.preventDefault()), stash the installation capability in memory, and present a beautifully branded, contextual in-app install button at the exact psychological moment of peak user delight (e.g., after completing a purchase, finishing a lesson, or creating a new document).
Technical Deep Dive & Specifications
Chromium PWA Installability Criteria
To trigger the beforeinstallprompt event, the application must satisfy strict browser heuristics:
+---------------------------------------------------------------------------------------+
| PWA INSTALLABILITY CRITERIA CHECKLIST |
+---------------------------------------------------------------------------------------+
| 1. Web App Manifest linked via <link rel="manifest"> |
| 2. Manifest includes: name / short_name, id, start_url, display (standalone/fullscreen)|
| 3. Manifest contains valid icons: at least 192x192 PNG and 512x512 PNG |
| 4. Manifest includes a maskable icon (purpose: "maskable" or "any maskable") |
| 5. Served over a Secure Context (HTTPS or http://localhost) |
| 6. Registered Service Worker with an active fetch event listener |
| 7. User Engagement Heuristic (User has spent >30 seconds on the page or interacted) |
+---------------------------------------------------------------------------------------+
The beforeinstallprompt Lifecycle Flow
BROWSER CLIENT DOM (UI) USER
| | |
| 1. Checks PWA heuristics | |
|====================================>| |
| 2. Fires 'beforeinstallprompt' | |
|------------------------------------>| |
| | [ Calls e.preventDefault() ] |
| | [ Stores e as deferredPrompt]|
| | [ Shows Custom Install UI ] |
| | |
| | 3. User clicks "Install App" |
| |<=============================|
| | |
| 4. deferredPrompt.prompt() | |
|<------------------------------------| |
| | |
| 5. Shows Native OS Install Dialog | |
|===================================================================>|
| | |
| 6. User clicks [Install] or [Cancel]| |
|<===================================================================|
| | |
| 7. Resolves deferredPrompt.userChoice |
| { outcome: 'accepted'|'dismissed'}| |
|------------------------------------>| |
| | |
| 8. Fires 'appinstalled' event | |
|------------------------------------>| [ Hides Install Button ] |
| | [ Sends Telemetry Metric ] |
Key Properties & Event Methods
| API / Property | Type | Description |
|---|---|---|
event.preventDefault() |
Method | Cancels the default browser mini-infobar prompt. |
deferredPrompt.prompt() |
Method (Async) | Triggers the browser's native installation confirmation dialog. |
deferredPrompt.userChoice |
Promise | Resolves to an object: { outcome: 'accepted' | 'dismissed', platform: string }. |
window.addEventListener('appinstalled') |
Event | Dispatched once the OS finishes placing the app icon on the home screen or app launcher. |
💻 Interactive Code Playground
Starter Code: Production Install Prompt Controller
Below is a complete, modular PWA installation manager with contextual UI presentation, outcome handling, and analytics logging.
Line-by-Line Code Breakdown
- Line 87 (
e.preventDefault()): Crucial method call that suppresses the browser's default mini-infobar, allowing you to orchestrate your own custom UI. - Line 90 (
deferredPrompt = e): Stores the event reference in memory for later user-initiated execution. - Line 108 (
await deferredPrompt.prompt()): Must be invoked from a user-initiated gesture (such as clicking the Install button); displays the operating system's native installation confirmation modal. - Line 111 (
const { outcome } = await deferredPrompt.userChoice): Resolves when the user either clicks "Install" (outcome === 'accepted') or "Cancel" (outcome === 'dismissed'). - Line 126 (
window.addEventListener('appinstalled')): Dispatched by the browser immediately after the OS registers the PWA icon.
Expected Browser Render Output
PWA Installation Flow
Demonstrating deferred prompts and install telemetry.
+-------------------------------------------------------------------+
| [🚀] Orbit Productivity [ Later ] |
| Install on your desktop or homescreen [ Install ] |
+-------------------------------------------------------------------+
Install Lifecycle Event Log
[02:45:10] Captured "beforeinstallprompt" event. Default banner prevented.
[02:45:10] Custom in-app install banner displayed to user.
(User clicks Install -> Native OS Dialog appears -> User clicks Confirm):
[02:45:18] Triggering deferredPrompt.prompt()...
[02:45:21] User response outcome: "accepted" on platform: "web"
[02:45:22] 🎉 Application successfully installed to the operating system!🏋️ Hands-On Exercise
🎯 The Challenge: Build a Header-Integrated PWA Install Trigger
Instructions:
- Create a navigation header with an "Install App" button that remains hidden by default (
display: none). - Listen for the
beforeinstallpromptevent, prevent default behavior, and reveal the header button. - When the user clicks the button, call
prompt(), evaluateuserChoice, and log whether the user accepted or rejected the installation. - Hide the button permanently if the user installs the app or if
appinstalledfires.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Calling
prompt()Outside a User Gesture: Attempting to calldeferredPrompt.prompt()inside asetTimeout()or immediately inside thebeforeinstallpromptlistener will throw aDOMException: The prompt() method must be called with a user gesture. - Reusing the Same
deferredPromptObject Twice: Thebeforeinstallpromptevent is single-use. Onceprompt()is called, the event object is consumed. If the user dismisses the dialog, you must wait for the browser to dispatch a newbeforeinstallpromptevent before callingprompt()again. - Assuming
beforeinstallpromptFires on iOS Safari: Apple WebKit on iOS does NOT support thebeforeinstallpromptevent. For iOS users, you must display instructional UI explaining how to tap the Safari "Share" button followed by "Add to Home Screen".
💡 Pro Tips
- Detect Installed State on App Launch: Always check
window.matchMedia('(display-mode: standalone)').matcheson initialization. If true, the user is already inside the installed application, so you should completely disable all install promotional logic. - Contextual In-App Triggers Over Top Banners: Conversion rates increase significantly when install buttons are placed contextually (e.g., "Install offline music player" next to a download button) rather than using generic global banners across the top of the viewport.
📌 Key Takeaways
- The browser fires
beforeinstallpromptwhen the site meets all PWA installability criteria. - Call
e.preventDefault()insidebeforeinstallpromptto suppress the browser's default mini-infobar and retain the event in memory. - Call
deferredPrompt.prompt()inside a user click handler to show the native OS installation dialog. - Inspect
deferredPrompt.userChoiceto capture whether the useracceptedordismissedthe prompt. - The
appinstalledevent fires when the operating system successfully registers the PWA icon. - --