Platform Support for Progressive Web Apps¶
A Progressive Web App runs on whatever browser engine the user's platform gives it, and that engine, not your code, decides which PWA features exist. Three engines matter: Blink (Chrome, Edge, Samsung Internet and most other Chromium browsers), WebKit (Safari, and every browser on iPhone and iPad) and Gecko (Firefox). This section documents how each platform installs, runs and limits PWAs. This index gives you the support matrix for September 2026, the engine rules behind it, and the feature detection patterns that let one codebase work everywhere.
Key takeaways
- The engine decides. Chrome, Edge, Samsung Internet, Opera, Brave and Vivaldi share Blink's PWA features, with vendor-specific install UI. Safari and all iOS browsers share WebKit's. Firefox uses Gecko on every platform except iOS.
- On iPhone and iPad, Home Screen web apps always run on WebKit. Apple allows other engines for browser apps only in the EU (iOS 17.4 and later) and Japan (iOS 26.2 and later). Open Web Advocacy reported in June 2026 that no vendor had shipped one to users.
- The core is cross-browser. Service workers, the Cache API, IndexedDB, Web Push and
persist()work in all seven browsers in the matrix, with iOS limiting push to Home Screen web apps. Web Share is missing only in desktop Firefox (flag) and Chrome on Linux. - Background and OS-integration APIs are Chromium-only. Background Sync, Periodic Background Sync, Background Fetch, File Handling, manifest protocol handlers, Window Controls Overlay and the rich install UI exist only in Blink browsers, and several of them only on desktop or only on Android.
- Installation differs everywhere. Chromium fires
beforeinstallprompt. Safari (iOS and macOS) and Firefox never do, so you need instructions instead of a button there. iOS 26 made every site added to the Home Screen open as a web app by default. - Detect features, never browsers. Some APIs exist but do nothing (
setAppBadge()on Android). Others exist only in one context (Notificationon iOS only inside a Home Screen web app). The detection module on this page accounts for both.
The three browser engines behind every PWA¶
A browser engine is the part that parses HTML and CSS, runs JavaScript and implements web APIs: service workers, the manifest parser, push, storage. The browser shell around it provides the address bar, menus and, for PWAs, the install UI and the integration with the operating system. When you ask "does this PWA feature work in browser X?", the engine answers most of the question and the shell answers the rest.
| Engine | Maintained by | Browsers built on it | Platforms | JavaScript engine |
|---|---|---|---|---|
| Blink | The Chromium project (Google, Microsoft, Samsung, Intel and others) | Chrome, Microsoft Edge, Samsung Internet, Opera, Brave, Vivaldi, Android WebView | Windows, macOS, Linux, ChromeOS, Android. Not iOS or iPadOS, except for prototypes under Apple's EU and Japan rules | V8 |
| WebKit | Apple, with outside contributors | Safari on every Apple platform, all browsers on iOS and iPadOS (Chrome, Edge, Firefox and others use WKWebView), apps using WKWebView or SFSafariViewController | macOS, iOS, iPadOS, visionOS | JavaScriptCore |
| Gecko | Mozilla | Firefox on desktop and Android, Firefox forks | Windows, macOS, Linux, Android. Firefox on iOS uses WebKit | SpiderMonkey |
Blink: where most PWA APIs ship first¶
Almost every PWA-specific capability beyond the core started in Chromium: beforeinstallprompt, WebAPKs, Background Sync, Periodic Background Sync, Background Fetch, Web Share Target, File Handling, manifest protocol_handlers, Window Controls Overlay, launch_handler, display_override, Isolated Web Apps. Many of these are specified in W3C Community Group or WICG documents rather than at the Recommendation stage. Mozilla or Apple have declined to implement several of them, so they are likely to stay Chromium-only.
Blink browsers share the engine, but they don't share everything:
- Install UI is per vendor. Chrome, Edge and Samsung Internet each build their own install surfaces. Samsung Internet mints WebAPKs only on Samsung devices. Edge adds its own install entry points on Windows. The rich install UI documented for Chrome isn't guaranteed to look the same in other Chromium browsers.
- Features can be switched off. A Chromium-based browser can ship with any Blink feature disabled, for privacy or product reasons. Test the browsers your analytics say matter.
- Versions lag. Samsung Internet 30, current since May 11, 2026, is built on Chromium 143 according to MDN's browser data, while Chrome itself was at 154 in September 2026. A Blink feature that shipped in Chrome 145 isn't in Samsung Internet yet.
- Android WebView isn't a PWA runtime. It's Blink, but apps that embed it don't install web apps, don't show
beforeinstallpromptand don't support Web Share. Trusted Web Activity is the supported way to put a PWA inside an Android app.
WebKit: one engine for every Apple device¶
Safari on macOS and every browser on iPhone and iPad run on WebKit. On iOS and iPadOS that is an App Store rule, not a technical accident. Apple's App Review Guideline 2.5.6 says: "Apps that browse the web must use the appropriate WebKit framework and WebKit JavaScript." The same guideline now adds that developers "may apply for an entitlement to use an alternative web browser engine" for the EU and Japan (see below).
Two consequences for PWAs:
- Chrome on iPhone isn't Chrome's engine. Chrome, Edge and Firefox on iOS have WebKit's feature set, not Blink's or Gecko's. They can't fire
beforeinstallprompt, don't support Background Sync, and can't install a WebAPK-style app. Since iOS and iPadOS 16.4 they can offer Add to Home Screen from their share menus, and the result is the same WebKit Home Screen web app that Safari creates. - WebKit's PWA features depend on context. Push, the
Notificationinterface and badging exist on iOS only inside a Home Screen web app, never in a Safari tab. Screen Wake Lock worked in Safari tabs from iOS 16.4 but not in Home Screen web apps until 18.4. Feature detection has to run in the context you care about.
iOS & iPadOS documents all of this in depth. WebKit on macOS adds web apps in the Dock (Safari 17 on macOS Sonoma and later), covered in Desktop Platforms.
Gecko: service workers yes, installation barely¶
Firefox implements the core service worker, Cache, IndexedDB, Push and Notifications APIs on desktop and Android, and also Web Share on Android and the Screen Wake Lock API (Firefox 126). Mozilla hasn't implemented the Chromium background APIs or the manifest integration members. Installation is the weak point:
- Firefox for Android installs manifest-based apps as a launcher shortcut with Firefox's badge. It isn't a WebAPK. The app opens in Firefox's standalone web-app activity. There's no
beforeinstallprompt. - Firefox on the desktop had no installation at all for years. Firefox 143 (September 16, 2025) added web apps on Windows: its release notes say "Firefox now supports running websites as web apps pinned directly to the taskbar". Firefox 150 (April 21, 2026) made them available to Microsoft Store installs as well. On Linux the feature exists but is disabled by default behind the
browser.taskbarTabs.enabledpreference, and macOS has no equivalent. MDN's compatibility data still lists the manifest members as unsupported in desktop Firefox, so don't expect it to honordisplay,shortcutsor icons the way Chromium does.
The iOS WebKit requirement and alternative engines¶
For most of the iPhone's history, the WebKit rule was global and absolute. Regulation has changed that in two regions, but not for Home Screen web apps.
| Region | Law | Alternative engines allowed from | What is allowed |
|---|---|---|---|
| European Union | Digital Markets Act (DMA) | iOS 17.4 (March 5, 2024); iPadOS 18 | Dedicated browser apps and apps with in-app browsing, under Apple's Web Browser Engine and Embedded Browser Engine entitlements |
| Japan | Mobile Software Competition Act (MSCA) | iOS 26.2 (December 12, 2025) | "Dedicated browser apps that provide a full web browser experience, and apps from browser engine stewards that provide in-app browsing experiences using an embedded browser engine" (Apple) |
| Everywhere else | – | – | WebKit only |
Apple's entitlement requirements for an alternative-engine browser are substantial. The engine must pass 90% of the Web Platform Tests and 80% of Test262, block third-party cookies by default, partition storage per top-level site, use memory-safe languages for web content processing, and fix actively exploited vulnerabilities within 30 days. Two things matter for PWA developers:
- Home Screen web apps stay on WebKit. When the EU rules were introduced, betas of iOS 17.4 turned EU Home Screen web apps into bookmarks. Apple reversed that before release and said Home Screen web apps "continue to be built directly on WebKit and its security architecture". Apple hasn't documented a way for an alternative-engine browser to create Home Screen web apps that run on its own engine. iOS & iPadOS tells the full story.
- Nobody has shipped yet. Open Web Advocacy, which campaigns for engine choice on iOS, wrote in July 2025 that "no browser vendor has ported their engine to iOS over the past 15 months". Its June 2026 post described a Blink prototype built by Microsoft's Edge team with Apple's BrowserEngineKit, running on iOS 26.5.1, and still reported no shipping product. For planning purposes, assume every iOS user in every region gets WebKit's feature set.
How to read the support matrix¶
The matrix below compares the seven browser and platform combinations that matter for PWAs. Versions are the first release with support according to MDN's browser-compat-data (the dataset behind MDN and caniuse's "mdn-" features), checked against vendor release notes where the two disagree.
Support data as of September 2026. The current stable releases were Chrome 154 (September 22, 2026), Edge 153 (September 11, 2026), Firefox 156 (September 15, 2026), Safari 27 (September 14, 2026, on iOS 27, iPadOS 27, macOS 27, macOS 26 and macOS Sequoia) and Samsung Internet 30 (May 11, 2026). For live data, check MDN's Progressive Web Apps reference and caniuse.
Legend:
- ✅ supported (number = first version)
- ⚠️ partial or conditional (see the footnote)
- ❌ not supported
- 🧪 behind a flag or in an origin trial
The Chrome / Edge column covers desktop Chrome and Edge on Windows, macOS, Linux and ChromeOS. Edge numbers match Chrome's from Edge 79 (the first Chromium-based Edge) onward, unless noted. The Safari macOS column covers Safari tabs and web apps added to the Dock. The Safari iOS column covers iPhone and iPad, and therefore every iOS browser.
PWA feature support matrix (September 2026)¶
Installation and app integration¶
| Feature | Chrome / Edge desktop | Chrome Android | Samsung Internet | Firefox desktop | Firefox Android | Safari macOS | Safari iOS / iPadOS |
|---|---|---|---|---|---|---|---|
| Install as an app | ✅ app window | ✅ WebAPK | ✅ WebAPK1 | ⚠️ 143, Windows only2 | ✅ shortcut3 | ✅ 17, Add to Dock4 | ✅ Add to Home Screen5 |
beforeinstallprompt | ✅ | ✅ | ✅ 5.0 | ❌ | ❌ | ❌ | ❌ |
appinstalled event | ✅ 64 | ✅ 57 | ✅ 7.0 | ❌ | ❌ | ❌ | ❌ |
Manifest display: standalone | ✅ | ✅ | ✅ | ❌2 | ✅ 47 | ✅ 17 | ✅ 11.3 |
fullscreen, minimal-ui | ✅ | ✅ | ✅ | ❌ | ✅ 47 | ❌ | ⚠️6 |
display_override | ✅ 89 | ✅ 89 | ✅ 15.0 | ❌ | ❌ | ❌ | ❌ |
App shortcuts (shortcuts) | ✅ 967 | ✅ 84 | ✅ 14.0 | ❌ | ❌ | ✅ 17.4 | ❌ |
Rich install UI (screenshots, description) | ✅ 108 | ✅ 94 | ⚠️8 | ❌ | ❌ | ❌ | ❌ |
Web Share Target (share_target) | ⚠️ 89, ChromeOS only | ✅ 71 (GET), 76 (POST, files) | ✅ 12.0 | ❌ | ❌ | ❌ | ❌ |
File Handling (file_handlers) | ✅ 102 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Protocol handlers | ✅ 96 (manifest)10 | ❌ | ❌ | ⚠️ API only10 | ⚠️ API only10 | ❌ | ❌ |
| Window Controls Overlay | ✅ 105 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
launch_handler | ✅ 110 | ✅ 110 | ✅ 21.0 | ❌ | ❌ | ❌ | ❌ |
scope_extensions | ✅ 1399 | ⚠️9 | ⚠️9 | ❌ | ❌ | ❌ | ❌ |
Manifest id | ✅ 96 | ✅ 96 | ✅ 17.0 | ❌ | ❌ | ✅ 17 | ✅ 16.4 |
Manifest icons | ✅ | ✅ | ✅ | ❌ | ✅ 79 | ✅ 1711 | ✅ 15.411 |
Manifest theme_color | ✅ | ✅ | ✅ | ❌ | ✅ 79 | ✅ 17 | ✅ 15 |
Manifest background_color (splash) | ✅ | ✅ | ✅ | ❌ | ✅ 79 | ❌ | ❌ |
Manifest orientation | ✅ | ✅ | ✅ | ❌ | ✅ 79 | ❌ | ❌ |
display-mode media query | ✅ 42 | ✅ 42 | ✅ 4.0 | ✅ 47 | ✅ 47 | ✅ 13 | ✅ 12.2 |
Service workers, background work and engagement¶
| Feature | Chrome / Edge desktop | Chrome Android | Samsung Internet | Firefox desktop | Firefox Android | Safari macOS | Safari iOS / iPadOS |
|---|---|---|---|---|---|---|---|
| Service workers | ✅ 40 (Edge 17) | ✅ 40 | ✅ 4.0 | ✅ 44 | ✅ 44 | ✅ 11.1 | ✅ 11.3 |
| Navigation preload | ✅ 59 | ✅ 59 | ✅ 7.0 | ✅ 99 | ✅ 99 | ✅ 15.4 | ✅ 15.4 |
Static routing (addRoutes()) | ✅ 123 | ✅ 123 | ✅ 27.0 | ❌ | ❌ | ✅ 27 | ✅ 27 |
| Push API | ✅ 42 (Edge 17) | ✅ 42 | ✅ 4.0 | ✅ 44 | ✅ 48 | ✅ 16.112 | ⚠️ 16.415 |
| Declarative Web Push | ❌ | ❌ | ❌ | 🧪14 | ❌ | ✅ 18.513 | ✅ 18.415 |
pushsubscriptionchange | ⚠️ 13816 | ⚠️ 13816 | ⚠️ 30.016 | ✅ 4417 | ✅ 4817 | ✅ 16 | ❌ |
Notification actions | ✅ 48 | ✅ 48 | ✅ 6.0 | ✅ 152 | ✅ 152 | ❌ | ❌ |
| Badging API | ⚠️ 8118 | ❌19 | ❌ | ❌ | ❌ | ✅ 17, web apps only | ✅ 16.4, Home Screen web apps only |
| Background Sync | ✅ 49 (Edge 79) | ✅ 49 | ✅ 5.0 | ❌ | ❌ | ❌ | ❌ |
| Periodic Background Sync | ✅ 8020 | ✅ 8020 | ✅ 13.0 | ❌ | ❌ | ❌ | ❌ |
| Background Fetch | ✅ 74 (Edge 79)21 | ✅ 74 | ✅ 11.0 | ❌ | ❌ | ❌ | ❌ |
Web Share (navigator.share()) | ⚠️ 89 / 12822 | ✅ 61 | ✅ 8.0 | 🧪 flag | ✅ 79 | ✅ 12.1 | ✅ 12.2 |
| Screen Wake Lock | ✅ 84 | ✅ 84 | ✅ 14.0 | ✅ 126 | ✅ 126 | ✅ 16.4 | ⚠️ 16.4 / 18.423 |
Persistent storage (persist()) | ✅ 55, heuristic | ✅ 55, heuristic | ✅ 6.0 | ✅ 57, prompt | ✅ 57 | ✅ 15.2, heuristic | ✅ 15.2, heuristic24 |
navigator.storage.estimate() | ✅ 61 | ✅ 61 | ✅ 8.0 | ✅ 57 | ✅ 57 | ✅ 17 | ✅ 17 |
| Origin Private File System | ✅ 86 | ✅ 109 | ✅ 21.0 | ✅ 111 | ✅ 111 | ✅ 15.2 | ✅ 15.2 |
Device and platform APIs PWAs often ask for¶
PWAs are frequently compared with native apps on hardware access. The APIs below aren't PWA-specific, but they decide whether a PWA can replace a native app for a given use case. Detailed coverage is in Hardware & Device APIs, File System Access and Media & System APIs.
| API | Chrome / Edge desktop | Chrome Android | Samsung Internet | Firefox desktop | Firefox Android | Safari macOS | Safari iOS / iPadOS |
|---|---|---|---|---|---|---|---|
File pickers (showOpenFilePicker()) | ✅ 86 | ✅ 132 | ✅ 29.0 | ❌ | ❌ | ❌ | ❌ |
| Web Bluetooth | ✅ 56 / 7025 | ✅ 56 | ✅ 6.0 | ❌ | ❌ | ❌ | ❌ |
| WebUSB | ✅ 61 | ✅ 61 | ✅ 8.0 | ❌ | ❌ | ❌ | ❌ |
| WebHID | ✅ 89 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Web NFC | ❌ | ✅ 89 | ✅ 15.0 | ❌ | ❌ | ❌ | ❌ |
| Contact Picker | ❌ | ✅ 80 | ❌ | ❌ | ❌ | ❌ | 🧪 14.5 |
| Vibration | ⚠️26 | ✅ 32 | ✅ 2.0 | ❌ | ⚠️26 | ❌ | ❌ |
| Screen orientation lock | ❌27 | ✅ 38 | ✅ 3.0 | ⚠️ 14427 | ✅ 144 | ❌ | ❌ |
| Fullscreen API | ✅ 71 | ✅ 71 | ✅ 10.0 | ✅ 64 | ✅ 64 | ✅ 16.4 | ⚠️ iPad only |
| WebAuthn (passkeys) | ✅ 67 | ✅ 70 | ✅ 10.0 | ✅ 6028 | ✅ 92 | ✅ 13 | ✅ 13 |
| Payment Request | ✅ 60 | ✅ 53 | ✅ 6.0 | 🧪 flag | ❌ | ✅ 11.129 | ✅ 11.329 |
| Navigation API | ✅ 102 | ✅ 102 | ✅ 19.0 | ✅ 147 | ✅ 147 | ✅ 26.2 | ✅ 26.2 |
| WebGPU | ✅ 113 (Linux 144) | ✅ 121 | ✅ 25.0 | ⚠️ 141, Windows | ❌ | ✅ 26 | ✅ 26 |
What the matrix means for each platform¶
The matrix compresses a lot. Here is what each column means in practice, and where the detail lives.
Chrome and Edge on the desktop. The most complete PWA platform. Installed apps get their own windows, taskbar or Dock entries, OS-level file and protocol association, shortcuts in the jump list or Dock menu, badges (except on Linux), Window Controls Overlay and link capturing. beforeinstallprompt lets you offer your own install button. Edge adds its own installation surfaces and Microsoft Store distribution. ChromeOS is the only desktop where share_target works. Chromium's Isolated Web Apps go further with Direct Sockets and Controlled Frame, but only for managed or allowlisted apps. Desktop Platforms covers Windows, macOS, Linux and ChromeOS.
Chrome on Android. Installation mints a WebAPK, a real Android package generated by Google's servers. It appears in the app drawer, in Settings and in the share sheet (through share_target), and it captures links in its scope through intent filters. Background Sync, Periodic Background Sync and Background Fetch work here, and push works with the app closed. There's no badging API (Android's notification dots take its place), no file handling and no protocol handlers. Android covers WebAPKs, updates and Trusted Web Activities.
Samsung Internet. Chromium with a lag of several Chrome versions and its own UI. Most Chromium PWA features are present from the version in the matrix. It mints WebAPKs on Samsung devices only. Test install and share flows on a Samsung phone, because the UI and the WebAPK minting service differ from Chrome's.
Firefox on the desktop. Service workers, offline caching, push and notifications work in the browser. Installation exists on Windows, as taskbar web apps from Firefox 143 (on Linux only behind a disabled-by-default preference), with no install event and no documented list of honored manifest members. Treat desktop Firefox as a place where your PWA runs well as a website.
Firefox for Android. Service workers, push and Web Share work, and manifest-based installation creates a Firefox-badged shortcut that opens in a standalone activity. No background sync of any kind, no share target, no badging.
Safari on macOS. Safari 17 and later on macOS Sonoma can add any site to the Dock. The resulting web app has its own window, its own cookies (copied from Safari when it's created) and its own storage. It gets push, notifications and badging, shortcuts since Safari 17.4, and link capturing for its scope since Safari 18. No beforeinstallprompt and no Chromium background APIs.
Safari on iOS and iPadOS. The platform with the most rules. Every browser uses WebKit. Installation is manual through the Share sheet, and since iOS 26 it works for any site. Push, notifications and badging exist only inside Home Screen web apps. There's no background execution besides push. Storage is separate from Safari's and exempt from Safari's seven-day deletion rule. iOS & iPadOS is the deep dive.
Other places your PWA runs¶
The matrix covers the browsers users install. Your PWA also runs in environments that aren't browsers in that sense.
| Environment | Engine | Service workers | Install | Notes |
|---|---|---|---|---|
| Opera, Brave, Vivaldi (desktop and Android) | Blink | ✅ | ✅ (vendor UI) | Feature set follows Chromium, but each vendor can disable APIs and builds its own install UI. |
| Android WebView (inside native apps) | Blink | ✅ | ❌ | No install, no navigator.share(), no beforeinstallprompt. Use Trusted Web Activity instead to ship a PWA as an Android app. |
| Chrome Custom Tabs (in-app browser on Android) | The user's browser | ✅ (shares the browser's storage) | ❌ from the tab | Runs in the user's default browser with its cookies and storage. |
WKWebView in iOS apps | WebKit | ❌ per MDN (webview_ios), except in apps that opt in to App-Bound Domains (iOS 14+) | ❌ | Includes most in-app browsers, which don't use App-Bound Domains. Storage quota is 15% of disk per origin instead of 60%, per WebKit's storage policy. |
SFSafariViewController on iOS | WebKit | ✅ | ❌ | Also what Home Screen web apps use for out-of-scope links. |
| Chrome, Edge, Firefox on iOS | WebKit | ✅ | ✅ Add to Home Screen since iOS 16.4 | Creates the same WebKit Home Screen web app as Safari. |
| Microsoft Store / Google Play packages | Edge / Chrome | ✅ | ✅ through the store | See Publishing to App Stores. |
Two patterns cause most real-world surprises:
- In-app browsers. Links opened from social or messaging apps often land in an embedded web view where your service worker may not run, storage is smaller and separate from the user's browser, and installation is impossible. If your install funnel starts from a link in such an app, tell users to open the page in their browser first.
- The same user, two copies of your app. On iOS, the Home Screen web app and Safari tab don't share storage. On macOS, the Dock app and the Safari tab don't either. On Android, a WebAPK shares storage with Chrome. Design sign-in and data sync so that a second copy isn't a broken copy.
Feature detection instead of browser sniffing¶
Every row in the matrix can change with a browser release, and several have caveats a user agent string can't express: context (tab or installed app), OS version, device vendor or engagement. Detect the capability itself, and design each feature as an enhancement over a baseline that works everywhere.
A capability detection module¶
The general-purpose module lives on the capabilities page: A capability detection module covers the Tier 1 to Tier 3 capability APIs, and Your First PWA shows the minimal checks a new app needs. What this page adds are the platform-matrix questions that module doesn't answer: am I in an installed window, is this an iPhone or iPad browser tab, and which background and push features exist. Manifest-only features (share_target, shortcuts, file_handlers, protocol_handlers) have no runtime API to query. You find out they work when a launch arrives.
const nav = globalThis.navigator ?? {};
const regProto = globalThis.ServiceWorkerRegistration?.prototype ?? {};
// Check navigator.standalone first: an iOS web app whose manifest says "standalone"
// matches display-mode: fullscreen (WebKit bug 264218); with no manifest it reports "browser".
export function isInstalledContext() {
if (nav.standalone === true) return true;
return ["window-controls-overlay", "fullscreen", "standalone", "minimal-ui"]
.some((mode) => globalThis.matchMedia?.(`(display-mode: ${mode})`).matches);
}
// navigator.standalone also exists in macOS Safari 17+ (false in tabs, true in Dock
// web apps), so require a touchscreen to single out iPhone and iPad browser tabs.
export function isAppleMobileTab() {
return nav.standalone === false && nav.maxTouchPoints > 0;
}
export function detectPlatformCapabilities() {
const installed = isInstalledContext();
const installPromptEvent = "BeforeInstallPromptEvent" in globalThis;
return {
installed, installPromptEvent,
needsManualInstall: !installed && !installPromptEvent, // iOS, Safari macOS, Firefox
push: "PushManager" in globalThis && "serviceWorker" in nav,
declarativePush: "pushManager" in globalThis, // Safari 18.4 iOS, 18.5 macOS
backgroundSync: "sync" in regProto,
periodicSync: "periodicSync" in regProto, // also needs a granted permission
backgroundFetch: "backgroundFetch" in regProto,
};
}
Use it to choose a path, not to block the user:
import { capabilities } from "./capabilities.js"; // the module on the capabilities page
import { detectPlatformCapabilities, isAppleMobileTab } from "./platform-caps.js";
const caps = detectPlatformCapabilities();
// 1. Install: a button where the browser supports it, instructions elsewhere.
if (caps.installPromptEvent) {
import("./install-button.js"); // listens for beforeinstallprompt
} else if (caps.needsManualInstall && isAppleMobileTab()) {
document.querySelector("#ios-install-help").hidden = false;
}
// 2. Offline writes: Background Sync where it exists, sync-on-launch everywhere.
if (!caps.backgroundSync) {
window.addEventListener("online", () => flushOutbox());
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") flushOutbox();
});
}
// 3. Share: native share sheet, else copy to clipboard.
const shareButton = document.querySelector("#share");
shareButton.hidden = false;
shareButton.addEventListener("click", async () => {
const data = { title: document.title, url: location.href };
if (capabilities.share && (!navigator.canShare || navigator.canShare(data))) {
try {
await navigator.share(data);
return;
} catch (error) {
if (error.name === "AbortError") return; // user closed the sheet
// NotAllowedError (no user activation, permissions policy) falls through.
}
}
try {
await navigator.clipboard.writeText(data.url);
shareButton.textContent = "Link copied";
} catch {
window.prompt("Copy this link:", data.url); // last resort: clipboard blocked
}
});
async function flushOutbox() {
// Replays requests queued in IndexedDB. A complete implementation with
// idempotency keys is outbox.js on the iOS & iPadOS page.
const { flush } = await import("./outbox.js");
await flush();
}
Two capabilities exist but may be inert until you check asynchronously: Periodic Background Sync needs navigator.permissions.query({ name: "periodic-background-sync" }) to return "granted", and navigator.storage.persisted() tells you, without prompting, whether storage is protected from eviction.
APIs that exist but don't work¶
"The property exists" isn't always "the feature works". These are the cases, verified against browser documentation and MDN's compatibility notes, where naive detection gives the wrong answer:
| API | Where it misleads | What actually happens | Better check |
|---|---|---|---|
navigator.setAppBadge() | Chrome for Android, Chrome on Linux | Exposed and resolves, no badge appears | Treat the badge as a hint. Keep the count visible in your UI too. |
Notification, PushManager | Safari on iOS and iPadOS | Undefined in a browser tab, defined in a Home Screen web app | Check in the context where the user will subscribe. Show install help in tabs (navigator.standalone === false). |
navigator.wakeLock | Home Screen web apps on iOS 16.4 to 18.3 | Present, but the lock didn't hold in standalone mode | Handle release events and re-request on visibilitychange. Older devices simply won't keep the screen on. |
registration.periodicSync | Chromium, uninstalled or low-engagement sites | register() rejects, or events never fire | Query the periodic-background-sync permission and handle rejection. |
screen.orientation.lock() | Desktop Chrome and Edge, Firefox on non-tablet desktops, Safari | Method missing (Safari) or present and rejecting with NotSupportedError | Call inside try/catch, only in fullscreen or an installed app. |
navigator.vibrate() | Firefox for Android | Returns true without vibrating | Never make vibration the only feedback. |
navigator.share() | Desktop Firefox (flag), Chrome on Linux | Missing | Always ship a copy-link fallback. |
BeforeInstallPromptEvent | Chromium, already installed or criteria not met | Constructor exists, event never fires | Wait for the event. Don't show an install button because the constructor exists. |
Don't sniff user agents¶
User-agent strings are the wrong tool for PWA decisions:
- Every browser on iOS reports a WebKit engine, but their user-agent strings name their brand (
CriOS,EdgiOS,FxiOS). A "Chrome" check that enables Background Sync breaks on iPhone. - iPadOS Safari requests desktop sites by default and reports a macOS user agent. A "Mac means desktop Safari" rule misclassifies iPads, which need the iOS install instructions.
- Chromium's user-agent reduction froze most of the platform version detail in the string. What remains doesn't tell you which features a given build enables.
- Context isn't in the user agent. Nothing in the string tells you whether the page runs in a Home Screen web app, an installed desktop window or a WebView.
If you need the platform for copy (for example, "tap Share, then Add to Home Screen"), combine feature checks. navigator.standalone used to be an iOS-only signal, but WebKit enabled it "for all Cocoa platforms, which including macOS and iOS" in June 2023 (WebKit commit 265004@main), so Safari 17 and later on macOS exposes it too (sites that assumed "has standalone means iOS" broke; WebKit carried a site-specific quirk for oracle.com through most of 2024). Pair it with navigator.maxTouchPoints > 0 to tell an iPhone or iPad from a Mac, as isAppleMobileTab() above does. On the Mac it is false in Safari tabs and true in Dock web apps. navigator.userAgentData?.platform is available in Chromium on HTTPS pages when you need the OS name there.
Progressive enhancement tiers¶
A practical way to use the matrix is to sort your features into tiers and make sure each tier degrades cleanly.
| Tier | Features | Where it works | Fallback |
|---|---|---|---|
| Baseline | HTTPS, responsive UI, manifest, icons, service worker with an offline page, Cache API, IndexedDB | All seven columns | – |
| Engagement | Web Push, notifications, badges | Everywhere except iOS tabs, with badging missing on Android and Firefox | Email or in-app inbox; count in the UI; install prompt on iOS |
| Reliability | Background Sync, Background Fetch, Periodic Background Sync | Chromium only | Sync on launch, visibilitychange and online; foreground downloads with progress |
| OS integration | Share target, file handling, protocol handlers, shortcuts, Window Controls Overlay | Chromium, split between desktop and Android; shortcuts also on macOS Safari | Paste and upload UI; in-app navigation; standard title bar |
| Hardware | Bluetooth, USB, HID, NFC, Serial | Chromium, split between desktop and Android; Firefox 151 (desktop) ships Web Serial, gated behind a site-permission add-on | Companion native app, or a feature that isn't offered |
Testing across platforms¶
A support table tells you what should work. Only devices tell you what does. At a minimum, test on:
- A current iPhone with the site added to the Home Screen, and in a Safari tab. Debug with Safari's Web Inspector over USB or the network. The iOS debugging steps walk through it.
- A Pixel or other Android device with Chrome, installed as a WebAPK. Use
chrome://inspectfrom desktop Chrome andabout://webapkson the device. - A Samsung device with Samsung Internet if your analytics show meaningful Samsung Internet traffic.
- Chrome or Edge on Windows and macOS, installed, with the app window, badges and file or protocol handling you use.
- Firefox on desktop and Android, for the "no install events, no background APIs" path.
Automate what you can: Playwright drives Chromium, WebKit and Firefox builds, which catches engine-level regressions but not installed-app behavior. Browser DevTools and Automated Testing cover the tooling. Lighthouse no longer has a PWA category; Lighthouse & Auditing explains what to check instead.
Pages in this section¶
-
iOS & iPadOS
Home Screen web apps in depth: the Add to Home Screen flow and iOS 26's Open as Web App, manifest members and Apple meta tags, storage isolation and quotas, splash screens, safe areas, OAuth, Web Inspector, and the EU DMA episode.
-
Android
WebAPKs and how Chrome mints and updates them, Samsung Internet and Firefox for Android, link capturing through intent filters, and when to use a Trusted Web Activity.
-
Desktop Platforms
Installed PWAs on Windows, macOS, Linux and ChromeOS: app windows, OS integration, Safari's Add to Dock, Firefox's taskbar web apps, and the Microsoft Store.
-
Isolated Web Apps
Chromium's signed, offline-packaged web apps with a strict CSP, unlocking Direct Sockets and Controlled Frame for managed and allowlisted deployments.
Common pitfalls¶
- Testing only in Chrome. Chrome's DevTools make PWA development pleasant, but a PWA that only works where
beforeinstallpromptand Background Sync exist is broken for every iPhone user. - Assuming Chrome on iOS behaves like Chrome on Android. Its engine is WebKit. Its PWA features are Safari's.
- Treating the Safari macOS and Safari iOS columns as the same. Push works in macOS Safari tabs. On iOS it needs a Home Screen web app. Shortcuts work on macOS and not on iOS.
- Showing features before checking the context. A "Turn on notifications" button in an iOS Safari tab can't work. Show install instructions instead.
- Forgetting the user's second copy. Home Screen and Dock web apps have their own storage. A user who signs in inside the app may still be signed out in the browser, and the reverse.
- Relying on version numbers from memory. Check MDN or caniuse when a feature matters. The matrix on this page is a snapshot.
Further reading¶
On this site
- iOS & iPadOS: the complete guide to Home Screen web apps
- Android: WebAPKs, Samsung Internet and Firefox for Android
- Desktop Platforms: Windows, macOS, Linux and ChromeOS
- Installability Criteria: exactly what each browser checks before it offers installation
- Installation by Platform: the install flows users see
- Display Modes: how each platform presents
standalone,fullscreenandminimal-ui - Storage Quotas & Persistence: per-engine quota and eviction rules
- Web Push on iOS & Safari: Apple's push requirements in detail
External references
- MDN: Progressive web apps and the Web app manifest reference, with compatibility tables
- MDN browser-compat-data: the dataset behind the version numbers on this page
- caniuse.com: live support tables
- WebKit Features in Safari 26.0 and WebKit Features for Safari 27.0
- Firefox 143 release notes: taskbar web apps on Windows
- Using alternative browser engines in the EU (Apple) and App Review Guidelines (guideline 2.5.6)
- Apple: distributing apps in Japan: the MSCA changes in iOS 26.2
- Chrome Platform Status: ship dates and origin trials for Chromium features
-
Samsung Internet mints WebAPKs on Samsung devices and creates home screen shortcuts on other Android phones. ↩
-
Firefox 143 (September 16, 2025) added taskbar web apps on Windows, and Firefox 150 (April 21, 2026) extended them to Microsoft Store installs. The window is Firefox's own simplified window. MDN lists
displayand the other manifest members as unsupported in desktop Firefox. On Linux the feature is present but disabled by default behind thebrowser.taskbarTabs.enabledpreference. There is nothing on macOS. ↩↩ -
Firefox for Android adds a Firefox-badged launcher shortcut that opens in a standalone web-app activity. It requires a manifest with
nameorshort_name, adisplayother thanbrowser, and an icon of at least 192 px. See Installability Criteria. ↩ -
Safari 17 on macOS Sonoma and later: File > Add to Dock works for any site, with or without a manifest. ↩
-
Manual only, through the Share sheet in Safari or, since iOS 16.4, in other browsers. Since iOS and iPadOS 26, every site added this way opens as a web app unless the user turns off Open as Web App. ↩
-
MDN lists
fullscreenandminimal-uias unsupported. iOS still presents any Home Screen web app with the status bar visible and no browser controls, so afullscreenmanifest gets astandalone-like window. ↩ -
Chrome and Edge 85 to 95 supported shortcuts on Windows only. All desktop platforms from 96. ↩
-
Samsung Internet parses
screenshotsanddescriptionwith Chromium's parser but ships its own install UI. Samsung doesn't document whether it uses them, so test on a Samsung device. ↩ -
Chrome Platform Status records the ship milestone as desktop Chrome 139, after an origin trial from Chrome 122 to 127, with no Android milestone. MDN's data says 138 and also lists Chrome for Android 138 and Samsung Internet 30. Treat it as desktop-only until you have tested link handling across the extra origins on Android. See Advanced & Integration Members. ↩↩↩
-
Chromium desktop supports both the manifest
protocol_handlersmember (96) andnavigator.registerProtocolHandler(). Firefox supports onlyregisterProtocolHandler(), which registers the handler in the browser, not with the operating system. MDN lists the method for Firefox for Android too. ↩↩↩ -
Safari uses manifest icons only when there's no
apple-touch-iconlink, and only icons whosepurposeisanyor absent. ↩↩ -
Safari 16.1 on macOS 13 Ventura. MDN lists Safari 16, but Web Push requires Ventura, which shipped with 16.1. ↩
-
MDN lists 18.4 for
window.pushManageron macOS; Declarative Web Push arrived in Safari 18.5 on macOS. ↩ -
Implemented behind the
dom.push.declarative.enabledpreference, off by default. ↩ -
Only in Home Screen web apps. In a Safari tab on iPhone and iPad,
Notificationis undefined andpushManager.subscribe()can't be used. See Web Push on iOS & Safari. ↩↩ -
Chromium fires the event in one situation only: when notification permission is granted again to an origin whose subscription was dropped because permission had been revoked. The event's
oldSubscriptionandnewSubscriptionare bothnull, so resubscribe inside the handler (Chrome Platform Status). ↩↩↩ -
Firefox has fired
pushsubscriptionchangesince 44; the event'soldSubscriptionandnewSubscriptionproperties arrived in Firefox 137. ↩↩ -
Windows and macOS from 81, ChromeOS from 91. MDN: "Linux offers no universal badging API on the operating system level", so the call resolves and does nothing on Linux. ↩
-
setAppBadge()is exposed and resolves on Chrome for Android, but does nothing. Android shows a launcher dot for unread notifications instead. See Badging API. ↩ -
Periodic Background Sync fires only for installed apps, and Chromium sets the interval based on site engagement. See Periodic Background Sync. ↩↩
-
Chromium engineers posted an intent to deprecate Background Fetch in November 2025, citing very low usage. After pushback (large AI model downloads were one use case raised), the effort did not reach consensus and the API remains shipped. See Background Fetch for the status. ↩
-
Screen Wake Lock works in Safari tabs from iOS 16.4, but MDN notes it "does not work in standalone Home Screen Web Apps" before iOS 18.4. The Safari 18.4 release notes list "Fixed Wake Lock API for Home Screen Web Apps." ↩
-
WebKit grants persistence "based on heuristics like whether the website is opened as a Home Screen Web App". Home Screen web apps are also exempt from Safari's seven-day cap on script-writable storage. ↩
-
Chrome shipped Web Bluetooth on macOS in 56 and on Windows and ChromeOS in 70. Linux support isn't enabled by default. ↩
-
Desktop Chrome exposes
navigator.vibrate(), but desktop hardware rarely vibrates. Firefox removed the method from desktop in 129, and in Firefox for Android it returnstruewithout vibrating. ↩↩ -
Desktop Chrome and Edge expose
screen.orientation.lock(), but it always rejects withNotSupportedError. Firefox 144 implementedlock()andunlock()"for Android and Windows tablets" (MDN's Firefox 144 release notes); on other desktops it still rejects. Safari exposesscreen.orientation(16.4) withoutlock(). ↩↩ -
Firefox 60 shipped WebAuthn for USB security keys, and MDN's data still carries that "Only supports USB U2F tokens" note for desktop. It is out of date for passkeys: Firefox 66 added Windows Hello through the Windows WebAuthn API, and Firefox 122 added passkeys stored in iCloud Keychain on macOS. Firefox for Android has full support from 92. ↩
-
Safari's Payment Request implementation is for Apple Pay. See Payments. ↩↩