Customize PWA Install Flow with beforeinstallprompt

The beforeinstallprompt event gives web development teams full control over how, when, and where a Progressive Web App (PWA) prompts users for installation. By intercepting this browser-level event, developers can suppress default, generic installation banners and replace them with custom, contextual user interfaces. This article explains how the event functions, how to capture and defer it, and how teams leverage it to build higher-converting, user-friendly installation journeys.

Intercepting the Default Browser Prompt

By default, Chromium-based browsers trigger an automatic mini-infobar or ambient badge when a PWA meets the core installability criteria (such as having a valid web app manifest, a registered service worker, and serving over HTTPS). While functional, this default prompt often appears prematurely—frequently on a user's first visit before they have experienced the app's core value.

The beforeinstallprompt event fires right before the browser shows this default prompt. Developers can listen for this event and call e.preventDefault(), which prevents the browser from displaying its standard installation UI.

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent the default mini-infobar from appearing
  e.preventDefault();
  // Stash the event so it can be triggered later
  deferredPrompt = e;
  // Update UI notify the user they can install the PWA
  showInstallPromotion();
});

Storing and Deferring the Event

Calling preventDefault() does not cancel the installation capability; it simply puts the process on hold. By saving the event object to a global or scoped variable (often named deferredPrompt), development teams can retain the ability to launch the native installation dialog at an optimal moment later in the user's journey.

Creating Contextual and Branded UI

Once the event is captured, teams can reveal custom UI components that match the application's design system. Common implementation patterns include:

  • Navigation Bar Buttons: A persistent "Install App" button in the header or sidebar that only becomes visible once the event fires.
  • Contextual Banners: An inline banner explaining the specific benefits of installing, such as offline access, faster load times, or push notifications.
  • Milestone Triggers: Prompting the user after they complete a meaningful action, such as bookmarking an item, finishing a checkout, or completing a level in a game.

Triggering the Native Install Dialog

When the user interacts with the custom installation UI, the stored event can be invoked using its .prompt() method. This re-engages the browser's native flow, presenting the official confirmation dialog to the user.

installButton.addEventListener('click', async () => {
  if (!deferredPrompt) {
    return;
  }
  // Show the browser install prompt
  deferredPrompt.prompt();
  
  // Wait for the user to respond to the prompt
  const { outcome } = await deferredPrompt.userChoice;
  
  // Log the outcome for analytics
  console.log(`User response to install prompt: ${outcome}`);
  
  // Clear the deferredPrompt variable since it can only be used once
  deferredPrompt = null;
  hideInstallPromotion();
});

Analyzing User Response with userChoice

The beforeinstallprompt event provides a userChoice property, which returns a promise that resolves with an object containing the user's decision (accepted or dismissed).

This mechanism allows development teams to:

  • Track conversion metrics and identify which UI triggers generate the highest install rates.
  • Gracefully update the UI if the user declines (for instance, hiding the custom button or waiting a set number of days before showing another promotion).
  • Trigger onboarding flows or confirmation messages when outcome === 'accepted'.

Handling Post-Installation Clean-Up

In addition to userChoice, teams typically pair beforeinstallprompt with the appinstalled event. Once an installation completes, the browser fires appinstalled, signaling that the application should remove any remaining install banners and adjust the interface to reflect that the app is now installed locally on the user's device.