Notifications API logo

notifications api

System notifications for web applications

$ npx docs2skills add web-notifications-api
SKILL.md

Notifications API

System notifications for web applications that work outside the browser viewport

What this skill does

The Notifications API enables web applications to display system-level notifications that appear outside the browser's viewport - on the desktop, in the system tray, or mobile notification center. These notifications persist even when users switch tabs or applications, making them ideal for alerts, messages, reminders, and real-time updates.

The API provides permission management, customizable notification content (title, body, icon, actions), and integrates seamlessly with Service Workers for background notifications. It's designed to be compatible with native notification systems across different platforms while maintaining consistent behavior.

Critical for modern web apps that need to re-engage users with timely information, status updates, or urgent alerts when the application isn't actively in focus.

Prerequisites

  • HTTPS context (secure origin required)
  • Modern browser with Notifications API support
  • User interaction required for permission requests
  • Service Worker registration (for background notifications)
  • No external dependencies or API keys needed

Quick start

// Request permission (must be in response to user gesture)
btn.addEventListener("click", async () => {
  const permission = await Notification.requestPermission();
  if (permission === "granted") {
    // Create basic notification
    new Notification("Hello!", {
      body: "This is a system notification",
      icon: "/icon.png"
    });
  }
});

Core concepts

Permission Model: Three states - default (not asked), granted (allowed), denied (blocked). Permission persists per origin and can only be requested via user interaction.

Notification Lifecycle: Create → Show → User interaction (click/close) → Events fired. Notifications auto-dismiss after system timeout unless persistent.

Service Worker Integration: Background notifications via ServiceWorkerRegistration.showNotification() persist across page refreshes and enable action buttons with event handling.

Platform Compatibility: Browser translates web notifications to native system notifications, inheriting platform-specific appearance and behavior.

Key API surface

// Permission management
Notification.permission // "default" | "granted" | "denied"
Notification.requestPermission() // Returns Promise<permission>

// Basic notification constructor
new Notification(title, options)

// Service Worker methods
ServiceWorkerRegistration.showNotification(title, options)
ServiceWorkerRegistration.getNotifications(options)

// Notification instance methods/properties
notification.close()
notification.onclick = handler
notification.onshow = handler
notification.onclose = handler
notification.onerror = handler

// Service Worker events
self.addEventListener('notificationclick', event)
self.addEventListener('notificationclose', event)

Common patterns

Basic user notification

async function showBasicNotification() {
  if (Notification.permission === "granted") {
    new Notification("Task Complete", {
      body: "Your file has finished uploading",
      icon: "/success-icon.png",
      tag: "upload-complete" // Replaces previous with same tag
    });
  }
}

Permission request flow

async function requestNotificationPermission() {
  if (Notification.permission === "default") {
    const permission = await Notification.requestPermission();
    return permission === "granted";
  }
  return Notification.permission === "granted";
}

// Usage in click handler
button.onclick = async () => {
  const granted = await requestNotificationPermission();
  if (granted) {
    showNotification();
  } else {
    showInAppAlert("Notifications blocked");
  }
};

Service Worker persistent notifications

// In service worker
self.addEventListener('notificationclick', event => {
  event.notification.close();
  
  if (event.action === 'reply') {
    // Handle reply action
    clients.openWindow('/messages');
  } else {
    // Handle default click
    clients.openWindow('/');
  }
});

// Show from main thread
navigator.serviceWorker.ready.then(registration => {
  registration.showNotification("New Message", {
    body: "You have a new message from John",
    icon: "/message-icon.png",
    badge: "/badge-icon.png",
    actions: [
      { action: 'reply', title: 'Reply' },
      { action: 'dismiss', title: 'Dismiss' }
    ],
    requireInteraction: true // Won't auto-dismiss
  });
});

Rich notification with data

function showRichNotification(messageData) {
  const notification = new Notification("New Order #" + messageData.id, {
    body: `${messageData.items.length} items - $${messageData.total}`,
    icon: "/order-icon.png",
    image: messageData.previewImage,
    badge: "/badge.png",
    tag: `order-${messageData.id}`,
    data: messageData, // Attach custom data
    requireInteraction: false,
    silent: false,
    vibrate: [200, 100, 200] // Mobile vibration pattern
  });

  notification.onclick = () => {
    window.focus();
    window.location.href = `/orders/${messageData.id}`;
    notification.close();
  };
}

Background sync notifications

// Service worker background sync
self.addEventListener('sync', event => {
  if (event.tag === 'background-sync') {
    event.waitUntil(doBackgroundSync());
  }
});

async function doBackgroundSync() {
  try {
    const data = await fetchUpdates();
    if (data.hasNewMessages) {
      self.registration.showNotification("Updates Available", {
        body: `${data.messageCount} new messages`,
        tag: "sync-update"
      });
    }
  } catch (error) {
    console.error("Background sync failed:", error);
  }
}

Configuration

Notification options

{
  body: "Notification body text",
  icon: "/icon-url.png",          // Square icon ~512px
  badge: "/badge-url.png",        // Monochrome badge ~96px
  image: "/large-image.jpg",      // Large image for rich notifications
  tag: "unique-identifier",       // Replaces notifications with same tag
  data: { custom: "data" },       // Arbitrary data attached
  requireInteraction: false,      // Auto-dismiss behavior
  silent: false,                  // Sound/vibration
  vibrate: [200, 100, 200],      // Vibration pattern (mobile)
  timestamp: Date.now(),          // Custom timestamp
  actions: [                      // Action buttons (Service Worker only)
    { action: "action1", title: "Button 1", icon: "/icon1.png" }
  ]
}

Service Worker registration

// Register service worker first
navigator.serviceWorker.register('/sw.js').then(registration => {
  // Use registration.showNotification() for persistent notifications
});

Best practices

  • Always check permission before showing notifications: Check Notification.permission before creating notifications
  • Request permission only from user gestures: Call requestPermission() only in click/touch handlers
  • Use meaningful tags: Prevent notification spam by using tags to replace similar notifications
  • Provide fallbacks: Show in-app notifications when permission is denied
  • Use Service Worker for persistence: Background notifications survive page refreshes
  • Handle all notification events: Implement click, close, and error handlers
  • Respect user preferences: Honor requireInteraction and silent options appropriately
  • Optimize icon sizes: Use 512x512px for icons, 96x96px for badges
  • Test cross-platform: Notification appearance varies by OS/browser
  • Implement progressive enhancement: Gracefully degrade when API unavailable

Gotchas and common mistakes

Permission must be requested from user gesture: Calling requestPermission() outside click/touch handlers will fail silently in many browsers. Always tie to user interaction.

HTTPS/localhost only: Notifications API requires secure context. Won't work on HTTP in production, only localhost for development.

Service Worker vs regular notifications behave differently: new Notification() auto-closes and has limited options. ServiceWorkerRegistration.showNotification() persists and supports actions.

Tag replacement is immediate: Notifications with same tag replace previous ones instantly, even if content is identical.

Mobile limitations: iOS Safari has limited support. Android Chrome works well but appearance varies by device.

Action buttons only work with Service Workers: Regular notifications ignore the actions option.

Icons may not display: Many browsers/OS combinations don't show notification icons consistently. Don't rely on them for critical information.

Notification.permission is synchronous: Don't await it - it's a property, not a promise.

Close event timing: onclose fires for both user dismissal and auto-dismiss, making it unreliable for tracking user engagement.

requireInteraction doesn't guarantee persistence: Some platforms ignore this hint and auto-dismiss anyway.

Data serialization: Custom data gets JSON serialized, so functions and complex objects may not survive.

Permission can't be programmatically reset: Once denied, only user can re-enable in browser settings.

Vibration patterns mobile-only: Desktop ignores vibrate option completely.

Badge icons should be monochrome: Colored badges may not display correctly on all platforms.

Notification timing: There's no guaranteed delivery time - system may delay or drop notifications under load.