Skip to content

Add push notifications

Published

Outcome: your server can reach the user with a message even when the app is not in the foreground. Per MDN, the Push API gives web applications the ability to receive messages pushed to them from a server, whether or not the web app is in the foreground — or even currently loaded — and per MDN the service worker is started as necessary to handle an incoming push message.

Two separate APIs are involved, and they are easy to confuse. Push is the transport: per MDN, it delivers the message to the worker’s push event. Notifications are the display: per MDN, the worker reacts to the message by, for example, displaying a notification with ServiceWorkerRegistration.showNotification().

Per MDN, the Push API is Baseline “widely available” — well established across many devices and browser versions, and available across browsers since March 2023. Per MDN, PushManager.subscribe() is available only in secure contexts (HTTPS) in some or all supporting browsers. Notification.requestPermission() is the weaker link: per MDN, that static method is not Baseline, because it does not work in some of the most widely used browsers.

  1. Have an active service worker. Per MDN, for an app to receive push messages it has to have an active service worker; once it is active, it can subscribe using PushManager.subscribe(). If you do not have one yet, start with Getting started.

  2. Ask for permission from a user gesture. Per MDN, Notification.requestPermission() returns a promise that fulfills with granted, denied, or default, and the request should be made in response to user interaction. Per MDN, when the value is default the user’s decision is unknown and the application will act as if permission was denied.

  3. Subscribe, also from a user gesture. Per MDN, subscribe() calls should be done in response to a user gesture such as clicking a button — not only as best practice, but because browsers are moving to disallow notifications not triggered by a user gesture, and MDN names Firefox as already doing this from version 72.

  4. Send the subscription to your server. Per MDN, the resulting PushSubscription includes everything the application needs to send a push message: an endpoint and the encryption key needed for sending data.

  5. Handle push in the worker and show something. Per MDN, incoming push messages are delivered to the push event handler, which can react by displaying a notification using ServiceWorkerRegistration.showNotification().

Per MDN, ServiceWorkerRegistration.pushManager is the entry point into using push messaging and returns a reference to the PushManager interface — so test for that interface before offering the subscribe UI at all:

async function subscribeToPush(applicationServerKey) {
if (!('PushManager' in window)) {
// No push support: keep the subscribe UI hidden and fall back to whatever
// in-app channel you already have (an inbox view, an email digest).
return null;
}
const permission = await Notification.requestPermission();
if (permission !== 'granted') return null;
const registration = await navigator.serviceWorker.ready;
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey,
});
}

Per MDN, applicationServerKey is a Base64-encoded string or ArrayBuffer containing an ECDSA P-256 public key that the push server uses to authenticate your application server; if specified, all messages from your application server must use the VAPID authentication scheme and include a JWT signed with the corresponding private key. Per MDN, that key is not the same ECDH key used to encrypt the data.

In the worker, turn the message into a notification:

self.addEventListener('push', (event) => {
const body = event.data ? event.data.text() : 'You have a new message.';
event.waitUntil(self.registration.showNotification('New message', { body }));
});
  • userVisibleOnly: true is effectively mandatory. Per MDN, applicationServerKey is required in some browsers such as Chrome and Edge, and they will reject the promise if userVisibleOnly is not set to true. Plan for a visible notification per push rather than silent background delivery.
  • Treat the endpoint as a secret. Per MDN, the subscription endpoint is a unique capability URL: knowledge of the endpoint is all that is necessary to send a message to your application, so it needs to be kept secret or other applications could push to your users.
  • Guard against CSRF when subscribing. MDN warns explicitly that when implementing PushManager subscriptions it is vitally important to protect against CSRF/XSRF issues in your app.
  • Delivery is not unlimited everywhere. Per MDN, waking a service worker to deliver a push message increases resource usage, particularly battery, and there is currently no standard mechanism for handling this: Firefox allows a limited quota of push messages per application (refreshed each time the site is visited, with messages that generate notifications exempt), while Chrome has no limits.
  • Subscriptions expire and change. Per MDN, the pushsubscriptionchange event fires when a push subscription has been invalidated or is about to be — for example when the push service sets an expiration time — so treat a stored subscription as something that can stop being valid, and re-subscribe when it does.
  • Each subscription belongs to one worker. Per MDN, each subscription is unique to a service worker.
  • A denial is explicit. Per MDN, denied means the user has explicitly denied permission for the current origin to display system notifications, and MDN’s own example notes that in that case you should be respectful and not bother the user any more.

← Back to the Guides overview.