Skip to content

Commit 2b99dce

Browse files
committed
Blog: add post about an install button for websites
1 parent 63da48e commit 2b99dce

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

  • blog/2026-10-06-install-button-for-websites
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
slug: install-button-for-websites
3+
title: "An install button for your website - and why the Play Store becomes optional"
4+
authors: [stesee]
5+
tags: [pwa, javascript, android, play store, web app manifest]
6+
---
7+
8+
A website can put its own INSTALL button into its UI. One click, and it sits on the home screen or in the Start menu, opens in its own window and works offline. None of the Play Store hassle is needed for that: no developer account, no registration fee, no revenue share, no closed test and no review queue. There is no package to build, sign or upload either - the deployed site is the app, and a new deployment is the update. This post shows the two pieces needed: a manifest and an event handler.
9+
10+
<!-- truncate -->
11+
12+
## Step 1: the manifest
13+
14+
Chromium only offers installation when the page links a web app manifest. This is the one from our [Push Up Game](https://pushupgame.codeuctivity.cloud/):
15+
16+
```json title="public/manifest.webmanifest"
17+
{
18+
"id": "/",
19+
"name": "Push Up Game",
20+
"short_name": "Push Up Game",
21+
"start_url": "/",
22+
"scope": "/",
23+
"display": "standalone",
24+
"background_color": "#081820",
25+
"theme_color": "#081820",
26+
"icons": [
27+
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
28+
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
29+
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
30+
]
31+
}
32+
```
33+
34+
```html title="index.html"
35+
<link rel="manifest" href="/manifest.webmanifest" />
36+
<!-- Safari on iOS takes the home screen icon from here -->
37+
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
38+
```
39+
40+
[Chrome's install criteria](https://web.dev/articles/install-criteria):
41+
42+
- served over HTTPS (`localhost` counts for development)
43+
- `name` or `short_name`, `start_url`, and `icons` with a 192px and a 512px entry
44+
- `display` set to `fullscreen`, `standalone`, `minimal-ui` or `window-controls-overlay`
45+
- `prefer_related_applications` absent or `false`
46+
- the user has clicked or tapped the page once and spent at least 30 seconds on it
47+
48+
The last point matters for testing: the install event does not fire on page load of a first visit.
49+
50+
A service worker is [no longer required](https://developer.chrome.com/blog/update-install-criteria) for installation from the browser menu (Chrome 108 on mobile, 112 on desktop). Register one anyway, otherwise the installed app shows the browser's offline error page without a network.
51+
52+
Set `id` explicitly. Without it the app's identity is derived from `start_url`, and changing that URL later creates a second app instead of an update.
53+
54+
## Step 2: the `beforeinstallprompt` event
55+
56+
There is no `navigator.install()` to call from a click handler. When the criteria are met, Chromium fires `beforeinstallprompt` on `window`; the event object's `prompt()` method raises the install dialog. The page keeps the event and uses it later.
57+
58+
```ts title="pwa.ts"
59+
interface BeforeInstallPromptEvent extends Event {
60+
prompt(): Promise<{ outcome: "accepted" | "dismissed" }>;
61+
}
62+
63+
/**
64+
* Calls `onAvailable` with a function that raises the install dialog,
65+
* and with `null` once it has been used or the app is installed.
66+
*/
67+
export function installPrompt(onAvailable: (prompt: (() => void) | null) => void): void {
68+
window.addEventListener("beforeinstallprompt", (event) => {
69+
// Keep the browser's own mini-infobar from showing up.
70+
event.preventDefault();
71+
const deferred = event as BeforeInstallPromptEvent;
72+
onAvailable(() => {
73+
onAvailable(null);
74+
void deferred.prompt();
75+
});
76+
});
77+
window.addEventListener("appinstalled", () => onAvailable(null));
78+
}
79+
```
80+
81+
```ts title="main.ts"
82+
const button = document.querySelector<HTMLButtonElement>("#install")!;
83+
button.hidden = true;
84+
85+
installPrompt((prompt) => {
86+
button.hidden = prompt === null;
87+
button.onclick = prompt;
88+
});
89+
```
90+
91+
What is easy to get wrong:
92+
93+
- **The button starts hidden** and only appears once the browser has announced that installation is possible.
94+
- **`prompt()` works once per event**, whatever the user chose, so the handler withdraws the button before calling it.
95+
- **`prompt()` needs a user gesture.** Call it from the click handler.
96+
- **`appinstalled` covers the other path**: the user may install through the address bar icon or the browser menu instead.
97+
98+
## Browser support
99+
100+
`beforeinstallprompt` is a WICG draft that [only Chromium implements](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeinstallprompt_event). Installing works in most other browsers too, but through their own menu.
101+
102+
| Platform | Browser | INSTALL button | How to install |
103+
| --- | --- | --- | --- |
104+
| Android | Chrome, Edge, Samsung Internet | Yes | Button, or browser menu → "Add to Home screen" / "Install app" |
105+
| Windows, macOS, Linux, ChromeOS | Chrome, Edge | Yes | Button, or the install icon in the address bar; own window and a Start menu / taskbar entry |
106+
| iPhone / iPad | Safari | No | Share → "Add to Home Screen" |
107+
| iPhone / iPad | Chrome, Edge, Firefox | No | Share menu → "Add to Home Screen" (iOS 16.4 and later) |
108+
| Desktop | Firefox | No | Not supported; the app only runs in a tab |
109+
| Mac | Safari | No | File → "Add to Dock" (macOS Sonoma and later) |
110+
111+
## Summary
112+
113+
A manifest and a 20-line event handler make a website installable with one click on Android and on desktop Chromium. Nothing but a deployment stands between `git push` and the user's home screen.
114+
115+
The source of this blog, including this post, is on GitHub: [Codeuctivity/codeuctivity.github.io](https://github.com/Codeuctivity/codeuctivity.github.io).
116+
117+
For questions or feedback, contact us at [Codeuctivity@gmail.com](mailto:Codeuctivity@gmail.com).

0 commit comments

Comments
 (0)