Skip to content

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 (Notification on 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

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 beforeinstallprompt and 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:

  1. 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.
  2. WebKit's PWA features depend on context. Push, the Notification interface 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.enabled preference, 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 honor display, shortcuts or 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.

platform-caps.js
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:

app.js
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:

  1. 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.
  2. A Pixel or other Android device with Chrome, installed as a WebAPK. Use chrome://inspect from desktop Chrome and about://webapks on the device.
  3. A Samsung device with Samsung Internet if your analytics show meaningful Samsung Internet traffic.
  4. Chrome or Edge on Windows and macOS, installed, with the app window, badges and file or protocol handling you use.
  5. 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.

    iOS & iPadOS

  • 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.

    Android

  • 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.

    Desktop Platforms

  • 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.

    Isolated Web Apps

Common pitfalls

  • Testing only in Chrome. Chrome's DevTools make PWA development pleasant, but a PWA that only works where beforeinstallprompt and 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

External references


  1. Samsung Internet mints WebAPKs on Samsung devices and creates home screen shortcuts on other Android phones. ↩

  2. 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 display and the other manifest members as unsupported in desktop Firefox. On Linux the feature is present but disabled by default behind the browser.taskbarTabs.enabled preference. There is nothing on macOS. ↩↩

  3. Firefox for Android adds a Firefox-badged launcher shortcut that opens in a standalone web-app activity. It requires a manifest with name or short_name, a display other than browser, and an icon of at least 192 px. See Installability Criteria. ↩

  4. Safari 17 on macOS Sonoma and later: File > Add to Dock works for any site, with or without a manifest. ↩

  5. 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. ↩

  6. MDN lists fullscreen and minimal-ui as unsupported. iOS still presents any Home Screen web app with the status bar visible and no browser controls, so a fullscreen manifest gets a standalone-like window. ↩

  7. Chrome and Edge 85 to 95 supported shortcuts on Windows only. All desktop platforms from 96. ↩

  8. Samsung Internet parses screenshots and description with Chromium's parser but ships its own install UI. Samsung doesn't document whether it uses them, so test on a Samsung device. ↩

  9. 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. ↩↩↩

  10. Chromium desktop supports both the manifest protocol_handlers member (96) and navigator.registerProtocolHandler(). Firefox supports only registerProtocolHandler(), which registers the handler in the browser, not with the operating system. MDN lists the method for Firefox for Android too. ↩↩↩

  11. Safari uses manifest icons only when there's no apple-touch-icon link, and only icons whose purpose is any or absent. ↩↩

  12. Safari 16.1 on macOS 13 Ventura. MDN lists Safari 16, but Web Push requires Ventura, which shipped with 16.1. ↩

  13. MDN lists 18.4 for window.pushManager on macOS; Declarative Web Push arrived in Safari 18.5 on macOS. ↩

  14. Implemented behind the dom.push.declarative.enabled preference, off by default. ↩

  15. Only in Home Screen web apps. In a Safari tab on iPhone and iPad, Notification is undefined and pushManager.subscribe() can't be used. See Web Push on iOS & Safari. ↩↩

  16. 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 oldSubscription and newSubscription are both null, so resubscribe inside the handler (Chrome Platform Status). ↩↩↩

  17. Firefox has fired pushsubscriptionchange since 44; the event's oldSubscription and newSubscription properties arrived in Firefox 137. ↩↩

  18. 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. ↩

  19. setAppBadge() is exposed and resolves on Chrome for Android, but does nothing. Android shows a launcher dot for unread notifications instead. See Badging API. ↩

  20. Periodic Background Sync fires only for installed apps, and Chromium sets the interval based on site engagement. See Periodic Background Sync. ↩↩

  21. 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. ↩

  22. Windows and ChromeOS from 89, macOS from Chrome 128. Chromium has no Web Share implementation on Linux. Edge supported Windows from 81 and has full support from 93. ↩

  23. 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." ↩

  24. 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. ↩

  25. Chrome shipped Web Bluetooth on macOS in 56 and on Windows and ChromeOS in 70. Linux support isn't enabled by default. ↩

  26. Desktop Chrome exposes navigator.vibrate(), but desktop hardware rarely vibrates. Firefox removed the method from desktop in 129, and in Firefox for Android it returns true without vibrating. ↩↩

  27. Desktop Chrome and Edge expose screen.orientation.lock(), but it always rejects with NotSupportedError. Firefox 144 implemented lock() and unlock() "for Android and Windows tablets" (MDN's Firefox 144 release notes); on other desktops it still rejects. Safari exposes screen.orientation (16.4) without lock(). ↩↩

  28. 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. ↩

  29. Safari's Payment Request implementation is for Apple Pay. See Payments. ↩↩