jsguides

Using the Notifications API to send desktop notifications

The Notifications API lets your web application display desktop notifications to users even when they’re not looking at your tab. Whether it’s a new message, a task reminder, or a background process completing, notifications keep users informed in real-time.

You need to request permission from the user, construct notification objects with a title and options, and respond to click events to take users to the right place. This tutorial covers the full workflow from permission request through event handling and responsible usage patterns.

Checking for notification support

Before using the Notifications API, check if the browser supports it:

if ('Notification' in window) {
  console.log('Notifications API supported!');
} else {
  console.log('Notifications not supported in this browser');
}

Most modern browsers support the Notifications API, but it’s good practice to check anyway.

Requesting Permission

Before you can display any notification, you must explicitly ask the user for permission. This is a deliberate design choice — browsers require user consent to prevent abuse.

async function requestNotificationPermission() {
  if (!('Notification' in window)) {
    console.log('This browser does not support notifications');
    return;
  }

  if (Notification.permission === 'granted') {
    console.log('Notification permission already granted');
    return;
  }

  if (Notification.permission !== 'denied') {
    const permission = await Notification.requestPermission();
    console.log('Permission status:', permission);
  }
}

// Call it when appropriate (e.g., on a button click)
requestNotificationPermission();

The Notification.requestPermission() method returns a promise that resolves to one of three values:

  • granted: The user has given permission
  • denied: The user has blocked notifications
  • default: The user dismissed the permission prompt

Notice that you can only request permission once. If the user denies it, you cannot ask again; you would need to guide them to manually enable notifications in browser settings.

Creating Notifications

Once you have permission, creating a notification is straightforward:

function showNotification(title, options = {}) {
  if (Notification.permission !== 'granted') {
    console.log('No permission to show notifications');
    return;
  }

  const notification = new Notification(title, {
    body: options.body || '',
    icon: options.icon || '/notification-icon.png',
    badge: options.badge || '/badge-icon.png',
    tag: options.tag || '',
    requireInteraction: options.requireInteraction || false,
    data: options.data || {}
  });

  // Handle notification click
  notification.onclick = () => {
    window.focus();
    notification.close();
  };

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

  return notification;
}

// Usage
showNotification('New Message', {
  body: 'You have a new message from Alice',
  icon: '/icons/message.png',
  tag: 'message-123'
});

The Notification constructor accepts a title and an options object with several useful properties:

  • body: The notification text below the title
  • icon: URL of an image to show as the notification icon
  • badge: A small icon shown in the taskbar
  • tag: A string identifier for grouping notifications
  • requireInteraction: Keeps the notification visible until the user interacts with it
  • data: Arbitrary data you want to attach to the notification

Setting the right options gives the notification content and context, but you also need to control what happens when the user clicks or dismisses it. The notification object exposes several events that let you respond to those interactions directly.

Handling notification events

Notifications can trigger several events you can listen to:

const notification = new Notification('Task Complete', {
  body: 'Your download has finished'
});

// When user clicks the notification
notification.onclick = (event) => {
  console.log('Notification clicked');
  window.focus();
  // Navigate to the relevant page
  window.location.href = '/downloads';
};

// When notification is closed (by user or auto)
notification.onclose = (event) => {
  console.log('Notification closed after', event.target.timeout, 'ms');
};

// When an error occurs
notification.onerror = (event) => {
  console.error('Notification error:', event);
};

You can also use the onshow event to track when the notification is actually displayed on screen. This fires after the OS renders the notification, so it is the right moment to start timers for auto-dismiss or to log that the notification was delivered successfully:

notification.onshow = (event) => {
  console.log('Notification shown at', new Date());

  // Auto-close after 5 seconds
  setTimeout(() => {
    notification.close();
  }, 5000);
};

Tracking when a notification appears and when it closes gives you a complete picture of its lifecycle. The onshow event fires as soon as the OS renders the notification, which is useful for logging delivery or starting auto-dismiss timers. The onclose and onerror handlers let you clean up state or retry when something goes wrong.

For sites that send several kinds of notifications, grouping them into named channels gives users a way to manage each category independently in browser settings.

Notification channels (advanced)

On some browsers (notably Chrome and Edge), you can create notification channels to give users fine-grained control over different types of notifications:

// Note: This works primarily in Chrome/Edge
function createNotificationChannel() {
  if (!('NotificationChannel' in window)) {
    console.log('Notification channels not supported');
    return;
  }

  const channel = new NotificationChannel('messages', {
    description: 'Notifications about new messages',
    importance: NotificationManager.GENERIC,
    lightColor: '#007bff',
    sound: '/sounds/message.mp3'
  });

  channel.addEventListener('notificationclick', (event) => {
    console.log('Message notification clicked');
    event.notification.close();
  });

  return channel;
}

// In practice, you'd register this during service worker setup
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.ready.then((registration) => {
    // Create notification channel via service worker
    console.log('Service Worker ready for notifications');
  });
}

Note: Notification channels require a service worker to work properly, and we will cover that in a future tutorial about push notifications.

Best Practices

Here are some tips for using notifications effectively:

  1. Ask at the right time: Don’t ask for permission immediately when the page loads. Wait until the user has engaged with your app and understands why notifications would be useful.

  2. Respect the user’s choice: If permission is denied, don’t keep asking. Instead, show a subtle UI element explaining how they can enable notifications in browser settings.

  3. Don’t over-notify: Too many notifications annoy users and lead them to block your site entirely.

  4. Include useful data: Use the data option to attach context to notifications so you can handle clicks intelligently.

  5. Handle permission states: Always check Notification.permission before attempting to create notifications.

A complete example

Here’s a practical example combining everything we’ve learned. The NotificationManager class wraps permission handling and notification creation into a single object you can import and use across your application:

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

  async init() {
    if (this.permission === 'default') {
      await this.requestPermission();
    }
    return this.permission === 'granted';
  }

  async requestPermission() {
    this.permission = await Notification.requestPermission();
    return this.permission;
  }

The constructor reads the current permission state on creation, so you always know whether you can send notifications without making an extra check. The init() method only prompts when the permission state is still default, which avoids bothering users who have already made a choice.

  notify(title, options = {}) {
    if (this.permission !== 'granted') {
      console.warn('Cannot notify: permission not granted');
      return null;
    }

    const notification = new Notification(title, {
      icon: '/icons/app-icon.png',
      badge: '/icons/badge.png',
      ...options
    });

    return notification;
  }

  notifyNewMessage(sender, preview) {
    return this.notify('New message from ' + sender, {
      body: preview,
      tag: 'messages',
      requireInteraction: true,
      data: { type: 'message', sender }
    });
  }
}

The notify() method is a factory: it checks permission, merges default icons with caller-supplied options, and returns the notification object for further event binding. notifyNewMessage() builds on notify() by adding message-specific defaults like the tag and requireInteraction flag, which keeps the caller code concise.

// Usage
const notifications = new NotificationManager();

document.getElementById('enable-btn').addEventListener('click', async () => {
  const granted = await notifications.init();
  if (granted) {
    console.log('Notifications enabled!');
    notifications.notifyNewMessage('Alice', 'Hey, are you free tonight?');
  }
});

Tying the permission request to a button click follows the best practice of asking at the right moment. The user clicks a control labeled “Enable notifications,” the browser shows its permission prompt, and the first notification fires only after the user grants access.

Summary

The Notifications API is a powerful way to re-engage users even when they’ve navigated away from your site. Remember these key points:

  • Always request permission before creating notifications
  • Handle the three permission states: granted, denied, and default
  • Use event listeners to respond to user interactions
  • Follow best practices to avoid annoying your users

In the next tutorial, we’ll explore the Clipboard API, another useful browser API for interacting with the user’s system.

A better notification habit

If a notification is going to interrupt the user, make sure it earns that interruption. The message should point to something actionable, the timing should match the event that triggered it, and the click path should take the user to the right place without extra friction. Notifications work best when they feel like a direct extension of the app rather than a separate message channel.

It also helps to think about how often the user might see the same event. If the same action can happen in a burst, use a tag or another grouping strategy so the screen does not fill with duplicate alerts. That keeps the experience readable and stops the browser from becoming noisy when several things finish at once.

Using notifications responsibly

Notifications work best when they feel like a useful extension of the app rather than an interruption. The clearest rule is to ask only after the user has shown intent. If the page asks for permission too early, the request feels abstract and the answer is often no. If the user has already clicked a relevant control, the request makes more sense and the permission prompt is easier to justify.

It also helps to keep each notification short and specific. The title should tell the user what happened, while the body adds just enough detail to decide whether they need to act. A notification that tries to do too much becomes harder to scan and more likely to be dismissed. In practice, the most useful ones point to a single task, message, or status change.

Data attached to the notification should support the click path. If a user taps the notification, the app should know where to take them or which record to open. That makes the notification feel connected to the rest of the product instead of like a separate system message. Good metadata also makes it easier to track delivery, response, and dismissal in a way that is useful for support or analytics.

Respecting permission state is part of the design, not just a technical check. A denied permission should lead to a calm fallback, such as an in-app indicator or settings hint. Repeated prompts tend to annoy people more than they help. The app feels better when it treats the user choice as final for the moment and offers a clear alternative path.

Finally, think about timing. A notification that arrives while the user is still working in the same view may not need to interrupt them at all. Some events are better shown inside the page or queued for later. Choosing the right delivery channel is what keeps notifications helpful instead of noisy.

See Also