Skip to content

Installation · Concept

Installability criteria

Published

Installability criteria are the conditions a browser checks before it offers an install surface (an address-bar icon, a menu item, or the beforeinstallprompt event) for a page. Chromium browsers (Chrome, Edge, Samsung Internet) gate on a secure origin plus a manifest with a name, a start URL, a non-browser display mode, and icons; Safari on iOS 26 and Firefox 143 on Windows have no manifest requirement at all and install any page from their own menus (BCD html.manifest, Chrome “Changes to the installability criteria”, WebKit Safari 26.0 release post, Firefox 143 release notes).

Chromium evaluates the page each time it loads and records the result in DevTools under Application › Manifest › “Installability”. The checks, in the order Chrome’s install-criteria article lists them:

  • The page is served from a secure context: https:, or http://localhost and http://127.0.0.1 during development.
  • The document links a manifest (<link rel="manifest">) that parses and contains name or short_name, start_url, a display of standalone, fullscreen, or minimal-ui (or a display_override that resolves to one), and icons including a 192 px and a 512 px entry; for the install dialog Chrome requires at least one icon of 144 px or larger in PNG, SVG, or WebP with sizes set and purpose including any.
  • start_url is within scope and returns a document (a redirect to another origin fails the check).
  • The app is not already installed in this browser profile.

A registered service worker with a fetch handler was part of the list until Chrome 108 on Android and Chrome 112 on desktop removed it for installation from the browser menu; Chrome supplies a default offline page for installed apps without one. The same Chrome post notes that the heuristic behind beforeinstallprompt still looked for a fetch handler, so a page can pass the menu check and never receive the event. Chrome additionally delays the event (and the Android mini-infobar) until its engagement heuristics are met, which is why the DevTools panel can report “installable” while no event fires.

Other browsers apply different lists:

Browser What it requires before offering install Install surface
Edge 79+ (desktop) Same manifest and HTTPS checks as Chrome, same Chromium release floors Address-bar icon, Apps menu, beforeinstallprompt
Samsung Internet 9.0+ Its own variant of the Chromium list; the compat dataset records beforeinstallprompt from 9.0 Address-bar install button, beforeinstallprompt
Safari, iOS and iPadOS 26 None: every site added from the Share sheet opens as a web app unless the user turns off “Open as Web App” Share sheet › “Add to Home Screen”
Safari, iOS and iPadOS 16.4 to 18 Manifest display: standalone or fullscreen (or the legacy apple-mobile-web-app-capable meta tag) for web-app mode; otherwise a bookmark Share sheet › “Add to Home Screen”
Safari 17+, macOS 14+ None; File › “Add to Dock” works for any page, manifest values are used when present File menu
Firefox 143+, Windows None; the address-bar icon pins any site as a taskbar web app Address-bar icon
Firefox for Android A manifest; installs as a home-screen shortcut, no event Three-dot menu › “Install”

The trade-off for authors is that the Chromium list is the only one that can be failed, so a manifest that satisfies Chrome also satisfies every other browser’s install path; the reverse is not true, and a page that only tests on iOS can ship without the icons Chrome’s dialog needs.

The first example is a page-side check that mirrors the Chromium list; the second shows a manifest that passes it.

Checking the page against the Chromium list and acting on the result

Section titled “Checking the page against the Chromium list and acting on the result”

The page cannot run Chrome’s installability algorithm, but it can verify the inputs it controls and decide what install UI to offer per browser. The fallback branch covers browsers that install without any of these inputs.

async function installabilityReport() {
const problems = [];
if (!isSecureContext) problems.push('not a secure context: no browser will offer install');
const link = document.querySelector('link[rel="manifest"]');
if (!link) {
problems.push('no <link rel="manifest">');
} else {
const manifest = await fetch(link.href).then((r) => r.json()).catch(() => null);
if (!manifest) problems.push('manifest does not parse');
else {
if (!manifest.name && !manifest.short_name) problems.push('manifest needs name or short_name');
if (!manifest.start_url) problems.push('manifest needs start_url');
if (!['standalone', 'fullscreen', 'minimal-ui'].includes(manifest.display)) problems.push('display must not be browser');
const sizes = (manifest.icons ?? []).flatMap((i) => (i.sizes ?? '').split(' '));
if (!sizes.includes('192x192') || !sizes.includes('512x512')) problems.push('icons need 192x192 and 512x512');
}
}
if (!('serviceWorker' in navigator)) problems.push('no service worker support: offline page unavailable');
return problems;
}
const problems = await installabilityReport();
if (problems.length && 'onbeforeinstallprompt' in window) {
console.warn('Chromium will not fire beforeinstallprompt:', problems); // fix the manifest
} else if (!('onbeforeinstallprompt' in window)) {
showMenuInstructions(); // Safari and Firefox install from their menus regardless of the list
}

The service worker line is informational: since Chrome 112 its absence no longer blocks installation, but it does mean the installed app shows Chrome’s default offline page instead of the site’s own.

A manifest that satisfies every browser’s install path

Section titled “A manifest that satisfies every browser’s install path”

The manifest below meets the Chromium list; Safari and Firefox read name, icons, and display from it but do not require them.

{
"name": "Field Notes",
"short_name": "Notes",
"id": "/notes/",
"start_url": "/notes/?source=pwa",
"scope": "/notes/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1f3a5f",
"icons": [
{ "src": "/notes/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/notes/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/notes/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}

Keeping the any and maskable purposes on separate icon entries matters for the Chrome dialog, which needs an icon whose purpose includes any; a single entry with "purpose": "any maskable" passes the check but renders the same bitmap in both contexts.

Specifications

SpecificationStatus
None.