The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Register a service worker | const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' }) | View examples |
| Wait for an active worker | const registration = await navigator.serviceWorker.ready | View examples |
| Request notification permission | const permission = await Notification.requestPermission() | View examples |
| Read current permission | if (Notification.permission === 'granted') enablePush() | View examples |
| Create a push subscription | await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }) | View examples |
| Reuse a subscription | const subscription = await registration.pushManager.getSubscription() | View examples |
| Unsubscribe locally | await subscription.unsubscribe() | View examples |
| Extend a push event | event.waitUntil(handlePush(event)) | View examples |
| Show a notification | await self.registration.showNotification(title, options) | View examples |
| Replace related notifications | { tag: 'inbox', renotify: false } | View examples |
| Handle a notification click | self.addEventListener('notificationclick', event => event.waitUntil(openApp(event))) | View examples |
| Find application windows | const windows = await clients.matchAll({ type: 'window', includeUncontrolled: true }) | View examples |
| Set an application badge | await navigator.setAppBadge(unreadCount) | View examples |
| Clear the application badge | await navigator.clearAppBadge() | View examples |
Web push delivers server-originated messages to a service worker, which can display notifications while no page is open. The browser push service, application server, service worker, Notifications API, and Badging API have distinct responsibilities. Ask permission only after informed user intent, store subscriptions like credentials, encrypt payloads using established libraries, and make every event handler finish through waitUntil().
Step by step
Detailed examples
Install one service worker before enabling push
PushManager belongs to a ServiceWorkerRegistration and secure contexts are required. Register a stable, same-origin worker URL with the narrowest useful scope, then await navigator.serviceWorker.ready before subscribing. Worker upgrades are asynchronous and old clients can coexist with new code, so version messages and storage formats compatibly. Push is not a guaranteed or real-time transport; the user agent or push service may delay, coalesce, expire, or discard messages.
async function preparePush() {
if (!isSecureContext || !('serviceWorker' in navigator) || !('PushManager' in window)) return null;
await navigator.serviceWorker.register('/sw.js', { scope: '/' });
return navigator.serviceWorker.ready;
} Ask only after explaining value and user control
Notification permission has granted, denied, and default states; default must be treated as not granted. Browsers may require transient user activation and may suppress abusive prompts. Explain the notification category and frequency in application UI, then call requestPermission() from the user's action. Never repeatedly prompt after denial, never gate unrelated functionality, and provide settings to disable categories and unsubscribe. Permission can later be revoked outside the page.
enableButton.addEventListener('click', async () => {
if (!('Notification' in window)) return showUnsupported();
const permission = Notification.permission === 'default'
? await Notification.requestPermission()
: Notification.permission;
if (permission !== 'granted') return showPushDisabled();
await enablePushSubscription();
}); Bind subscriptions to accounts and protect endpoints
subscribe() normally uses userVisibleOnly true and an applicationServerKey containing the VAPID P-256 public key bytes, not its base64url text. A PushSubscription JSON representation contains a capability URL plus encryption material; anyone holding the endpoint may consume quota or attempt delivery, so transmit it over authenticated HTTPS, apply CSRF protection, bind it to the correct account and device, and never log it. On logout, delete the server binding and unsubscribe where product policy requires it.
async function enablePushSubscription() {
const registration = await navigator.serviceWorker.ready;
const existing = await registration.pushManager.getSubscription();
const subscription = existing ?? await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64urlToBytes(VAPID_PUBLIC_KEY),
});
const response = await fetch('/api/push/subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) });
if (!response.ok) throw new Error('Subscription registration failed');
} Validate payloads and show useful persistent notifications
A push event may have no data. Parse untrusted payloads defensively, enforce a version and size limit, and avoid placing secrets in notification text or URLs. Use a maintained Web Push library on the server to implement payload encryption and VAPID authorization; Web Crypto calls alone do not implement the protocol. waitUntil() must cover parsing, data refresh, and showNotification(). In normal userVisibleOnly deployments, silent background work is not a substitute for a user-visible notification.
self.addEventListener('push', event => {
event.waitUntil((async () => {
let message = { title: 'Updates available', body: 'Open the app to refresh.' };
try {
const value = event.data?.json();
if (value?.version === 1 && typeof value.title === 'string') message = value;
} catch {}
await self.registration.showNotification(message.title, { body: message.body, tag: message.tag, data: { path: safePath(message.path) } });
})());
}); Route clicks to same-origin destinations
notificationclick runs in the service worker. Close the notification when appropriate, validate action identifiers and stored routes against an allowlist, then focus a suitable existing same-origin WindowClient or open a new one. clients.openWindow() may be restricted to user activation supplied by the click. Do not accept an arbitrary payload URL, since that can create an open redirect or phishing surface. Handle notificationclose separately if the product truly needs coarse dismissal analytics.
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil((async () => {
const path = safePath(event.notification.data?.path) || '/inbox';
const windows = await clients.matchAll({ type: 'window', includeUncontrolled: true });
const existing = windows.find(client => new URL(client.url).origin === self.location.origin);
if (existing) { await existing.navigate(path); return existing.focus(); }
return clients.openWindow(path);
})());
}); Use badges as optional, reconstructible hints
The Badging API sets a number or flag associated with an installed app on supporting operating systems. Window and service-worker globals expose different navigator objects but can both provide setAppBadge and clearAppBadge. Support, installation requirements, display, limits, and permission behavior vary; feature-detect and never make the badge the sole unread indicator. Keep the server or durable app state authoritative because a badge can outlive a page and can be cleared by the platform.
async function updateBadge(count) {
if (!('setAppBadge' in navigator)) return;
try {
if (count > 0) await navigator.setAppBadge(Math.floor(count));
else await navigator.clearAppBadge();
} catch (error) {
console.debug('Badge unavailable', error.name);
}
} Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- World Wide Web ConsortiumPush APIw3.org
- World Wide Web ConsortiumNotifications API Standardnotifications.spec.whatwg.org
- World Wide Web ConsortiumService Workers Nightlyw3.org
- World Wide Web ConsortiumBadging APIw3.org
- Internet Engineering Task ForceGeneric Event Delivery Using HTTP Pushrfc-editor.org
- Internet Engineering Task ForceVoluntary Application Server Identification for Web Pushrfc-editor.org
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



