Blog · iOS and Safari
Making your site installable as a PWA: the manifest step by step
What each browser needs before it offers to install your site, a minimal manifest explained field by field, maskable icons, and why push still needs a service worker.
To make a browser offer to install your site as a PWA, serve it over HTTPS and link a web app manifest with a name, 192 and 512 pixel icons, a start_url and a display mode such as standalone. Chrome no longer requires a service worker for installation, but you will still need one if you want to send push notifications. This guide builds that manifest field by field and shows what each browser does with it.
In short:
- Chrome and Edge want HTTPS plus a manifest with
nameorshort_name, 192 and 512 px icons,start_urlanddisplay.- Safari on the Mac (macOS Sonoma and later) and on iPhone can add any site to the Dock or Home Screen; the manifest controls the name, icons and launch behavior.
- Desktop Firefox does not install PWAs from a manifest.
- Keep
anyandmaskableicons in separate files, and set a stableidbefore launch.- A service worker is not an install requirement, but push depends on it; on iPhone the site must also be on the Home Screen.
What each browser needs before it installs your site
There is no single definition of "installable"; every engine decides when to offer it:
| Browser | How users install | What it requires |
|---|---|---|
| Chrome and Edge (desktop and Android) | Address bar icon, browser menu or your own button | HTTPS, a manifest with the required fields, and some engagement: at least one click or tap and about 30 seconds on the page |
| Safari on macOS Sonoma or later | File › Add to Dock | Nothing: it works for any site, with or without a manifest |
| Safari on iPhone and iPad | Share › Add to Home Screen | Nothing; since iOS 26 the site opens as a web app by default even without a manifest |
| Firefox desktop | No manifest-based installation | — |
| Firefox on Android | Home screen shortcut | The site opens inside the browser |
On Android, only Chrome (on devices with Google services) and Samsung Internet perform a full install; other browsers create a shortcut. MDN's guide to making PWAs installable keeps this current. The takeaway: the manifest is mandatory for Chromium's install prompt, and on Apple platforms it gives the installed app your name, icons and colors instead of a generic shortcut.
A minimal manifest that works
Create /manifest.webmanifest (manifest.json works too; the file name does not matter) and serve it as application/manifest+json:
{
"id": "/",
"name": "Lumen Bookings",
"short_name": "Lumen",
"description": "Book and manage your appointments",
"start_url": "/app?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1f6feb",
"lang": "en",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
Link it from the <head> of every page, not just the homepage, since users can install from anywhere:
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#1f6feb">
What each field is for:
nameandshort_name: the first shows in the install dialog, the second under the icon. As a rule of thumb, keepshort_namearound 12 characters.start_url: the page that opens when the app launches. Adding?source=pwalets your analytics separate visits from the installed app.display:standaloneremoves the browser UI andminimal-uikeeps a few basic controls. Withbrowser, Chrome will not treat the site as installable.background_colorandtheme_color: the splash screen and title bar colors. Match your CSS to avoid a flash on launch.descriptionandscreenshots: optional, but Chrome uses them for a richer install dialog.
Icons: sizes, maskable and Apple
Chromium requires two sizes, 192×192 and 512×512, as PNG. Android crops icons into whatever shape the device maker picked (circle, rounded square and so on), which is where purpose comes in:
any(the default): the icon is shown as is. Use it for your logo on a transparent background.maskable: the system may crop it. Keep everything important inside a centered circle whose radius is 40% of the icon width, because the edges can disappear.
Avoid "purpose": "any maskable" on a single file: either the logo looks tiny where nothing is cropped, or it loses its edges where it is. Ship two files, as in the example above.
On iPhone and iPad, Safari has used manifest icons since iOS 15.4, but an <link rel="apple-touch-icon"> in your HTML takes precedence. If you inherited one from an older version of the site, make sure it does not show an outdated logo. The usual choice is a 180×180 square PNG with no transparency, because iOS fills transparent areas with black.
start_url, scope and id: avoid surprises later
Three fields define what "your app" is, and mistakes with them surface months later, once people have it installed.
scope: where the app ends
If you omit scope, it defaults to the folder of start_url. With "start_url": "/app/home" and no scope, the app covers only /app/, so a link to /pricing opens outside the app window or with browser chrome on top. If your app and marketing site share a domain and should feel like one product, set "scope": "/". Also keep start_url inside the scope; otherwise browsers discard your scope value.
id: the app's identity
Without an id, the browser uses start_url as the app's identity. Changing start_url later, for example to add ?source=pwa, can then turn it into a "different" app for people who already installed it. Set a fixed id such as "/" before launch and never change it. iOS also uses it to tell installs apart and to sync Focus settings across devices.
Service worker: optional for install, required for push
Chrome used to demand a service worker with a fetch handler before calling a site installable. Not anymore: the manifest is enough. But the service worker is still what receives push messages while your site is closed, so if installation is your first step toward notifying users, you will need one anyway.
iPhone and iPad add one more condition: web push only works once the site is on the Home Screen (iOS 16.4 or later). There, installing is not a nice extra; it is the precondition for asking permission at all. Safari will not suggest it on its own, so your site has to explain it. The castly SDK can detect that case so you can show your own instructions:
<script src="https://castly.sh/sdk/v1/castly.js"></script>
<script>
Castly.init({ publicKey: 'pk_live_…' });
if (Castly.needsInstall()) {
// Safari on iPhone/iPad, not installed: show "Share › Add to Home Screen"
document.querySelector('#install-ios').hidden = false;
} else if (Castly.isSupported()) {
document.querySelector('#enable-alerts').onclick = () => Castly.subscribe();
}
</script>
Desktop Safari lets users subscribe from a regular tab; see web push in Safari on macOS, and the quickstart covers the service worker file you need to serve.
How to check that your site is installable
- Chrome DevTools › Application › Manifest: shows the parsed fields, previews your icons (including the maskable crop) and lists the reasons the site is not installable.
- A real install: try desktop Chrome, Android and Safari's Add to Dock or Add to Home Screen. Check the name, the icon and that
start_urlopens the right page. - Display mode:
window.matchMedia('(display-mode: standalone)').matchestells you whether the site runs as an app; on iOS,navigator.standalonedoes too.
Do not look for a PWA score in Lighthouse: that category was removed in version 12, in 2024. Chrome's install criteria on web.dev are the current reference.
For a custom "Install app" button in Chromium, keep the beforeinstallprompt event and call prompt() from a click. It is non-standard and Safari does not implement it, so treat the button as an enhancement, not the only path:
let deferred;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferred = e;
installButton.hidden = false;
});
installButton.addEventListener('click', async () => {
deferred.prompt();
const { outcome } = await deferred.userChoice; // 'accepted' or 'dismissed'
installButton.hidden = true;
});
Frequently asked questions
Do I need a service worker to make my site installable?
Not in Chrome, Edge or Safari: HTTPS and a valid manifest are enough, and Safari does not even require the manifest. You do need one for offline support and for receiving push notifications while the site is closed.
Can I run two PWAs on the same domain?
Yes, as long as each has its own manifest with a distinct id and scopes that do not overlap, such as /app/ and /admin/. Every page should link the manifest of the app it belongs to.
Why doesn't Chrome offer to install my site?
Usually a required field is missing, an icon fails to load, display is browser, the app is already installed, or there has not been enough engagement yet. The DevTools Manifest panel tells you which.
Can you install a PWA in desktop Firefox?
Not from a manifest: desktop Firefox does not offer PWA installation the way Chrome does. Your site works the same in a tab, and Firefox does not require installation for push.
Does a site need to be installed to receive push notifications?
Only on iPhone and iPad, where it must be on the Home Screen. In Chrome, Edge, Firefox, desktop Safari and on Android, users can subscribe from a normal browser tab.