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”.