Web APIs Cheatsheet

Notifications

Use this Web APIs reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Permission

// Check current state
Notification.permission; // 'default' | 'granted' | 'denied'

// Request permission (must be called in response to user gesture)
const result = await Notification.requestPermission();
// result: 'granted' | 'denied' | 'default'

Browsers may silently ignore requestPermission() if not triggered by a user gesture (click, keypress, etc.).

Showing a Notification (Page Context)

if (Notification.permission === 'granted') {
  const n = new Notification('Title', {
    body: 'Notification body text.',
    icon: '/icons/notification-icon.png',  // image URL
    badge: '/icons/badge.png',             // small monochrome icon (Android)
    image: '/img/preview.jpg',             // large preview image
    tag: 'thread-1',                       // group/replace same-tag notifications
    data: { url: '/messages/42' },         // arbitrary data, no display
    silent: false,                         // mute sound/vibration
    requireInteraction: false,             // keep notification visible until user acts
    renotify: false,                       // re-notify if same tag replaced
    dir: 'auto',                           // text direction: 'auto' | 'ltr' | 'rtl'
    lang: 'en',                            // language tag
    vibrate: [200, 100, 200],              // vibration pattern ms (mobile)
    timestamp: Date.now(),                 // when the event occurred
  });
}

Notification Events (page context)

n.addEventListener('click', e => {
  window.focus();
  n.close();
});
n.addEventListener('close', () => {});
n.addEventListener('error', e => console.error(e));
n.addEventListener('show',  () => {});

// Programmatically close
n.close();

Service Worker Event Handlers

// sw.js

// User clicked notification body
self.addEventListener('notificationclick', e => {
  e.notification.close();
  const { url } = e.notification.data;

  if (e.action === 'reply') {
    e.waitUntil(clients.openWindow(`${url}?reply=true`));
    return;
  }
  if (e.action === 'dismiss') {
    return; // already closed
  }

  // Default click — focus existing window or open new one
  e.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true }).then(list => {
      for (const client of list) {
        if (client.url === url) return client.focus();
      }
      return clients.openWindow(url);
    })
  );
});

// Notification dismissed by user or OS
self.addEventListener('notificationclose', e => {
  const { data } = e.notification;
  // Analytics: log that user dismissed
});

Web Push (background notifications)

// 1. Subscribe
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
});

// sub.endpoint, sub.keys.p256dh, sub.keys.auth
// Send sub to your server to store

// 2. Check existing subscription
const existing = await reg.pushManager.getSubscription();

// 3. Unsubscribe
await sub.unsubscribe();

// Helper to convert VAPID key
function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const raw = atob(base64);
  return Uint8Array.from([...raw].map(c => c.charCodeAt(0)));
}
// sw.js — receive push
self.addEventListener('push', e => {
  if (!e.data) return;
  const payload = e.data.json();   // or e.data.text(), e.data.arrayBuffer()
  e.waitUntil(
    self.registration.showNotification(payload.title, {
      body: payload.body,
      icon: payload.icon,
      data: payload.data,
    })
  );
});

// Push subscription change (key rotation)
self.addEventListener('pushsubscriptionchange', e => {
  e.waitUntil(
    e.newSubscription
      ? sendToServer(e.newSubscription)
      : reg.pushManager.subscribe(subscribeOptions).then(sendToServer)
  );
});

Listing and Closing Active Notifications

const reg = await navigator.serviceWorker.ready;

// Get all notifications (optional filter by tag)
const notifications = await reg.getNotifications({ tag: 'thread-1' });
notifications.forEach(n => n.close());

Notification.permission States

StateMeaning
'default'Not yet asked; notifications will not show
'granted'User allowed; notifications will show
'denied'User blocked; requestPermission always returns 'denied'

Once denied, there is no programmatic way to re-prompt. Users must change permission in browser settings.

Gotchas

  • Page notifications (new Notification()) don't work in Service Worker scope and don't persist across page close.
  • requireInteraction is not supported on mobile browsers (notifications always auto-dismiss).
  • actions are only available in Service Worker notifications, and limited to 2 buttons on most platforms.
  • Push notifications require a VAPID key pair, a push subscription endpoint, and a server that can send to the Web Push protocol.
  • Silent push (e.data is null) can wake a Service Worker but may be throttled by browsers to prevent abuse.
  • The browser may suppress notifications if the user has not interacted with the site recently (quiet UI permission).

Quick Permission Pattern

async function askNotificationPermission() {
  if (!('Notification' in window)) return false;
  if (Notification.permission === 'granted') return true;
  if (Notification.permission === 'denied') return false;
  const result = await Notification.requestPermission();
  return result === 'granted';
}

button.addEventListener('click', async () => {
  if (await askNotificationPermission()) {
    new Notification('Notifications enabled!');
  }
});