← Notification attribution for PWAs on macOS · Chrome 152 reference

v152 · behavior contract · badging

App badging requires notification permission for installed PWAs on macOS

With notification attribution active (Chrome 152+, macOS, installed PWAs), the Badging API only updates the PWA's Dock icon badge when the app has notification permission. Without it, navigator.setAppBadge() and navigator.clearAppBadge() silently do nothing — no error, no rejection, no badge.

Silent failure — check permission before you badge This is an intentional silent no-op, matching WebKit's behavior for installed web apps on macOS. If your PWA relies on the Dock badge (unread counts, pending tasks), request notification permission first or surface the count in-app; there is no programmatic way to detect that a badge update was dropped.

syntax

The API surface is unchanged — the change is a precondition on the existing methods:

// Badging API (W3C WICG)
await navigator.setAppBadge(contents); // contents: optional integer
await navigator.clearAppBadge();
Source: W3C Badging API — setAppBadge() / clearAppBadge()

inputs

InputContract under attribution (Chrome 152+, macOS, installed PWA)
setAppBadge(n), n > 0Shows the number badge on the PWA's Dock icon only if notification permission is granted; otherwise silently ignored
setAppBadge() / setAppBadge(0)Per the Badging API, no argument shows an unnumbered badge and 0 clears; the same permission precondition applies (Safari differs: 0 clears rather than showing a dot — see compatibility)
clearAppBadge()Clears the badge; subject to the same permission gate

Scope: the gate applies to the app badge of installed PWAs with attribution active. It does not change the Badging API on other platforms or for non-installed pages.

Source: blink-dev PSA; W3C Badging API

outputs

Source: blink-dev PSA; app_shim_manager_mac.h

errors

The defining characteristic of this change is the absence of an error path: without notification permission the calls succeed from the page's point of view and simply have no effect. Permission state is observable in advance via Notification.permission ("granted" / "denied" / "default") or navigator.permissions.query({ name: "notifications" }) — check it before relying on the badge.

Source: blink-dev PSA; WHATWG Notifications API Standardpermission model

context and exposure

The gate applies under the same conditions as attribution itself (macOS, installed with OS integration, registered ad-hoc-signed app shim, feature enabled — see requirements). Why the coupling exists: with attribution, the badge belongs to the PWA's own bundle identity at the OS level, and macOS only shows a bundle's badge when that bundle is permitted to post notifications — badge visibility is part of per-app notification settings.

Source: blink-dev PSA; web_app_tab_helper.cc

lifecycle

  1. The PWA calls navigator.setAppBadge(n) (page or service worker context, per the Badging API).
  2. Chrome checks the app's notification permission for the attributed PWA (the two-layer model — web origin permission plus the shim's OS-level permission; see the permission model).
  3. Granted → the badge value is routed to the app shim (UpdateAppBadge) and macOS updates the Dock icon. Not granted → the call silently does nothing.
  4. Revoking notification permission later (macOS settings or Chrome settings) removes the OS-level badge display; subsequent calls no-op until permission is restored.
Source: blink-dev PSA; app_shim_manager_mac.h; permission_prompt_notifications_mac.cc

examples

// Gate badge updates on notification permission — required for
// installed PWAs on macOS from Chrome 152.
async function updateUnreadBadge(count) {
  if (!("setAppBadge" in navigator)) return;
  if (Notification.permission !== "granted") {
    showInAppUnreadIndicator(count); // don't lose the signal
    return;
  }
  if (count > 0) {
    await navigator.setAppBadge(count);
  } else {
    await navigator.clearAppBadge();
  }
}

// Ask up front (from a user gesture) if your UX depends on the badge:
enableButton.addEventListener("click", async () => {
  const result = await Notification.requestPermission();
  if (result === "granted") await updateUnreadBadge(currentCount);
});

Try it in the Chrome Platform Showcase badge permission lab (route HEAD-checked 200, 2026-07-29).

Source: behavior per the PSA; demo per chrome-platform-showcase

browser compatibility

From BCD api/Navigator.json setAppBadge (fetched 2026-07-29), with the macOS permission gate annotated — BCD has no entry for the gate itself:

BrowsersetAppBadge()Notes
Chrome81BCD notes: “Windows and macOS since Chrome 81. ChromeOS since Chrome 91. Linux offers no universal badging API on the operating system level.” 152 (macOS): notification permission required for installed PWAs (listing, PSA)
Edge81 (mirror)BCD edge: mirror; no separate statement on the macOS permission gate
FirefoxNo supportBCD version_added: false
Safari17“Badging is supported for installed web apps on macOS Sonoma and higher”; passing 0 clears the badge instead of displaying an unnumbered dot
Source: BCD api/Navigator.json; webstatus.dev badging (Baseline “limited”, 2026-07-29)

security and privacy

Source: blink-dev PSA — behavior + WebKit alignment

specifications

W3C Badging APIWICG draft — unchanged; the notification-permission precondition is a Chrome-on-macOS platform constraint, not spec text
WHATWG Notifications API Standardpermission modelLiving Standard — referenced for the permission state the gate consults

see also