Working with Browser Notifications

The Notifications API lets a web page display system-level notifications via the Notification interface. Its basic workflow is straightforward: check the current permission state, request permission if it hasn't been decided yet, and then fire off a notification once you have granted permission.

Permission States and the Initial Setup

A site's permission state lives in the read-only Notification.permission property. There are three possible values:

  • default—the user hasn't made a choice; permission must be requested.
  • granted—the user has allowed notifications.
  • denied—the user has blocked them.

A simple way to report the current state is to call a helper function like showPermission() on page load and log the value of Notification.permission to the console.

function showPermission() {
  const permission = Notification.permission;
  console.log('Permission:', permission);
  document.querySelector('output').textContent = permission;
}

Requesting Permission

To ask the user for permission, you call Notification.requestPermission(). This method triggers a popup where the user can allow or block notifications for your site. It returns a promise that resolves with the resulting permission string.

async function requestPermission() {
  const permission = await Notification.requestPermission();
  console.log('Permission:', permission);
}

The user's response shapes what happens next:

  • Allow: Notification.permission becomes granted, and the site can show notifications. Future calls to requestPermission() resolve to granted without a popup.
  • Block: Notification.permission becomes denied, and the site can't show notifications. Future calls resolve to denied without a popup.
  • Dismissal: The permission remains default, and the popup will show again on future calls. If this keeps happening, the browser may decide to block the site entirely and set Notification.permission to denied.

On that last point, notice that browser behavior on repeatedly dismissed popups is still evolving. The practical implication is to always request permission in response to a user gesture—a button click, for example—so the request feels expected rather than intrusive.

Sending a Notification

To display a notification after you have permission, you instantiate a new Notification object. The constructor takes two arguments: a required title string and an optional options object that defines body text, icons, vibration patterns, and other data.

function sendNotification() {
  const title = 'Test title';
  const options = {
    body: 'Test body',
    icon: 'images/icon.png',
    badge: 'images/badge.png'
  };
  new Notification(title, options);
}

If the user hasn't granted permission, the constructor will throw an error. You can observe this failure mode by attaching an onerror event handler:

const notification = new Notification(title, options);
notification.onerror = (error) => {
  console.log('ERROR ALERT', 'Could not send notification');
  console.log(error);
};

To reproduce the error:

  1. Reset the site's notification permission to its default by clicking the lock icon in the URL bar.
  2. Click the Request permission button and select Block on the popup.
  3. Click Send notification. The onerror handler logs Could not send notification and the event object to the console.

Useful Notification Options

Modern platforms support a rich set of fields in the options object. These include body text, icons for different contexts, actions with unique IDs, badge icons for mobile, a silent flag, and vibration patterns. Keep in mind that browsers and devices implement these options differently, so testing your implementation across a few environments is worthwhile before shipping it.

The complete field set, as documented on MDN, looks like this:

{
  body: '',
  dir: 'auto',
  lang: '',
  tag: '',
  icon: '',
  badge: '',
  image: '',
  data: {},
  vibrate: [],
  renotify: false,
  requireInteraction: false,
  silent: false,
  timestamp: 0,
  actions: [
    { action: '', title: '', icon: '' }
  ]
}

One useful strategy when you only need to support the fundamentals is to bind the permission request to a specific button click. Not only does that give the user context for the popup, it also avoids triggering the browser's dismissal-heuristic fallback logic in the first place.