JavaScript Web Notifications API Complete Guide

Master browser notifications: permission requests, notification creation, interaction handling, and Service Worker notifications

JavaScript Web Notifications API Complete Guide

The Web Notifications API allows web pages to send system notifications to users. This article covers notification API usage and best practices.

Basic Concepts

Checking Support

// Check if notifications are supported
function isNotificationSupported() {
  return 'Notification' in window;
}

// Check current permission status
function getNotificationPermission() {
  if (!isNotificationSupported()) {
    return 'unsupported';
  }
  return Notification.permission;
  // 'default' - not requested
  // 'granted' - authorized
  // 'denied' - rejected
}

Requesting Permission

// Request notification permission
async function requestNotificationPermission() {
  if (!isNotificationSupported()) {
    return 'unsupported';
  }

  // Already granted
  if (Notification.permission === 'granted') {
    return 'granted';
  }

  // Already denied (cannot request again)
  if (Notification.permission === 'denied') {
    return 'denied';
  }

  // Request permission
  try {
    const permission = await Notification.requestPermission();
    return permission;
  } catch (error) {
    console.error('Permission request failed:', error);
    return 'error';
  }
}

// Request on user interaction
document.querySelector('.enable-notifications').addEventListener('click', async () => {
  const permission = await requestNotificationPermission();

  switch (permission) {
    case 'granted':
      showToast('Notifications enabled');
      break;
    case 'denied':
      showToast('Notifications denied, please enable in browser settings');
      break;
    case 'default':
      showToast('Please allow notification permission');
      break;
  }
});

Creating Notifications

Basic Notification

// Create simple notification
function showNotification(title, body) {
  if (Notification.permission !== 'granted') {
    console.warn('Notifications not authorized');
    return null;
  }

  const notification = new Notification(title, {
    body: body
  });

  return notification;
}

// Usage
showNotification('New Message', 'You have a new message');

Notification Options

// Full notification options
function showRichNotification() {
  const notification = new Notification('New Order Alert', {
    // Notification body
    body: 'You received a new order, click to view details',

    // Notification icon
    icon: '/icons/notification-icon.png',

    // Badge (small icon)
    badge: '/icons/badge.png',

    // Large image
    image: '/images/order-preview.jpg',

    // Notification tag (same tag notifications replace each other)
    tag: 'order-notification',

    // Require user interaction to close
    requireInteraction: true,

    // Silent mode
    silent: false,

    // Vibration pattern [vibrate, pause, vibrate]
    vibrate: [200, 100, 200],

    // Custom data
    data: {
      orderId: '12345',
      url: '/orders/12345'
    },

    // Text direction
    dir: 'ltr', // 'ltr', 'rtl', 'auto'

    // Language
    lang: 'en-US',

    // Timestamp
    timestamp: Date.now()
  });

  return notification;
}

Notification Action Buttons

// Notification with action buttons (requires Service Worker)
async function showActionNotification() {
  const registration = await navigator.serviceWorker.ready;

  await registration.showNotification('New Message', {
    body: 'Message from John: Hello!',
    icon: '/icons/message.png',
    actions: [
      {
        action: 'reply',
        title: 'Reply',
        icon: '/icons/reply.png'
      },
      {
        action: 'dismiss',
        title: 'Dismiss',
        icon: '/icons/dismiss.png'
      }
    ],
    data: {
      messageId: '123',
      senderId: 'john'
    }
  });
}

Notification Events

Basic Events

function showNotificationWithEvents(title, options) {
  const notification = new Notification(title, options);

  // Notification shown
  notification.addEventListener('show', () => {
    console.log('Notification displayed');
  });

  // Click notification
  notification.addEventListener('click', (event) => {
    console.log('Notification clicked');

    // Focus window
    window.focus();

    // Navigate to related page
    if (notification.data?.url) {
      window.location.href = notification.data.url;
    }

    // Close notification
    notification.close();
  });

  // Notification closed
  notification.addEventListener('close', () => {
    console.log('Notification closed');
  });

  // Notification error
  notification.addEventListener('error', (error) => {
    console.error('Notification error:', error);
  });

  return notification;
}

Auto-Close

// Auto-closing notification
function showAutoCloseNotification(title, options, duration = 5000) {
  const notification = new Notification(title, options);

  // Set auto-close
  setTimeout(() => {
    notification.close();
  }, duration);

  return notification;
}

// Usage
showAutoCloseNotification('Download Complete', {
  body: 'File downloaded to local storage'
}, 3000);

Service Worker Notifications

Registering Service Worker

// main.js
async function registerServiceWorker() {
  if ('serviceWorker' in navigator) {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js');
      console.log('Service Worker registered successfully');
      return registration;
    } catch (error) {
      console.error('Service Worker registration failed:', error);
      return null;
    }
  }
  return null;
}

Handling Notifications in Service Worker

// sw.js
self.addEventListener('notificationclick', (event) => {
  const notification = event.notification;
  const action = event.action;
  const data = notification.data;

  notification.close();

  // Handle action buttons
  if (action === 'reply') {
    // Open reply page
    event.waitUntil(
      clients.openWindow(`/messages/${data.messageId}/reply`)
    );
  } else if (action === 'dismiss') {
    // Mark as read
    event.waitUntil(
      fetch(`/api/messages/${data.messageId}/dismiss`, {
        method: 'POST'
      })
    );
  } else {
    // Click on notification body
    event.waitUntil(
      clients.matchAll({ type: 'window' }).then(clientList => {
        // Find open window
        for (const client of clientList) {
          if (client.url.includes('/messages') && 'focus' in client) {
            return client.focus();
          }
        }
        // Open new window
        return clients.openWindow(data.url || '/');
      })
    );
  }
});

// Notification close event
self.addEventListener('notificationclose', (event) => {
  console.log('Notification closed:', event.notification.tag);

  // Can record user behavior
  event.waitUntil(
    fetch('/api/analytics/notification-closed', {
      method: 'POST',
      body: JSON.stringify({
        tag: event.notification.tag
      })
    })
  );
});

Sending Notifications from Service Worker

// sw.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() || {};

  const options = {
    body: data.body || 'You have a new notification',
    icon: data.icon || '/icons/default.png',
    badge: '/icons/badge.png',
    tag: data.tag || 'default',
    data: data,
    requireInteraction: data.requireInteraction || false
  };

  event.waitUntil(
    self.registration.showNotification(data.title || 'Notification', options)
  );
});

Practical Applications

Message Notification System

class NotificationManager {
  constructor() {
    this.permission = Notification.permission;
    this.unreadCount = 0;
  }

  async init() {
    // Request permission
    if (this.permission === 'default') {
      this.permission = await Notification.requestPermission();
    }

    // Listen for messages
    this.startListening();
  }

  startListening() {
    // Assume using WebSocket
    const ws = new WebSocket('wss://api.example.com/ws');

    ws.addEventListener('message', (event) => {
      const message = JSON.parse(event.data);

      if (message.type === 'new_message') {
        this.showMessageNotification(message);
      }
    });
  }

  showMessageNotification(message) {
    // Only show notification when page is hidden
    if (document.hidden && this.permission === 'granted') {
      const notification = new Notification(message.sender, {
        body: message.content,
        icon: message.avatar,
        tag: `message-${message.id}`,
        data: {
          messageId: message.id,
          url: `/messages/${message.id}`
        }
      });

      notification.addEventListener('click', () => {
        window.focus();
        window.location.href = `/messages/${message.id}`;
        notification.close();
      });

      // Update title
      this.unreadCount++;
      this.updateTitle();
    }
  }

  updateTitle() {
    const originalTitle = document.title.replace(/^\(\d+\)\s*/, '');
    if (this.unreadCount > 0) {
      document.title = `(${this.unreadCount}) ${originalTitle}`;
    } else {
      document.title = originalTitle;
    }
  }

  clearUnread() {
    this.unreadCount = 0;
    this.updateTitle();
  }
}

// Usage
const notificationManager = new NotificationManager();
notificationManager.init();

// Clear unread when page visible
document.addEventListener('visibilitychange', () => {
  if (!document.hidden) {
    notificationManager.clearUnread();
  }
});

Scheduled Reminders

class ReminderService {
  constructor() {
    this.reminders = new Map();
  }

  async requestPermission() {
    if (Notification.permission !== 'granted') {
      await Notification.requestPermission();
    }
  }

  setReminder(id, title, body, time) {
    const delay = time - Date.now();

    if (delay <= 0) {
      console.warn('Reminder time has passed');
      return false;
    }

    // Store timer
    const timerId = setTimeout(() => {
      this.showReminder(id, title, body);
    }, delay);

    this.reminders.set(id, {
      timerId,
      title,
      body,
      time
    });

    // Persist storage
    this.saveReminders();

    return true;
  }

  showReminder(id, title, body) {
    if (Notification.permission === 'granted') {
      const notification = new Notification(title, {
        body,
        icon: '/icons/reminder.png',
        tag: `reminder-${id}`,
        requireInteraction: true
      });

      notification.addEventListener('click', () => {
        window.focus();
        notification.close();
      });
    }

    // Remove reminder
    this.reminders.delete(id);
    this.saveReminders();
  }

  cancelReminder(id) {
    const reminder = this.reminders.get(id);
    if (reminder) {
      clearTimeout(reminder.timerId);
      this.reminders.delete(id);
      this.saveReminders();
      return true;
    }
    return false;
  }

  saveReminders() {
    const data = Array.from(this.reminders.entries()).map(([id, r]) => ({
      id,
      title: r.title,
      body: r.body,
      time: r.time
    }));
    localStorage.setItem('reminders', JSON.stringify(data));
  }

  loadReminders() {
    const data = JSON.parse(localStorage.getItem('reminders') || '[]');
    data.forEach(r => {
      if (r.time > Date.now()) {
        this.setReminder(r.id, r.title, r.body, r.time);
      }
    });
  }
}

Notification Grouping

// Notification group management
class NotificationGroup {
  constructor(groupId, maxNotifications = 5) {
    this.groupId = groupId;
    this.maxNotifications = maxNotifications;
    this.notifications = [];
  }

  add(title, body, data) {
    // Close old notifications
    if (this.notifications.length >= this.maxNotifications) {
      const oldest = this.notifications.shift();
      oldest.close();
    }

    // Create new notification
    const notification = new Notification(title, {
      body,
      tag: `${this.groupId}-${Date.now()}`,
      data
    });

    this.notifications.push(notification);

    // Listen for close event
    notification.addEventListener('close', () => {
      const index = this.notifications.indexOf(notification);
      if (index > -1) {
        this.notifications.splice(index, 1);
      }
    });

    return notification;
  }

  closeAll() {
    this.notifications.forEach(n => n.close());
    this.notifications = [];
  }

  // Show summary notification
  showSummary(count, message) {
    this.closeAll();

    new Notification(`${count} new messages`, {
      body: message,
      tag: `${this.groupId}-summary`,
      requireInteraction: true
    });
  }
}

// Usage
const messageGroup = new NotificationGroup('messages', 3);

// On new message
function onNewMessage(message) {
  if (messageGroup.notifications.length >= 3) {
    messageGroup.showSummary(4, 'You have multiple unread messages');
  } else {
    messageGroup.add(message.sender, message.content, {
      messageId: message.id
    });
  }
}

Best Practices Summary

Web Notifications Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Permission Handling                               │
│   ├── Request at appropriate time                  │
│   ├── Explain why notifications are needed         │
│   └── Handle denial gracefully                     │
│                                                     │
│   Notification Design                               │
│   ├── Keep it concise and clear                    │
│   ├── Use meaningful icons                         │
│   ├── Provide relevant action buttons              │
│   └── Avoid excessive notifications                │
│                                                     │
│   User Experience                                   │
│   ├── Only send for important events               │
│   ├── Provide notification settings                │
│   ├── Use tags wisely to avoid duplicates          │
│   └── Avoid sending when page is visible           │
│                                                     │
└─────────────────────────────────────────────────────┘
PropertyPurposeExample
bodyNotification bodyMessage content preview
iconNotification iconSender avatar
tagNotification IDSame tag replaces
requireInteractionPersistent displayImportant notifications
actionsAction buttonsReply, Dismiss

Master the Web Notifications API to create timely and effective user alerts.