Skip to content

Notifications · Concept

Web Push on iOS and iPadOS (16.4+)

Published Updated

Limited availabilityNot supported in Safari (iOS)W3C

iOS and iPadOS 16.4 (2023-03) added Web Push for web apps added to the Home Screen: the same Push API, Notifications API, and service worker combination as desktop, delivered through the Apple Push Notification service, and available only after the user installs the app and taps a control that requests permission. In a Safari tab on iPhone the Notification interface does not exist, so an uninstalled site cannot even ask.

WebKit gates the whole feature on installation. A site becomes a Home Screen web app when the user chooses “Add to Home Screen” from the Share menu and the manifest’s display member is standalone or fullscreen (or minimal-ui; any non-default value); without that manifest value the icon is a bookmark that opens in Safari, where push is unavailable (Web Push for Web Apps on iOS and iPadOS, webkit.org). From 16.4 third-party browsers can offer the same Share-menu item, and the resulting app opens as a web app regardless of which browser added it.

Browser-compat-data records the iOS entry for Notification as 16.4, partial, with two notes: the interface is undefined unless the page is a web app saved to the Home Screen with a non-default manifest display, and a notification can only be sent from a service worker (api.Notification, safari_ios). Both have code consequences. Notification.requestPermission() in a tab throws ReferenceError, not a rejected promise, and new Notification() has no supported path on the platform at all; registration.showNotification() is the only way to display one.

Inside the installed app, the permission request must follow direct user interaction, such as a tap on a subscribe button; iOS then shows the system notification prompt, and the user manages the result per web app under Settings > Notifications like any native app. pushManager.subscribe() requires userVisibleOnly: true and a VAPID applicationServerKey; the returned endpoint is on a *.push.apple.com host, which servers that allow-list push endpoints must permit. WebKit revokes a subscription whose push events do not result in a visible notification (Meet Web Push, webkit.org), so a handler that swallows a message, or fails before showNotification(), costs the subscription rather than one alert.

Notifications integrate with Focus, and Focus settings sync across devices for web apps whose manifest id and user-chosen name match. The Badging API (navigator.setAppBadge()) shipped in the same release for Home Screen web apps, with the badge shown once notification permission is granted.

iOS and iPadOS 18.4 added a second flavour for Home Screen web apps, and Safari 18.5 brought it to macOS: window.pushManager (BCD api.Window.pushManager: Safari 18.4) lets a page subscribe without a service worker, and a push message in the declarative JSON format ("web_push": 8030 plus a notification object) is displayed by the browser with no JavaScript. A service worker, if present, may still modify the notification; if its handler fails, the declarative message is shown as the fallback, so the revocation rule above does not bite on this path (Meet Declarative Web Push, webkit.org).

Safari 16 on macOS Ventura shipped Web Push first (2022-10), with delivery through the webpushd daemon so that Safari need not be running. iOS and iPadOS 16.4 followed for Home Screen web apps only, and the compat table on this page tracks both. Android WebView and iOS WebViews inside other apps do not get push; a Chromium-based browser on Android gets it in any tab.

The examples run inside the installed app on iOS and in any tab elsewhere; the first one is the guard that tells the two apart.

Routing an uninstalled iPhone visitor to install guidance

Section titled “Routing an uninstalled iPhone visitor to install guidance”

The 'Notification' in window test is the install check on iOS: it is false in a tab and true in the Home Screen app. The same test is also the right first step everywhere else, so no platform sniffing is needed.

async function enablePush(registration, applicationServerKey) {
if (!('Notification' in window)) {
showAddToHomeScreenGuidance(); // iOS tab or embedded web view: install first
return null;
}
const permission = await Notification.requestPermission();
if (permission !== 'granted') return null;
return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey });
}

Call enablePush() from a tap handler; on iOS a call outside direct user interaction resolves "denied" without a prompt.

Keeping the userVisibleOnly promise in the push handler

Section titled “Keeping the userVisibleOnly promise in the push handler”

Every push event must end in showNotification(), including the error path, or WebKit revokes the subscription. A generic fallback notification is safer than none.

self.addEventListener('push', (event) => {
event.waitUntil((async () => {
let payload = { title: 'Update available', body: '' };
try {
payload = event.data.json();
} catch {
// malformed payload: still show something, or the subscription is revoked
}
await self.registration.showNotification(payload.title, { body: payload.body });
})());
});

The try/catch matters on iOS more than elsewhere: a server bug that sends a malformed body would otherwise silently unsubscribe every iPhone user.

Preferring the declarative manager when present

Section titled “Preferring the declarative manager when present”

Probe for the manager you will use rather than for the PushManager interface. window.pushManager exists only on the declarative path; a service worker registration’s pushManager requires an active worker.

async function getPushManager() {
if ('pushManager' in window) return window.pushManager; // Declarative Web Push, Safari 18.4+
if (!('serviceWorker' in navigator)) return null; // no push at all: in-app messaging only
const registration = await navigator.serviceWorker.getRegistration();
return registration?.active ? registration.pushManager : null;
}

getRegistration() settles with undefined when nothing is registered; navigator.serviceWorker.ready would wait forever in that case, which is why the probe avoids it.

Specifications

SpecificationStatus
Web PushW3C
  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Android)Yes50mediumsource—
Chrome (Desktop)Yes50mediumsource—
Edge (Desktop)Yes17mediumsource—
Safari (iOS)Partial16.4mediumsource1
Safari (macOS)Yes16mediumsource—
Firefox (Desktop)Yes44mediumsource—
Samsung InternetYes5.0mediumsource—
  1. Only for home-screen-installed web apps; requires user-gesture permission and Web Push via APNs.

Ecosystem & commercial policy

EntityTypeContextStatusSponsoredNotes
Apple Push (APNs)delivery_policyiOSPartialNoiOS Web Push requires the user to add the app to the Home Screen first.

Source data: /compatibility/web-push.json · Global usage: 89 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-06-24 · Confidence: medium (computed from sources)