Web push notifications allow web applications to send timely messages to users even when the browser is not actively open. They are a powerful tool to re-engage users with updates, alerts, and other relevant content. In this guide, we will explore how web push notifications work and walk through the steps to implement them in a web application. We'll cover obtaining user permission, registering a service worker, subscribing to a push service, sending notifications from a server, and handling incoming push messages to display notifications to the user.
Web push notifications rely on a combination of technologies working together. The main components involved in enabling push notifications on the web are:
ServiceWorkerRegistration.showNotification(). (See the MDN Notifications API documentation for details.)When a user subscribes to web push notifications, the browser associates the service worker with a push service. Each browser has its own push service (for example, Google uses Firebase Cloud Messaging for Chrome). The push service generates a unique subscription endpoint (a URL) for the service worker. The server can later send a network request to this URL to trigger a push message. If the user's device is offline, the push service queues the message and delivers it once the device comes online.
Note: As a developer, you don't need to worry about the specifics of each browser's push service. You send your notification requests to the subscription endpoint, and the browser's push service handles routing the message to the correct user’s device.
Before we dive into implementation, it's worth noting that not all browsers supported web push notifications historically. Today, most major browsers (Chrome, Firefox, Edge, Opera, and others) support push notifications on both desktop and Android. Safari introduced support for web push on macOS and iOS in recent updates, but with some differences (e.g., using Apple's Push Notification service). Always check current browser support if targeting a broad audience.
The first step is to ask the user for permission to send notifications. Browsers will not display notifications from a web app unless the user has granted explicit permission. The Notifications API provides a method Notification.requestPermission() which triggers the browser's permission prompt. This should be called in response to a user action (e.g., a button click), so that users understand why the permission is being requested.
For example, you might have a button that says "Enable Notifications" which, when clicked, runs a function to request permission:
async function askNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
throw new Error('Notification permission denied');
}
}
In the code above, Notification.requestPermission() returns a promise that resolves to the string 'granted', 'denied', or 'default'. We proceed only if the permission is 'granted'. It's important to handle the other cases to avoid errors or unwanted behavior (for example, if permission is denied or dismissed).
Tip: Only request notification permission in context (for example, after a user clicks "Enable Notifications"). Prompting for permission immediately on page load is discouraged, as users are more likely to deny the request when they haven't interacted with the site.
The next step is to register a service worker. The service worker will run in the background and is responsible for listening to push events and displaying notifications. You can register a service worker by calling navigator.serviceWorker.register() with the script file (for example, sw.js):
async function registerServiceWorker() {
const registration = await navigator.serviceWorker.register('sw.js');
console.log('Service Worker registered:', registration);
return registration;
}
Service workers must be served from the same origin as your site, and the file is typically placed at the root (or configured with a scope) to control the relevant pages. After calling register, the browser installs the service worker in the background. The returned registration object provides a reference to the service worker registration, which we'll need for subscribing to push messages.
Now that we have permission and a service worker registered, the next step is to subscribe the service worker to the browser’s push service. This will generate a push subscription endpoint (and associated cryptographic keys) that our server can use to send push messages.
To subscribe, we use the PushManager.subscribe() method available via the service worker registration. However, to ensure compatibility with all browsers (especially Chrome), we need to pass an applicationServerKey (VAPID public key) and set userVisibleOnly: true in the options. The applicationServerKey is a public key that your server will use to authenticate itself with the push service (part of the VAPID protocol).
First, you'll need to generate a VAPID key pair (a public and private key). You can do this using the web-push command-line utility:
$ npm install -g web-push
$ web-push generate-vapid-keys
This tool will output a pair of keys. Keep the private key secret (on your server), and use the public key in your client code. For example:
// Utility to convert base64 to Uint8Array for subscription (VAPID requirement)
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
// Subscribe the service worker to Push
async function subscribeUserToPush(registration) {
const vapidPublicKey = 'PUBLIC_VAPID_KEY_FROM_SERVER';
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey
});
console.log('Obtained subscription:', JSON.stringify(subscription));
return subscription;
}
In the above code, we first define a helper urlBase64ToUint8Array to convert our Base64-encoded VAPID public key to the format required by subscribe(). Then subscribeUserToPush uses the service worker registration.pushManager.subscribe() method with userVisibleOnly: true and the applicationServerKey. The resulting subscription object contains an endpoint URL (and cryptographic keys) that uniquely identify this user's browser and can be used by your server to send push messages.
Typically, you would send this subscription object to your server (e.g., via an AJAX call to a /subscribe endpoint) so that the server can save the subscription details (usually in a database). The server needs this information to know where to send notifications for this user.
With the user's subscription info stored on the server, you can trigger push notifications by sending a message to the subscription endpoint. In practice, your server will use the subscription (which includes the endpoint URL and keys) along with your VAPID keys to send an authenticated request to the browser’s push service.
For example, using Node.js with the web-push library:
const webpush = require('web-push');
// Configure web-push with your VAPID keys
const vapidKeys = {
publicKey: 'YOUR_PUBLIC_VAPID_KEY',
privateKey: 'YOUR_PRIVATE_VAPID_KEY'
};
webpush.setVapidDetails(
'mailto:your-email@example.com',
vapidKeys.publicKey,
vapidKeys.privateKey
);
// This subscription would be retrieved from your database in a real app
const subscription = /* subscription object saved earlier */;
// Send a push notification with a payload
const payload = JSON.stringify({ title: 'Hello!', body: 'This is a test notification.' });
webpush.sendNotification(subscription, payload)
.then(() => {
console.log('Push Notification sent successfully.');
})
.catch(error => {
console.error('Error sending notification', error);
});
In the above snippet, we set our VAPID details (including a contact email and the keys). Then, using webpush.sendNotification(), we send a notification to the subscriber. The payload is optional – you can send a JSON payload with the notification, or even an empty string just to trigger the push. The push service will forward this request to the appropriate browser and, if everything is set up correctly, the service worker on the user's device will receive the push event.
Finally, we need to handle incoming push messages in the service worker. When a push message arrives, the service worker will emit a 'push' event. We can listen for this event and then display a notification to the user (often using the Notification API through the service worker's registration.showNotification method).
Open your sw.js (service worker file) and add an event listener for 'push' events. For example:
self.addEventListener('push', event => {
console.log('Push event received');
if (event.data) {
const data = event.data.json();
const title = data.title || 'New Notification';
const options = {
body: data.body || '',
// You can add other options like icon, image, vibrate, etc.
}
// Show the notification
event.waitUntil(
self.registration.showNotification(title, options)
);
} else {
console.log('Push event but no data');
}
});
In this code, when a push event is received, we extract the data (which we assume was sent as a JSON payload from the server). We then call showNotification on the service worker registration to display a notification with the given title and options. The event.waitUntil() is used to ensure the service worker stays alive long enough to display the notification.
You can also listen for notification click events in the service worker (via self.addEventListener('notificationclick', ...)) to define what happens when the user clicks the notification (such as opening a specific page on your site). This helps create a seamless experience where clicking a notification takes the user to relevant content in your web app.
In this article, we've walked through the full process of implementing web push notifications in a web application. To recap, you need to obtain user permission, register a service worker, subscribe the user to a push service (with VAPID keys for authentication), send notifications from your server to the browser's push service, and handle incoming push events in the service worker to display notifications. When implemented correctly, web push notifications can significantly boost user engagement by allowing you to reach users with timely updates, even when they're not actively browsing your site.
Keep in mind that web push should be used judiciously – always send value-driven notifications and respect user preferences. With careful implementation, push notifications can enhance the user experience and keep users coming back to your web app.