Build my web app

Installing a web app on Android: criteria, the prompt, WebAPKs and the richer install dialog

Exactly when Chrome offers to install, how to trigger the prompt from your own button, what a WebAPK is and why it matters, and how to get the store-like install dialog.

Android is where progressive web apps are first-class. Chrome installs them as real apps, Google Play will list them, and the browser actively offers installation. This guide covers the mechanics so you can control them rather than hope.

When Chrome considers a site installable

The criteria have relaxed over the years; as of current Chrome on Android:

Chrome also applies an engagement heuristic before showing its own prompt: the user must have interacted with the site for a short period. A first-time visitor who bounces in two seconds won't see it. Your own install button (below) is not subject to this delay once the event has fired.

What "installed" produces: the WebAPK

When a user installs from Chrome on a device with Google Play services, Chrome asks a Google server to mint a tiny signed Android package — a WebAPK — containing your manifest data and icon, and installs it. That is why an installed PWA on Android appears in Settings → Apps, can be uninstalled like any app, gets its own task-switcher card, and handles links to your scope directly. Chrome checks the manifest for changes about once a day and re-mints the WebAPK if the name or icon changed. On devices without Play services (and in some other browsers) installation falls back to a home-screen shortcut with the same standalone behaviour.

Controlling the prompt: beforeinstallprompt

Chrome fires this event on the page when the site becomes installable. Catch it, stash it, and show your own button:

let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();               // stop the default mini prompt
  deferredPrompt = e;
  document.getElementById('installBtn').hidden = false;
});

document.getElementById('installBtn').addEventListener('click', async () => {
  if (!deferredPrompt) return;
  deferredPrompt.prompt();
  const { outcome } = await deferredPrompt.userChoice;   // 'accepted' | 'dismissed'
  deferredPrompt = null;
  document.getElementById('installBtn').hidden = true;
});

window.addEventListener('appinstalled', () => {
  // analytics: installation completed
});

Show the button where it makes sense — on the install page, after a successful order, at the end of an article — not on first paint of the homepage. Users who dismiss the prompt won't be re-prompted by Chrome for a while; your button remains available.

The richer install dialog

Since Chrome 108, if your manifest has a description and at least one screenshots entry with form_factor: "narrow", the install prompt becomes a bottom sheet with a screenshot carousel and your description — much closer to a store listing. Sites that add this generally see more completed installs because the prompt finally explains what is being installed. The screenshot tool exports compliant images; the manifest guide has the field details.

Detecting the installed state

const installed = window.matchMedia('(display-mode: standalone)').matches
               || navigator.standalone === true;   // iOS

Use it to hide the install call-to-action inside the app and to tag analytics.

Other Android browsers

Debugging installability

On desktop Chrome, open your site, DevTools → ApplicationManifest: the Installability section lists every failing criterion in plain words ("Manifest does not contain a suitable icon"). On the phone, chrome://inspect from a connected desktop shows the same. Nine times out of ten the cause is an icon size, a wrong start_url, or the manifest failing to load because of a path or CORS error.

From installed PWA to Google Play

Everything above is about installing from the browser. If you also want a Play Store listing — for discovery, or because your audience expects one — the same manifest and service worker power a Trusted Web Activity. Get the browser install right first; the Play listing inherits it.