How to turn your web app into an installable PWA
What a web app needs to be installable on Android, iPhone and desktop: the manifest, icons, an optional service worker, iOS differences and caching traps.
Leer en español →
To turn a web app into an installable progressive web app (PWA), serve it over HTTPS and add a web app manifest: a small JSON file with the app's name, a 192-pixel and a 512-pixel icon, a start URL and a display mode such as standalone, linked from every page. That's enough for Chrome, Edge and Samsung Internet to offer installation, and iPhone users can add it from the Share menu. A service worker is optional for installation but is what gives you offline support, faster repeat loads and push notifications.
This guide goes through the manifest, the service worker, what's different on iPhone, the caching mistakes that cause "my users see the old version" problems, and how a PWA compares to publishing in the app stores.
What does "installable" actually give my users?
An installed PWA gets an icon on the home screen or in the dock, opens in its own window without the browser's address bar, appears in the app switcher and can be uninstalled like an app. With a service worker it can also load instantly on repeat visits, show something useful offline and, on supported platforms, receive push notifications.
It's still your web app. One codebase, one deployment, and updates reach everyone at once without app store review.
What do I need in the manifest?
Create manifest.webmanifest (or manifest.json) in your public folder:
{
"name": "Acme Planner",
"short_name": "Planner",
"start_url": "/app",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1f3a5f",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icons/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
Then link it in the <head> of every page:
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
For Chromium-based browsers (Chrome, Edge, Samsung Internet), the manifest must include name or short_name, icons at 192 and 512 pixels, a start_url, a display value of standalone, fullscreen, minimal-ui or window-controls-overlay, and must not set prefer_related_applications to true. Chrome also waits until the visitor has interacted with the page before offering its install prompt.
A few practical notes:
start_urlshould be where a returning user wants to land, often the dashboard rather than the marketing home page. If that page needs a login, make sure the redirect brings them back.- A maskable icon has extra padding so Android can crop it into a circle or squircle without cutting off your logo.
- The apple-touch-icon link is what iOS uses for the home screen icon.
Do I need a service worker?
Not for installation from the browser menu any more. Chrome removed the requirement for a service worker with a fetch handler for menu installation in version 108 on mobile and 112 on desktop, and MDN lists it as optional. You'll still want one for:
- Offline support, even if it's just a friendly "You're offline" page.
- Speed, by caching your app's JavaScript, CSS and fonts after the first visit.
- Push notifications, which run through the service worker.
For a Vite or React app, the vite-plugin-pwa plugin generates the service worker and manifest from your configuration. Next.js projects have similar packages, or you can write a small service worker by hand.
What should the service worker cache?
Cache the app shell: the built JavaScript and CSS files (which have a content hash in their names), icons and fonts. Be careful with:
- API responses with private data. Caching them means one user's data could sit on a shared device after logout. Let those go to the network.
index.html. If you cache it aggressively, users keep loading the old version after you deploy.
Why do users keep seeing the old version?
This is the most common PWA complaint. A new service worker waits until all tabs using the old one are closed before it takes over. On an installed app that people rarely fully close, that can mean days.
Fix it by detecting when a new version is waiting and showing a small "A new version is available, reload" message that tells the waiting worker to activate (vite-plugin-pwa has a prompt mode for this). Avoid forcing an immediate takeover on every deploy if users might be in the middle of filling a form.
What's different on iPhone and iPad?
- There's no automatic install prompt. The
beforeinstallpromptevent doesn't exist on iOS, so a custom "Install app" button won't work there. Show short instructions instead: Share, then Add to Home Screen. - Since iOS 16.4, other browsers on iOS (Chrome, Edge, Firefox and others) can add web apps to the home screen through their Share menu too, not only Safari.
- Since iOS 26, sites added to the home screen open as web apps by default, with an option in the dialog to keep them as a plain bookmark.
- Push notifications on iOS work only for web apps added to the home screen (supported since iOS 16.4), and only after the user grants permission from a tap inside the app.
On desktop, Safari on macOS Sonoma and later offers Add to Dock. Firefox on desktop doesn't install PWAs from a manifest.
Should I use a PWA or publish to the app stores?
| PWA | App store app | |
|---|---|---|
| Distribution | A link; install from the browser | Store listing, review process |
| Updates | Instant on next load | Store review for each release |
| Device features | Many web APIs; fewer than native on iOS | Full native access |
| Discoverability | Search engines | Store search |
You can package a PWA for Google Play using a Trusted Web Activity. Apple's App Store review guidelines reject apps that are little more than a website in a wrapper, so for iOS a store app generally needs real native functionality. If you need the stores, see our App Store publishing service, and for apps built with Bolt and Expo, publishing a Bolt mobile app to the stores.
How do I check it works?
- In Chrome DevTools, open Application, then Manifest. It shows parsed fields, icons and any installability problems.
- Under Service workers in the same panel, check that it's registered and activated, then tick Offline and reload.
- Install it on a real Android phone and a real iPhone, open it from the home screen, log in, close it fully and open it again.
- Deploy a small change and confirm installed users get it, with your update message.
Quick answers
Does a PWA work offline automatically? No. Offline support comes from what your service worker caches and how your app handles failed requests.
Can a PWA send push notifications on iPhone? Yes, on iOS 16.4 and later, if the user added it to the home screen and allowed notifications.
Does making my app a PWA help SEO? Not directly. The pages are still ranked as web pages; any speed gain from caching can help the experience.
Will a PWA replace a native app? For many business tools, yes. For apps that rely heavily on device hardware or store presence, usually not.
Want it done for you?
If you'd like your app installable without the caching surprises, our mobile app development team works on PWAs as well as native apps, and maintenance and support keeps releases and service worker updates under control. Run the free health check first, or contact us with your URL. MDN's guide to making PWAs installable covers each browser in detail.


