The Notifications API
The Notifications API
Section titled “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.
Requesting permission
Section titled “Requesting permission”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.
Showing a notification from the page
Section titled “Showing a notification from the page”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 codeasync 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' },});Notification options
Section titled “Notification options”| Option | Type | Purpose |
|---|---|---|
body | string | Secondary text below the title |
icon | string (URL) | Large image beside the notification |
badge | string (URL) | Small monochrome icon for the status bar |
image | string (URL) | Large inline image |
actions | array | Buttons the user can tap without opening the app |
data | any | Arbitrary payload passed to the notificationclick handler |
tag | string | Replaces any existing notification with the same tag |
renotify | boolean | Re-alerts the user even when replacing a tagged notification |
requireInteraction | boolean | Keeps the notification visible until the user interacts |
silent | boolean | Suppresses sound and vibration |
Handling notificationclick
Section titled “Handling notificationclick”The notificationclick event fires in the service worker when the user taps the notification or one of its action buttons:
// sw.js — service workerself.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.
Try it live
Section titled “Try it live”The demo below shows a local notification from the service worker. Grant permission when prompted, then click “Notify me”.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Ask for permission contextually (after the user is already engaged) | Higher grant rate, users understand the reason before deciding | Requires designing the right trigger point — more UX design work |
| Ask for permission immediately on first load | Simplest code | Very high block rate — users deny before they have seen any value in the app |
Common mistakes
Section titled “Common mistakes”- 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.