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.
OptionBenefitCost
Ask for permission contextually (after the user is already engaged)Higher grant rate, users understand the reason before decidingRequires designing the right trigger point — more UX design work
Ask for permission immediately on first loadSimplest codeVery high block rate — users deny before they have seen any value in the app
  • Asking for notification permission the instant the page loads, so most users block it before they even know what the app does.
  • Not handling a permanent “denied” state, so the code keeps trying to ask again with no way to recover.
  • Showing notifications without a proper icon/tag, so the same notification stacks up multiple times instead of replacing the previous one.

Apps that request permission only after a related action — sending a message, for instance — rather than on entry, and services that time a reminder to a relevant moment (like a check-in reminder tied to booking a ticket), both apply the same “ask at the moment of relevance” principle.

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?