How Safari Web Apps Use the Badging API for Notifications

This article explains how the Badging API enables web applications installed through the Safari browser to display unread notification counts directly on their Home Screen or macOS Dock icons. It covers the system and permission prerequisites for using the API on Apple devices, the specific JavaScript methods required to set and clear badges, and how the API integrates with Service Workers to update counts even when the web app is closed.

Prerequisites for Safari Web Apps

Before a web app can display an icon badge via Safari, it must meet specific installation and permission criteria enforced by WebKit:

  1. Installation: The web app cannot display badges inside a standard Safari browser tab. On iOS and iPadOS (16.4 or later), the user must install the app using the "Add to Home Screen" option in the Share menu. On macOS Sonoma (Safari 17 or later), the user must install it using the "Add to Dock" option.
  2. Notification Permission: For privacy reasons, Safari requires the user to explicitly grant notification permissions before allowing a web app to display a badge. The app must call Notification.requestPermission() and receive a "granted" status before the Badging API will function.

Setting the Unread Count

The Badging API is exposed on the global navigator object. This allows developers to update the badge both from the main application window and from a background Service Worker.

To display an unread count, you use the setAppBadge() method and pass an integer representing the number of unread items:

// Sets the badge on the Home Screen or Dock icon to 5
if ('setAppBadge' in navigator) {
  navigator.setAppBadge(5).catch((error) => {
    console.error('Failed to set app badge:', error);
  });
}

The method returns a Promise, allowing developers to handle any potential errors if the platform fails to apply the badge.

Clearing the Badge

Once a user opens the web app and views their messages or notifications, the badge should be removed. There are two ways to clear the badge using the API:

  1. Call the dedicated clearAppBadge() method.
  2. Pass the number 0 into the setAppBadge() method.
// Removes the badge from the app icon
if ('clearAppBadge' in navigator) {
  navigator.clearAppBadge().catch((error) => {
    console.error('Failed to clear app badge:', error);
  });
}

Updating Badges in the Background

For an unread count to be useful, it must update even when the user does not have the web app actively open. Safari supports this by allowing the Badging API to run inside a Service Worker alongside the Push API.

When a backend server sends a push notification to the user's device, the web app's Service Worker wakes up in the background to handle the push event. Within this event listener, the Service Worker parses the incoming data to determine the new unread count and updates the icon immediately:

// Inside sw.js (Service Worker)
self.addEventListener('push', (event) => {
  const data = event.data.json();
  const unreadCount = data.unreadCount;

  // Update the badge count
  if ('setAppBadge' in navigator) {
    event.waitUntil(navigator.setAppBadge(unreadCount));
  }

  // Display the standard push notification to the user
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.message
    })
  );
});

By tying navigator.setAppBadge() to the push event using event.waitUntil(), the Service Worker ensures the operating system does not terminate the background process before the Home Screen or Dock icon badge is successfully updated.