JavaScriptNotifications API

Notifications API

The Notifications API lets a web app display system-level notifications — the little banners that appear in the corner of the OS, outside the browser window. Combined with a service worker and the Push API, the same notification can be sent from a server and shown to the user even when the tab is closed. The browser surface is small, but the permission rules and UX considerations are everything.

Two flavours of notification
  • Page notifications — shown by your tab while it is open. Created with new Notification(title, options).

  • Service-worker notifications — shown via registration.showNotification(title, options). Persist after the tab closes, can wake the SW, and are required for push.

Push needs the service worker form
Only `registration.showNotification` can be called from a service worker — and a server-pushed notification *must* be shown from inside the `push` event. The page-only `new Notification(...)` form does not work in workers.
Asking for permission

Notifications require explicit user consent. Notification.permission is one of "default" (not asked yet), "granted", or "denied".

JS
if (Notification.permission === "default") {
  const result = await Notification.requestPermission();
  // result: "granted" | "denied" | "default"
}
Do not prompt on page load
Prompting cold is a near-universal rejection. Browsers have started blocking automatic prompts, and a denial is sticky — the user has to dig into site settings to take it back. Tie the request to a meaningful user action: "Subscribe to updates", "Notify me when it's ready".
Showing a basic notification

JS
if (Notification.permission === "granted") {
  const n = new Notification("New message", {
    body: "Alice replied in #general.",
    icon: "/icons/chat.png",
    badge: "/icons/badge.png",
    tag: "msg-1234",          // collapse duplicates
    data: { url: "/chat/1234" },
  });

  n.addEventListener("click", () => {
    window.focus();
    location.assign(n.data.url);
    n.close();
  });
}
The options worth knowing
  • body — secondary line of text.

  • icon / badge — small images. badge is a monochrome icon shown in some OS UIs (Android).

  • tag — a string; a new notification with the same tag replaces the previous one instead of stacking.

  • renotify — pair with tag. If true, replacing the previous notification still vibrates / sounds.

  • silent — suppress the sound and vibration.

  • requireInteraction — keep the notification visible until the user dismisses it (desktop only).

  • actions — array of { action, title, icon } buttons (service-worker notifications only).

  • data — arbitrary payload accessible later from the click handler.

Service-worker notifications

from inside sw.js

JS
self.registration.showNotification("Build finished", {
  body: "main.js — 12.3 KB",
  icon: "/icons/build.png",
  actions: [
    { action: "open", title: "Open" },
    { action: "dismiss", title: "Dismiss" },
  ],
  data: { url: "/builds/42" },
});

handling the click

JS
self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  if (event.action === "dismiss") return;

  event.waitUntil(
    self.clients.matchAll({ type: "window" }).then((clients) => {
      const open = clients.find((c) => c.url.endsWith(event.notification.data.url));
      if (open) return open.focus();
      return self.clients.openWindow(event.notification.data.url);
    })
  );
});

The pattern — find an existing tab on the right URL and focus it, otherwise open a new one — is the standard UX for any notification that links somewhere.

Push notifications, in one page

The full flow has three actors:

  • Your service worker subscribes the browser to push: registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }).

  • Your server receives the resulting PushSubscription (an endpoint URL and keys) and stores it.

  • To notify, your server signs a push payload with the matching VAPID private key and POSTs it to the endpoint. The browser delivers it, fires push in the service worker, and your code calls showNotification.

push.js (service worker)

JS
self.addEventListener("push", (event) => {
  const data = event.data?.json() ?? {};
  event.waitUntil(
    self.registration.showNotification(data.title ?? "Update", {
      body: data.body,
      icon: data.icon,
    })
  );
});
Notification vs other UI
  • In-page toast → use when the tab is open and focused. Cheaper and less intrusive.

  • System notification → use when the tab might be backgrounded, hidden, or closed.

  • Push notification → use when the server needs to reach the user without the tab being open at all.

Browser support
Web push works in Chrome, Edge, Firefox, Opera and recent Safari (with a notable caveat: iOS Safari requires the site to be installed to the home screen as a PWA before push works). Always feature-detect `window.Notification` and `navigator.serviceWorker`.