Skip to content

The notification permission model

Published Updated

In one line: Notification.permission reports one of three states — default, granted, or denied — and per MDN, code should only call Notification.requestPermission() in response to a real user gesture, never on load.

Per MDN’s Notification.permission reference:

  • default — the user has not made a choice yet; the browser treats this the same as denied until the user grants permission.
  • granted — the user accepted; on desktop the page may construct notifications with the Notification constructor (per MDN, most mobile browsers throw instead, and expect ServiceWorkerRegistration.showNotification()).
  • denied — the user refused; notifications will not be displayed.

Per MDN’s Using the Notifications API guide, permission should be requested in response to a user gesture such as a click, and browsers are increasingly disallowing requests that aren’t — MDN names Firefox 72+ and Safari as browsers that already enforce this. Ask in response to that gesture, not on load:

document.getElementById('enable-notifications').addEventListener('click', async () => {
if (!('Notification' in window)) {
// Fallback: the Notification API doesn't exist in this browser at all.
return;
}
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
// Fallback: user declined (or dismissed) the prompt — don't proceed as if granted.
return;
}
await showEnabledNotification();
});
async function showEnabledNotification() {
// Per MDN, most mobile browsers expose `Notification` but throw a
// TypeError from its constructor, expecting
// `ServiceWorkerRegistration.showNotification()` instead — so route
// through a registered service worker when one is available rather than
// assuming the constructor works everywhere permission was granted.
const registration = 'serviceWorker' in navigator
? await navigator.serviceWorker.getRegistration()
: undefined;
if (registration) {
await registration.showNotification('Notifications enabled');
return;
}
try {
new Notification('Notifications enabled');
} catch {
// Fallback: no service worker registration and the constructor threw
// (e.g. most mobile browsers) — nothing more to show without a
// registered service worker.
}
}

MDN’s own requestPermission() example code comments that once a user has denied notifications, “there is no need to bother them any more” — don’t call requestPermission() again on a schedule hoping for a different answer. MDN’s own Using the Notifications API guide takes a lighter approach and keeps its enable button visible after a denial, so the user has a chance to change their mind later; either way, the request itself should not repeat automatically.

Push subscriptions are not exclusively behind a service worker

Section titled “Push subscriptions are not exclusively behind a service worker”

Per the Push API spec, a PushManager is available on both Window and ServiceWorkerRegistration: a Window’s PushManager has a null associated service worker registration, while a ServiceWorkerRegistration’s PushManager is tied to that registration. The spec also defines window-accessible push subscriptions and lets a declarative push message display a notification without going through a page’s own script. Requesting a push subscription via subscribe() is its own permission flow, distinct from the Notification permission described above.

Per MDN’s browser-compat data, Notification.permission and requestPermission() are Limited availability — not Baseline, because they don’t work the same way in some widely-used browsers. Desktop support is broad (Chrome 32+, Edge 14+, Firefox 22+, Safari 7+), and Chrome, Firefox, and Samsung Internet on Android support the permission property and requestPermission() just as fully. WebView on Android has no support for the permission API at all. Safari on iOS only exposes this to a web app the user has added to the Home Screen (16.4+), not to ordinary browser tabs. Requesting permission only from a user gesture is guidance MDN gives and some browsers already enforce, rather than a spec requirement.

Note that the separate Notification() constructor has its own Android restrictions on some browsers that are out of scope for this compatibility table, since they don’t apply to the permission property or requestPermission() documented here.

  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Desktop)Yes32mediumsource—
Chrome (Android)Yes42mediumsource—
Edge (Desktop)Yes14mediumsource—
Firefox (Desktop)Yes22mediumsource—
Firefox (Android)Yes22mediumsource—
Safari (macOS)Yes7mediumsource—
Safari (iOS)Partial16.4mediumsource1
Samsung InternetYes4.0mediumsource—
WebView (Android)No—mediumsource2
  1. Only for web apps added to the Home Screen; not available to ordinary browser tabs.
  2. Not supported per MDN browser-compat data.

Source data: /compatibility/notification-permission.json · Global usage: 67 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-09-08 · Confidence: medium (computed from sources)

See The Notifications API and Web Push for the per-browser availability of the APIs this permission gates.

  • Only call requestPermission() from inside a user gesture handler, never on page load.
  • Treat default the same as “not permitted yet” — don’t assume a request will succeed.
  • Once Notification.permission is denied, don’t call requestPermission() again on a schedule hoping for a different answer.
  • Feature-detect 'Notification' in window in page code before touching the API — per MDN, Notification.permission is also reachable from Web Workers, which have no window.
  • Don’t assume push subscription requires an active service worker registration — the Push API also defines a window-scoped path.