Notifications · Concept
Web Push on iOS and iPadOS (16.4+)
Published Updated
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.
How it works
Section titled “How it works”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.
What a Safari tab exposes
Section titled “What a Safari tab exposes”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.
Permission, subscription, and delivery
Section titled “Permission, subscription, and delivery”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.
Declarative Web Push (18.4+)
Section titled “Declarative Web Push (18.4+)”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).
Support position
Section titled “Support position”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.
Examples
Section titled “Examples”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.
See also
Section titled “See also”- Web Push for Web Apps on iOS and iPadOS (webkit.org)
- Meet Declarative Web Push (webkit.org)
- Push API (w3.org)
- Web Push and PushManager.subscribe()
- Add to Home Screen on iOS
- Notification.requestPermission()
- Badging API
Specifications
| Specification | Status |
|---|---|
| Web Push | W3C |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | Yes | 50 | medium | source | — |
| Chrome (Desktop) | Yes | 50 | medium | source | — |
| Edge (Desktop) | Yes | 17 | medium | source | — |
| Safari (iOS) | Partial | 16.4 | medium | source | 1 |
| Safari (macOS) | Yes | 16 | medium | source | — |
| Firefox (Desktop) | Yes | 44 | medium | source | — |
| Samsung Internet | Yes | 5.0 | medium | source | — |
- Only for home-screen-installed web apps; requires user-gesture permission and Web Push via APNs.
Ecosystem & commercial policy
| Entity | Type | Context | Status | Sponsored | Notes |
|---|---|---|---|---|---|
| Apple Push (APNs) | delivery_policy | iOS | Partial | No | iOS Web Push requires the user to add the app to the Home Screen first. |
Try it
Run this capability in the OpenPWA demo app: /demo/#push