Web Push Browser Compatibility Reference
This reference maps Web Push support across Chromium (Chrome, Edge), Gecko (Firefox), and WebKit (Safari) so you can decide which features are safe to ship and which need feature detection. It covers minimum versions, per-feature support, the aes128gcm payload contract, and the vendor caveats that break delivery in production.
Web Push is a standard, but a fragmented one. The transport (RFC 8030), encryption (RFC 8291), and VAPID authentication (RFC 8292) are stable, yet each vendor layers its own permission model, payload validation, and UI rendering on top. The matrices below treat the spec as the baseline and call out where each engine diverges.
How Web Push support breaks down by vendor
Three independent push services back the four major browsers. There is no shared infrastructure: a subscription endpoint is opaque, vendor-specific, and bound to both the browser and the applicationServerKey it was created with.
- Chrome and Edge (Chromium) route through Firebase Cloud Messaging (FCM). They share the same engine, so feature parity is near-total. Edge inherits Chromium behavior including the 4 KB ciphertext ceiling.
- Firefox (Gecko) uses Mozilla’s autopush service, entirely independent of FCM. Its payload validation is stricter and its feature timeline differs from Chromium’s.
- Safari (WebKit) routes through Apple Push Notification service (APNs). It is the most constrained: Web Push arrived only in Safari 16 on macOS Ventura and Safari 16.4 on iOS/iPadOS, and on iOS it requires the site to be installed to the Home Screen as a PWA.
Because endpoints are opaque, your server treats all three identically at send time — the same signed request with aes128gcm encryption — and the vendor differences surface as different status codes, size limits, and rendering quirks. The cross-browser notification quirks reference covers the runtime rendering divergences; this page focuses on the version and feature matrix you need before writing a line of code.
Version support matrix
The minimum versions below are the first stable releases where the Push API, Notifications API, and service worker push delivery worked together end to end. Modern evergreen browsers are far past these, but the iOS constraints remain the dominant gotcha.
| Browser / Engine | Push API support since | Push service | Platform constraints |
|---|---|---|---|
| Chrome (Chromium) | Chrome 42 (2015), VAPID since Chrome 52 | FCM | Desktop + Android; no iOS engine (uses WebKit there) |
| Edge (Chromium) | Edge 17 (2018, Chromium-based) | FCM | Desktop + Android; matches Chrome behavior |
| Firefox (Gecko) | Firefox 44 (2016) | Mozilla autopush | Desktop + Android; not available on iOS |
| Safari (WebKit) — macOS | Safari 16 / macOS Ventura 13.0 (2022) | APNs | macOS only; standard Web Push API |
| Safari (WebKit) — iOS/iPadOS | Safari 16.4 / iOS 16.4 (2023) | APNs | Requires install to Home Screen (PWA); no push from a normal tab |
| Opera / Samsung Internet | Tracks Chromium baseline | FCM | Inherit Chromium feature set and limits |
On iOS, a regular Safari tab cannot subscribe at all. navigator.serviceWorker exists, but Notification.requestPermission() and pushManager.subscribe() only function once the user adds the site to the Home Screen and launches it in standalone mode. Detecting window.navigator.standalone (or the display-mode: standalone media query) before offering a prompt avoids a guaranteed failure. This interacts directly with service worker registration patterns, since the worker must already be registered inside the installed PWA context.
Feature support matrix
The transport and encryption are uniform, but the Notification rendering options diverge sharply. Use this table to decide what to send and what to feature-detect at display time inside the service worker.
| Feature | Chrome / Edge | Firefox | Safari (macOS) | Safari (iOS PWA) |
|---|---|---|---|---|
| Push API + service worker push | Yes | Yes | Yes | Yes (installed PWA only) |
| VAPID (RFC 8292) auth | Yes | Yes | Yes | Yes |
aes128gcm content-encoding (RFC 8291) |
Yes | Yes | Yes | Yes |
| Max encrypted payload | ~4 KB (4096 B) | ~4 KB (stricter validation) | ~4 KB | ~4 KB |
Notification actions (buttons) |
Yes | Firefox 152+ (2025) | No | No |
image (large hero image) |
Yes | No | No | No |
badge (monochrome status icon) |
Yes (Android) | Partial | No | App icon badge only |
requireInteraction |
Yes | Yes | Ignored (system-managed) | Ignored |
silent notification |
Yes (budget-limited) | Yes | No | No |
Declarative Web Push (Notification from header) |
Experimental / origin-trial era | No | Safari 18.x adopting | Safari 18.x adopting |
Silent push (userVisibleOnly: false) |
Restricted (push budget) | Restricted | No | No |
Two rows deserve emphasis. First, actions are not portable: Safari ignores them entirely and Firefox only added them in version 152, so any flow that depends on a button must degrade to a plain body and a notificationclick default action. Second, silent push is effectively unavailable in the cross-browser sense — every vendor expects a user-visible notification per push, and Chromium enforces this through a push budget that can suppress notifications if you abuse userVisibleOnly: false.
Declarative Web Push
Declarative Web Push lets the push service render a notification from a JSON Notification field in the message without waking your service worker’s push handler. WebKit has driven this proposal and Safari 18.x began adopting it; Chromium experimented with it during an origin-trial period. It is not yet a safe baseline — always ship a push event handler that calls showNotification() as the portable path, and treat declarative rendering as progressive enhancement.
Per-vendor caveats
Chrome and Edge (Chromium / FCM)
Chromium is the most permissive engine but enforces a 4 KB ciphertext limit and a notification-display budget. If you repeatedly send pushes without showing a user-visible notification, the browser revokes your ability to send silent pushes for that origin. Subscriptions can also be revoked when the user clears site data, returning 410 Gone on the next send. Edge behaves identically because it is Chromium under the hood.
Firefox (Gecko / Mozilla autopush)
Firefox validates payloads more strictly than Chromium and historically rejected malformed aes128gcm records that Chrome tolerated. It added notification actions in Firefox 152 (2025); earlier versions silently drop the actions array rather than erroring. Firefox does not render the large image option, so a media-rich design must fall back to icon only.
Safari and WebKit (APNs)
Safari is the strictest target. On iOS the site must be installed as a PWA before any push subscription is possible, and the permission prompt requires a genuine user gesture. Safari ignores requireInteraction, silent, image, and actions, and the system fully manages dismissal. Because the relay is APNs, the same opaque-endpoint model applies, but Apple historically enforced shorter title rendering and tighter UI than Chromium. Always test on a physical device — the iOS Simulator does not deliver real pushes.
A subtle Safari trait is that the standards-based Web Push API on macOS Ventura and iOS 16.4+ is distinct from the older Safari Push Notifications mechanism that used Apple Developer certificates and a pushPackage. The modern path uses VAPID and standard pushManager.subscribe() exactly like Chromium and Firefox, so you do not need an Apple Developer account for it. If you find older documentation referencing certificate-based safari.pushNotification, it predates the standards alignment and does not apply to the Web Push covered in this matrix.
Reading status codes per vendor
Because the three push services validate independently, the same client bug surfaces as different HTTP responses. A 410 Gone means the subscription is dead and should be deleted regardless of vendor. A 404 Not Found is also a permanently invalid endpoint. A 413 Payload Too Large means you exceeded the 4 KB ciphertext ceiling — Firefox tends to return this sooner than Chromium because of its stricter validation. A 429 Too Many Requests is rate limiting from the push service, and a 403 Forbidden almost always means a VAPID signature mismatch — typically a key that no longer matches the subscription’s applicationServerKey. Treating these uniformly in your sender, with 410/404 triggering subscription cleanup, keeps your subscriber list healthy across all three vendors.
Feature detection and verification
Never assume support from a user-agent string alone for capability gating; detect the actual APIs and, for display-time options, probe what the Notification surface accepts. The snippet below gates subscription on real support, including the iOS standalone requirement.
// Client-side capability gate before offering a push opt-in
function getPushSupport() {
const hasSW = 'serviceWorker' in navigator;
const hasPush = 'PushManager' in window;
const hasNotification = 'Notification' in window;
// iOS only allows push from an installed (standalone) PWA
const isIOS = /iP(hone|ad|od)/.test(navigator.platform) ||
(navigator.userAgent.includes('Mac') && 'ontouchend' in document);
const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true;
return {
supported: hasSW && hasPush && hasNotification && (!isIOS || isStandalone),
needsInstall: isIOS && !isStandalone,
};
}
// Detect which NotificationOptions the current engine honors
function supportsActions() {
try {
// maxActions is 0 on engines without action-button support
return 'maxActions' in Notification && Notification.maxActions > 0;
} catch {
return false;
}
}
const support = getPushSupport();
if (support.needsInstall) {
// Prompt the user to "Add to Home Screen" instead of requesting permission
} else if (support.supported) {
// Safe to call Notification.requestPermission() on a user gesture
}
On the server, the request is identical across vendors: a VAPID-signed aes128gcm payload under 4 KB. Always source the keys from the environment — never hardcode the VAPID public key server-side. See VAPID key generation and rotation for the full key lifecycle.
// Server-side: one signed request works for all three push services
const webpush = require('web-push');
webpush.setVapidDetails(
'mailto:ops@yourdomain.com',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
);
async function send(subscription, payload) {
const body = JSON.stringify(payload);
if (Buffer.byteLength(body) > 3500) {
// Leave headroom under the 4 KB ciphertext ceiling after encryption overhead
throw new Error('Payload too large; send an identifier and fetch the rest client-side');
}
// web-push performs ECDH + HKDF + aes128gcm encryption per RFC 8291
return webpush.sendNotification(subscription, body, { TTL: 86400 });
}
The 4 KB figure is the encrypted ciphertext limit, not your JSON size — RFC 8291 encryption adds overhead, so budgeting roughly 3.5 KB of plaintext is the safe rule. The mechanics of that encoding and how to debug it live in the Push API payload encryption reference.
A portable design baseline
Given the matrix above, a payload and handler design that works everywhere narrows to a small, conservative core. Send a title and body, a single same-origin icon at 192×192, an optional badge for Android, and a tag for coalescing. Treat actions, image, requireInteraction, and silent as enhancements applied only when feature detection confirms support, and never depend on silent push or declarative push as a baseline. On the client, gate the opt-in behind real capability detection and, on iOS, behind standalone-mode detection. On the server, enforce the 4 KB ceiling at send time, source VAPID keys from the environment, and treat 410/404 as cleanup signals. This baseline is dull by design — it is the intersection of what Chrome, Edge, Firefox, and Safari all honor, and it is what keeps a single send pipeline reaching every supported browser without per-vendor branching at the transport layer.
FAQ
Does Web Push work in Safari on iPhone?
Yes, from iOS 16.4 onward, but only after the user adds the site to the Home Screen and opens it as an installed PWA. A regular Safari tab on iOS cannot subscribe to push. Detect standalone mode before offering an opt-in.
Is the 4 KB payload limit the same in every browser?
The ~4 KB ceiling applies to the encrypted aes128gcm ciphertext and is effectively uniform across Chrome, Edge, Firefox, and Safari, though Firefox validates more strictly. Because encryption adds overhead, keep plaintext JSON under roughly 3.5 KB.
Do notification action buttons work cross-browser?
No. Chromium has supported actions for years, Firefox added them in version 152 (2025), and Safari ignores them entirely. Always provide a default notificationclick action and treat buttons as progressive enhancement.
Can I send a silent push that shows no notification?
Not reliably. Chromium permits userVisibleOnly: false only within a push budget and will suppress your pushes if you abuse it; Firefox restricts it; Safari does not support it. Design for a visible notification on every push.
Does VAPID work the same way in all browsers?
Yes. VAPID (RFC 8292) authentication is supported by Chrome, Edge, Firefox, and Safari, and a single signed request reaches all three push services. The applicationServerKey used at subscribe time is bound to the resulting subscription across every vendor.
Related
- Push API Payload Encryption — the
aes128gcm/RFC 8291 encoding behind the 4 KB ciphertext limit in this matrix. - VAPID Key Generation & Rotation — how the VAPID auth supported by every vendor is generated and rotated.
- Service Worker Registration Patterns — registering the worker that makes push delivery possible, including the iOS PWA case.
- Cross-Browser Notification Quirks — the runtime rendering and lifecycle divergences behind the feature matrix.