The beforeinstallprompt event fires when the browser determines the PWA is installable. It allows you to intercept the default install prompt and show a custom one.
let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (event) => {
// Prevent the mini-infobar from appearing
event.preventDefault();
// Store for later use
deferredPrompt = event;
// Show a custom install button
showInstallButton();
});
// Handle the install button click
async function handleInstallClick() {
if (!deferredPrompt) return;
// Show the browser's install prompt
deferredPrompt.prompt();
// Wait for user response
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('User installed the PWA');
trackInstallEvent('accepted');
} else {
console.log('User dismissed the install prompt');
trackInstallEvent('dismissed');
}
// Clean up
deferredPrompt = null;
hideInstallButton();
}
Create a branded install prompt that matches your app's design:
<!-- Custom install prompt HTML -->
<div id="install-prompt" class="install-banner" hidden>
<div class="install-content">
<img src="/icons/icon-192.png" alt="App icon">
<div>
<h3>Install My PWA</h3>
<p>Add to your home screen for the best experience</p>
</div>
<button id="install-btn">Install</button>
<button id="dismiss-btn">Not now</button>
</div>
</div>
/* Custom install prompt styles */
.install-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 16px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
z-index: 1000;
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.install-content {
display: flex;
align-items: center;
gap: 12px;
}
.install-content img {
width: 48px;
height: 48px;
border-radius: 12px;
}
#install-btn {
background: #4f46e5;
color: white;
border: none;
padding: 8px 20px;
border-radius: 20px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
Best practices for deferred install prompts:
function trackInstallEvent(outcome) {
// Send to analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'install_prompt', {
event_category: 'PWA',
event_label: outcome,
value: 1
});
}
// Store dismissal in localStorage to avoid re-prompting
if (outcome === 'dismissed') {
localStorage.setItem('install_prompt_dismissed', 'true');
localStorage.setItem('install_prompt_dismissed_at', Date.now().toString());
}
}
// Check if we should show the prompt
function shouldShowInstallPrompt() {
const dismissed = localStorage.getItem('install_prompt_dismissed');
if (!dismissed) return true;
// Re-prompt after 30 days
const dismissedAt = parseInt(localStorage.getItem('install_prompt_dismissed_at'));
const daysSinceDismissed = (Date.now() - dismissedAt) / (1000 * 60 * 60 * 24);
return daysSinceDismissed > 30;
}