Notifications API
The Notifications API allows web applications to display system-level notifications to the user, even when the tab is not focused. Notifications can include a title, body text, icon, image, and action buttons. They are displayed using the operating system's native notification mechanism.
Before showing a notification, you must request the user's permission. The permission can be "granted", "denied", or "default" (not yet asked). Once denied, you cannot request permission again — the user must manually enable it in browser settings.
| 1 | // Basic notification |
| 2 | if ("Notification" in window) { |
| 3 | // Request permission first |
| 4 | const permission = await Notification.requestPermission(); |
| 5 | |
| 6 | if (permission === "granted") { |
| 7 | new Notification("Hello!", { |
| 8 | body: "This is a browser notification", |
| 9 | icon: "/icon.png" |
| 10 | }); |
| 11 | } |
| 12 | } |
The Notification.requestPermission()method prompts the user for notification permission. It returns a Promise that resolves to a string: "granted", "denied", or "default". You can only request permission in response to a user gesture (click, keydown, etc.).
| 1 | // Check current permission state |
| 2 | console.log(Notification.permission); // "granted", "denied", or "default" |
| 3 | |
| 4 | // Request permission with UI |
| 5 | async function requestNotificationPermission() { |
| 6 | if (!("Notification" in window)) { |
| 7 | console.log("Notifications not supported"); |
| 8 | return false; |
| 9 | } |
| 10 | |
| 11 | if (Notification.permission === "granted") { |
| 12 | return true; |
| 13 | } |
| 14 | |
| 15 | if (Notification.permission === "denied") { |
| 16 | console.log("Notifications blocked — user must enable in settings"); |
| 17 | return false; |
| 18 | } |
| 19 | |
| 20 | // Permission is "default" — ask the user |
| 21 | const permission = await Notification.requestPermission(); |
| 22 | return permission === "granted"; |
| 23 | } |
| 24 | |
| 25 | // Button click handler |
| 26 | document.getElementById("enable-notifications").addEventListener("click", async () => { |
| 27 | const granted = await requestNotificationPermission(); |
| 28 | |
| 29 | if (granted) { |
| 30 | showNotification("Notifications enabled!", "You will now receive updates"); |
| 31 | } else { |
| 32 | showNotification("Notifications blocked", "Enable them in browser settings"); |
| 33 | } |
| 34 | }); |
| 35 | |
| 36 | // Check if notifications are supported and enabled |
| 37 | function canNotify() { |
| 38 | return "Notification" in window && Notification.permission === "granted"; |
| 39 | } |
warning
The Notificationconstructor accepts an options object that controls the notification's appearance and behavior. You can set the body text, icon, image, tag, requireInteraction, and more.
| Option | Type | Description |
|---|---|---|
| body | string | Notification body text |
| icon | string | URL of icon to display |
| image | string | URL of image (Android only) |
| tag | string | ID to replace existing notification |
| requireInteraction | boolean | Keep notification visible until clicked |
| silent | boolean | Suppress notification sound/vibration |
| data | any | Arbitrary data attached to notification |
| 1 | // Full notification options |
| 2 | const notification = new Notification("New Message", { |
| 3 | body: "You have a new message from Alice", |
| 4 | icon: "/icons/message.png", |
| 5 | image: "/images/alice-avatar.png", |
| 6 | tag: "message-123", // Replaces previous notification with same tag |
| 7 | requireInteraction: true, // Stays visible until user interacts |
| 8 | silent: false, // Play sound |
| 9 | data: { |
| 10 | url: "/messages/123", |
| 11 | messageId: 123 |
| 12 | } |
| 13 | }); |
| 14 | |
| 15 | // Handle notification click |
| 16 | notification.addEventListener("click", () => { |
| 17 | const data = notification.data; |
| 18 | window.focus(); |
| 19 | window.location.href = data.url; |
| 20 | notification.close(); |
| 21 | }); |
| 22 | |
| 23 | // Handle notification close |
| 24 | notification.addEventListener("close", () => { |
| 25 | console.log("Notification closed"); |
| 26 | }); |
| 27 | |
| 28 | // Notification with actions (service worker only) |
| 29 | // See service worker section below |
| 30 | |
| 31 | // Replacing notifications with same tag |
| 32 | function notifyNewMessage(message) { |
| 33 | new Notification("New Message", { |
| 34 | body: message.text, |
| 35 | icon: "/icons/message.png", |
| 36 | tag: `message-${message.channel}`, // Replaces old notification for this channel |
| 37 | requireInteraction: true |
| 38 | }); |
| 39 | } |
For more advanced notifications (like actions, badges, and background delivery), use the ServiceWorkerRegistration.showNotification() method. This is essential for push notifications, where the service worker receives the push event and shows a notification even when the page is closed.
| 1 | // Service Worker notification |
| 2 | async function showSWNotification(title, options = {}) { |
| 3 | if (!("serviceWorker" in navigator)) { |
| 4 | // Fallback to basic notification |
| 5 | return new Notification(title, options); |
| 6 | } |
| 7 | |
| 8 | const registration = await navigator.serviceWorker.ready; |
| 9 | |
| 10 | await registration.showNotification(title, { |
| 11 | body: options.body || "", |
| 12 | icon: options.icon || "/icon.png", |
| 13 | badge: options.badge || "/badge.png", |
| 14 | tag: options.tag || "default", |
| 15 | requireInteraction: options.requireInteraction || false, |
| 16 | data: options.data || {}, |
| 17 | actions: options.actions || [] |
| 18 | }); |
| 19 | } |
| 20 | |
| 21 | // Usage with actions |
| 22 | showSWNotification("New Order", { |
| 23 | body: "Order #1234 has been placed", |
| 24 | tag: "order-1234", |
| 25 | actions: [ |
| 26 | { action: "view", title: "View Order" }, |
| 27 | { action: "dismiss", title: "Dismiss" } |
| 28 | ], |
| 29 | data: { url: "/orders/1234" } |
| 30 | }); |
| 31 | |
| 32 | // Handle notification clicks in service worker |
| 33 | self.addEventListener("notificationclick", (event) => { |
| 34 | event.notification.close(); |
| 35 | |
| 36 | const action = event.action; |
| 37 | const data = event.notification.data; |
| 38 | |
| 39 | if (action === "view" || !action) { |
| 40 | event.waitUntil( |
| 41 | clients.openWindow(data.url || "/") |
| 42 | ); |
| 43 | } else if (action === "dismiss") { |
| 44 | // Just close — already done above |
| 45 | } |
| 46 | }); |
| 47 | |
| 48 | // Handle push events |
| 49 | self.addEventListener("push", (event) => { |
| 50 | const data = event.data?.json() || {}; |
| 51 | |
| 52 | event.waitUntil( |
| 53 | self.registration.showNotification(data.title || "New Notification", { |
| 54 | body: data.body || "You have a new notification", |
| 55 | icon: data.icon || "/icon.png", |
| 56 | tag: data.tag || "push-notification" |
| 57 | }) |
| 58 | ); |
| 59 | }); |
info