Install Prompt — Triggering PWA Installation to Home Screen
In this tutorial, you will learn about Install Prompt. We cover key concepts, practical examples, and best practices to help you master this topic.
The install prompt (beforeinstallprompt event) lets users add your PWA to their home screen, increasing engagement and creating a native app-like presence on the device.
What You'll Learn
By the end of this tutorial, you will understand how the beforeinstallprompt event works, how to show a custom install button, how to defer the prompt, and how to handle the user's install decision.
Why It Matters
Installation is what transforms a website into a PWA in the user's mind. Installed PWAs appear on the home screen, open in standalone mode, and have higher engagement. Implementing a thoughtful install prompt can increase installation rates by 5-10x compared to the default browser prompt.
Real-World Use
A music streaming PWA shows a custom install banner after the user listens to 5 songs. The banner says "Add to your home screen for quick access and offline playback." The custom prompt converts 40% of users compared to 8% for the default browser prompt.
Install Prompt Flow
Install Prompt Flow
User meets PWA install criteria
(service worker, manifest, HTTPS, engagement)
↓
Browser fires 'beforeinstallprompt' event
↓
╔══════════════════════════════════════╗
║ Call event.preventDefault() to ║
║ defer the prompt for our own UI ║
╚══════════════════════════════════════╝
↓
Show a custom install button or banner
↓
User clicks custom install button
↓
Call event.prompt() to show the browser dialog
↓
╔═══════════════════════════╗
║ User installs or cancels ║
╚═══════════════════════════╝
↓
┌──────────┐ ┌──────────────┐
│ Installed │ │ Canceled │
└─────┬─────┘ └──────┬───────┘
↓ ↓
Hide install button Can prompt again
permanently (up to 3 times)
Think of the install prompt like a store asking if you want to become a member. The browser offers the prompt (like a cashier asking), but you can defer it to your own timing (like asking after the customer has shopped a few times). A well-timed ask gets more yeses than an immediate ask.
Capturing the Install Prompt
let deferredPrompt = null;
let installButton = null;
window.addEventListener('beforeinstallprompt', event => {
// Prevent the default mini-infobar from appearing
event.preventDefault();
// Store the event for later use
deferredPrompt = event;
// Show our custom install button
showInstallButton();
console.log('Install prompt captured');
});
function showInstallButton() {
installButton = document.getElementById('install-button');
if (installButton) {
installButton.style.display = 'block';
installButton.addEventListener('click', showInstallPrompt);
}
}
Showing the Install Prompt
async function showInstallPrompt() {
if (!deferredPrompt) {
console.log('No install prompt available');
return;
}
// Show the browser install prompt
deferredPrompt.prompt();
// Wait for the user's response
const choiceResult = await deferredPrompt.userChoice;
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the install prompt');
trackInstallAccepted();
} else {
console.log('User dismissed the install prompt');
trackInstallDismissed();
}
// Reset the deferred prompt
deferredPrompt = null;
hideInstallButton();
}
function hideInstallButton() {
if (installButton) {
installButton.style.display = 'none';
}
}
Custom Install UI
function showInstallBanner() {
const banner = document.getElementById('install-banner');
if (!banner) return;
// Check if app is already installed
if (window.matchMedia('(display-mode: standalone)').matches) {
banner.style.display = 'none';
return;
}
banner.style.display = 'block';
banner.querySelector('.install-button')
.addEventListener('click', showInstallPrompt);
banner.querySelector('.dismiss-button')
.addEventListener('click', () => {
banner.style.display = 'none';
// Show again after 7 days
localStorage.setItem('install-banner-dismissed', Date.now());
});
}
// Check if we should show the banner
function shouldShowInstallBanner() {
const dismissed = localStorage.getItem('install-banner-dismissed');
if (dismissed) {
const daysSinceDismiss = (Date.now() - parseInt(dismissed)) / (1000 * 60 * 60 * 24);
if (daysSinceDismiss < 7) return false;
}
return true;
}
Tracking Install State
// Check if app is running in standalone mode
function isRunningStandalone() {
return window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone ||
document.referrer.includes('android-app://');
}
console.log('Running standalone:', isRunningStandalone());
// Listen for the appinstalled event
window.addEventListener('appinstalled', event => {
console.log('PWA was installed');
trackInstallCompleted();
hideInstallButton();
});
// Track display mode changes
window.matchMedia('(display-mode: standalone)').addEventListener('change', event => {
if (event.matches) {
console.log('App moved to standalone mode');
sendAnalytics('pwa_installed');
}
});
Install Prompt Best Practices
// Defer install prompt until user has shown engagement
let pageVisits = parseInt(localStorage.getItem('page-visits') || '0');
function trackPageVisit() {
pageVisits++;
localStorage.setItem('page-visits', pageVisits.toString());
}
function shouldPromptInstall() {
const conditions = {
supported: !!deferredPrompt,
notInstalled: !isRunningStandalone(),
sufficientVisits: pageVisits >= 3,
recentDismiss: checkDismissCooldown(),
engaged: checkUserEngagement()
};
console.log('Install conditions:', conditions);
return conditions.supported &&
conditions.notInstalled &&
conditions.sufficientVisits &&
!conditions.recentDismiss &&
conditions.engaged;
}
function checkUserEngagement() {
const timeOnSite = parseInt(sessionStorage.getItem('time-on-site') || '0');
return timeOnSite > 60000; // More than 1 minute
}
function checkDismissCooldown() {
const lastDismiss = localStorage.getItem('install-dismissed');
if (!lastDismiss) return false;
return (Date.now() - parseInt(lastDismiss)) < 7 * 24 * 60 * 60 * 1000;
}
// Conditionally show after user engagement
window.addEventListener('beforeinstallprompt', event => {
event.preventDefault();
deferredPrompt = event;
if (shouldPromptInstall()) {
setTimeout(showInstallBanner, 2000);
}
});
Common Mistakes
- Showing the install prompt immediately. Users who just arrived do not install. Wait for sufficient engagement (visits, time, actions).
- Not checking if already installed. If the user already installed, do not show the prompt again. Check matchMedia(display-mode: standalone).
- Calling prompt() without user gesture. The prompt() must be called in response to a user action (click or tap). Calling it programmatically fails.
- Not deferring with preventDefault(). Without preventDefault(), the browser shows its own mini-infobar which is less effective than a custom prompt.
- Showing the prompt after it was dismissed too many times. Track dismissals and stop showing after 3 attempts to avoid annoying users.
Practice Questions
- What is the beforeinstallprompt event and why should you capture it?
- Why must you call event.preventDefault() on the beforeinstallprompt event?
- When should you show the custom install button relative to user engagement?
- How do you detect if the PWA is already installed?
- What happens after the user accepts the install prompt?
Challenge: Implement a complete install prompt flow: capture beforeinstallprompt, defer it, show a custom banner after 3 page visits or 60 seconds of engagement, call prompt() on the deferred event on button click, handle installation acceptance/dismissal, and stop showing after 3 dismissals.
FAQ
Mini Project
Create a custom install prompt system: capture beforeinstallprompt, show a branded install banner after the user visits 3 different pages or spends 2 minutes on site, include install and dismiss buttons, track install state, hide permanently after install, and stop showing after 3 dismissals. Test by clearing site data and going through the flow.
What's Next
Install prompt is handled. Now learn the App Shell architecture — loading a minimal application shell instantly and populating content dynamically.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro