The manifest looks right. You’ve read it three times. Chrome still won’t offer to
install the page, and — this is the part that costs the hour — it never says
why. The install prompt (beforeinstallprompt, and the install icon in the
omnibox) is withheld silently whenever any one of five documented criteria fails. No console
warning by default, no network error, nothing in the Elements panel.
This is the checklist Chrome evaluates, in order, with the failure each step actually looks like in practice — not just what the spec says should be true.
Open Chrome DevTools on the page in question, go to Application → Manifest, and look under “Installability”. Chrome lists the specific criterion it failed there, even though it never surfaces it anywhere a user or a CI log would see. That panel and the checklist on this page check the same five criteria; the difference is that DevTools needs the page open in a real Chrome window, and this tool works from a CLI fetch (usable in CI, against a staging URL, or against a page you can’t easily open in a browser).
| # | Criterion | What it looks like when it silently fails |
|---|---|---|
| 1 | A <link rel="manifest"> tag exists on this page |
The manifest is linked from the homepage but not from the page the visitor actually landed on — a blog post, a pricing page reached from search. |
| 2 | The manifest URL returns valid JSON | A trailing comma or a build step that emits the file with a wrong Content-Type
so some fetchers refuse to parse it. |
| 3 | name or short_name is set |
Rare on its own, but shows up in a hand-trimmed manifest that dropped both while removing other fields. |
| 4 | display is standalone, fullscreen, or
minimal-ui |
The most common cause. The field is left out entirely, which the spec
defaults to "browser" — and "browser" is explicitly excluded
from what Chrome will install. The manifest reads as complete because nothing is
wrong, only absent. |
| 5 | An icon of at least 192×192 (or sizes: "any") actually
loads |
The second most common cause. The manifest lists the icon path
correctly, but the file 404s (moved during a deploy, wrong build output directory) or the
sizes attribute says 192x192 while the actual image file is
smaller. Reading the manifest JSON alone cannot catch either — the file has to be
fetched. |
Why #4 and #5 are the ones that waste an hour: both pass a glance at the
manifest file. The field is either present-but-wrong (an icon path that 404s) or silently
defaulted (a missing display), so scanning the JSON by eye finds nothing to fix.
Both require actually fetching what the manifest points at — which is what
the CLI on this page does, and what reading the file in an editor cannot.
A missing 512×512 icon, a missing start_url, and missing
theme_color/background_color are real gaps — they affect the
splash screen and richer install UI — but none of them withholds the install prompt on their
own. If the checklist above is clean and the prompt still doesn’t appear, these are not the
cause.
A service worker is not on this checklist because it is not part of Chrome’s current installability criteria. It used to be, in older Chrome versions and in some other browsers’ implementations, so if your target is one of those, verify the prompt directly in that browser — a manifest-only check like this one has no way to see it.
Chrome debounces beforeinstallprompt per origin as an anti-annoyance measure: once a
visitor dismisses it, or after enough visits without installing, Chrome stops firing the event for a
while. Test in a fresh profile or an Incognito window before concluding the manifest is still at
fault — a clean report from this checklist and a suppressed prompt from engagement heuristics
look identical from the manifest’s side.
node ventures/pwa-installability-check/check.mjs https://example.com/ --json
Paste the output into the report viewer for a plain-English breakdown of exactly
which of the five criteria failed, or read the exit code directly (0 installable by
this checklist, 1 otherwise) to use it as a CI gate against a regression.
Chrome evaluates the manifest silently and simply withholds the beforeinstallprompt event and the omnibox install icon when any criterion fails — there is no console error or warning by default. The one built-in way to see the reason is DevTools' Application panel, under Manifest, which lists "Installability" errors when you open it on the page in question.
The two most common causes are a display mode that defaults to "browser" because the display field was left out entirely, and an icon that is listed in the manifest but 404s or is smaller than 192x192 when actually fetched — both invisible from reading the manifest file itself, because the file can look complete while the field is absent or the icon file is missing on disk.
Only the page a visitor is on needs the <link rel="manifest"> tag for Chrome to evaluate installability on that page. A manifest linked only from the homepage means a visitor who lands on an inner page first, from a search result or a shared link, gets no install prompt at all on that page even though the site is installable elsewhere.
Chrome also debounces the prompt per origin: once a visitor dismisses beforeinstallprompt or after enough page visits without installing, Chrome stops firing it for a while as an anti-annoyance measure. If every documented criterion checks out and it still will not prompt on a fresh profile or an incognito window, that engagement heuristic — not the manifest — is the remaining suspect.
Not for Chrome's current installability criteria, which this checklist covers in full. Some other browsers and some older Chrome versions did require a registered service worker with a fetch handler. If a specific target browser still enforces that, it will not show up in a manifest-only check like this one — verify the actual prompt in that browser directly.