Skip to content

The Notifications API

The Notifications API lets your PWA display system-level notifications. Unlike an alert box, a notification appears in the device’s notification tray and is visible even if the user has switched tabs.

Before showing any notification you must request permission. The call returns a Promise that resolves to "granted", "denied", or "default":

async function askPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Notifications allowed');
} else {
console.warn('Notifications blocked or dismissed');
}
}

Call this function in response to a user gesture — a button click, a form submit, a swipe. Never call it on page load.

Once permission is granted you can create a notification directly from page JavaScript:

new Notification('Your order shipped!', {
body: 'It will arrive by Thursday.',
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
});

This works, but the notification will not appear if no tab is open. The better approach is to show notifications from the service worker.

Showing a notification from the service worker

Section titled “Showing a notification from the service worker”

Use registration.showNotification() on the ServiceWorkerRegistration object. The SW keeps running even when all tabs are closed, so this notification fires reliably:

// main.js — page code
async function notify(title, options) {
const registration = await navigator.serviceWorker.ready;
registration.showNotification(title, options);
}
notify('Your order shipped!', {
body: 'It will arrive by Thursday.',
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
actions: [
{ action: 'view', title: 'View order' },
{ action: 'dismiss', title: 'Dismiss' },
],
data: { orderId: 'ORD-42' },
});
OptionTypePurpose
bodystringSecondary text below the title
iconstring (URL)Large image beside the notification
badgestring (URL)Small monochrome icon for the status bar
imagestring (URL)Large inline image
actionsarrayButtons the user can tap without opening the app
dataanyArbitrary payload passed to the notificationclick handler
tagstringReplaces any existing notification with the same tag
renotifybooleanRe-alerts the user even when replacing a tagged notification
requireInteractionbooleanKeeps the notification visible until the user interacts
silentbooleanSuppresses sound and vibration

The notificationclick event fires in the service worker when the user taps the notification or one of its action buttons:

// sw.js — service worker
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'view') {
// Open or focus the app
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
if (clientList.length > 0) {
return clientList[0].focus();
}
return clients.openWindow('/orders/' + event.notification.data.orderId);
})
);
}
});

Always call event.notification.close() — on Android, notifications stay open until explicitly closed.

The demo below shows a local notification from the service worker. Grant permission when prompted, then click “Notify me”.

Runs a real service worker + manifest in your browser.
What does Notification.requestPermission() return?
Why is registration.showNotification() preferred over new Notification()?
Where does the notificationclick event fire?
Which notification option replaces an existing notification with the same identifier?