← Back to Tutorials Chapter 13

Install Prompt

beforeinstallprompt Event

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();
}

Custom Install Prompts

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;
}

Deferred Prompts

Best practices for deferred install prompts:

Analytics and Tracking

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;
}
Exercise: Implement a custom install prompt for a PWA. Show it after the user has completed 3 interactions (e.g., clicked 3 buttons). Store the user's decision in localStorage and don't re-prompt for 7 days if they dismissed it.