Quick answer: Chrome only offers installation when the page is served over HTTPS, links a valid web app manifest (name, icons of 192 and 512 px, start_url, display of standalone, fullscreen or minimal-ui) and is not already installed. beforeinstallprompt does not fire inside iframes (itch.io, game portals) or in Safari and Firefox. DevTools > Application > Manifest lists what is missing.

You added a manifest so players can install your browser game, and the install button never appears. Chrome is strict and quiet about installability; fortunately it also tells you exactly what is wrong if you know where to look.

Check DevTools first

Open the game in Chrome, then DevTools > Application > Manifest. The Installability section lists every failed requirement. Fix those before anything else.

Manifest requirements

{
  "name": "My Game",
  "short_name": "MyGame",
  "start_url": "/index.html",
  "display": "fullscreen",
  "background_color": "#000000",
  "icons": [
    { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

Link it with <link rel="manifest" href="manifest.json">. Common mistakes: icon files that 404, sizes that do not match the real image, start_url outside the manifest's scope, and serving the manifest with the wrong content type.

HTTPS

Installation requires a secure context. localhost counts, so local testing works; a LAN IP over plain HTTP does not.

Service worker

Older Chrome versions required a service worker with a fetch handler. Current Chrome no longer needs one to be installable, but you still want one for offline play and caching large game data. Godot's web export has a Progressive Web App option that generates the manifest and service worker for you; Construct 3 has a similar export option.

Handling beforeinstallprompt

The event fires once per page load, and calling prompt() must happen in response to a user gesture:

let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();          // keep it for later
  deferredPrompt = e;
  installButton.hidden = false;
});

installButton.addEventListener('click', async () => {
  if (!deferredPrompt) return;
  deferredPrompt.prompt();
  await deferredPrompt.userChoice;
  deferredPrompt = null;       // it can only be used once
  installButton.hidden = true;
});

Register the listener early (before your game engine loads), or the event may fire before you are listening.

Why it still does not fire

Testing tip

Chrome's install icon in the address bar appears whenever the page is installable, independent of your button. If the icon appears but your button does not, your event handling is the problem; if neither appears, it is the manifest or hosting.

Related fixes