# Notification.requestPermission()

> How Notification.requestPermission() and Notification.permission work, the user-gesture rule Firefox 72 and Safari enforce, and Firefox's console messages.

`Notification.requestPermission()` asks the user whether the origin may show system
notifications and resolves with the resulting state; `Notification.permission` reads that state
at any time without a prompt. The same permission gates `ServiceWorkerRegistration.showNotification()`
and, because a push subscription requires it, `PushManager.subscribe()`, so this one prompt is
the gate for every notification path a PWA has.

## Syntax

```js
Notification.requestPermission()
Notification.permission
```

`requestPermission()` is a static method returning a `Promise<NotificationPermission>`, where the
string is `"default"`, `"granted"`, or `"denied"`. It also accepts a legacy callback argument,
`Notification.requestPermission(callback)`, which is the only form Safari supported before
version 15 (BCD `api.Notification.requestPermission_static`). `Notification.permission` is a
static read-only property with the same three values. Both exist in windows only; a worker has no
`Notification.requestPermission`, and a service worker reads the state through
`registration.pushManager.permissionState()` or `navigator.permissions.query({ name: 'notifications' })`.

## Parameters

| Parameter | Type | Meaning |
|---|---|---|
| `deprecatedCallback` | function, optional | Called with the permission string when the user decides. Kept for Safari 7 to 14 compatibility; new code awaits the promise instead. |

The three permission values are not symmetric. `"default"` means the user has not decided and
the browser behaves as if the answer were `"denied"`; `"denied"` means the user, or an enterprise
policy, has refused, and the prompt will not be shown again until the user changes the site
setting; `"granted"` is the only state in which `showNotification()` succeeds.

## Exceptions

`requestPermission()` does not throw for a refusal; it resolves with `"denied"`. The failures
below surface before the prompt.

| Failure | Where | When |
|---|---|---|
| `ReferenceError` | reading `Notification` | Safari on iOS and iPadOS leaves the interface undefined unless the page runs as a Home Screen web app whose manifest sets a non-default `display` (BCD note on `api.Notification`). Test `'Notification' in window` before touching the static. |
| Resolves `"denied"` without a prompt, with a console message | Firefox 72+ | The call did not run inside a user-generated event handler. Firefox logs `The Notification permission may only be requested from inside a short running user-generated event handler.` (`NotificationsRequireUserGesture` in [dom.properties](https://github.com/mozilla-firefox/firefox/blob/main/dom/locales/en-US/chrome/dom/dom.properties), github.com). |
| Resolves `"denied"` without a prompt, with a console message | Firefox 70+ | The call came from a cross-origin `<iframe>`: `The Notification permission may only be requested in a top-level document or same-origin iframe.` |
| Resolves `"denied"` without a prompt, with a console message | Firefox | The page is not a secure context: `The Notification permission may only be requested in a secure context.` |
| Prompt suppressed, state stays `"default"` | Chrome 80+ | Chrome's quieter permission UI replaces the prompt with a crossed-out bell icon in the address bar for users who usually block notifications and for sites with low acceptance rates ([Introducing quieter permission UI for notifications](https://blog.chromium.org/2020/01/introducing-quieter-permission-ui-for.html), blog.chromium.org); the promise resolves only when the user acts on the icon. |

Safari on macOS requires a user gesture as well and otherwise resolves `"denied"` without a
prompt.

## Examples

The examples separate asking (once, from a gesture) from reading (on every load) and end with the fallback for platforms that cannot show system notifications.

### Asking from a button after explaining why

The request runs inside the `click` handler, which satisfies Firefox 72 and Safari. The button
stays visible after a denial so the user can change the site setting and try again.

```js
const button = document.querySelector('#enable-alerts');

button.addEventListener('click', async () => {
  const state = await Notification.requestPermission();
  if (state === 'granted') {
    button.hidden = true;
    await showWelcomeNotification();
  } else {
    button.textContent = 'Notifications are blocked in your browser settings';
  }
});
```

Do not call `requestPermission()` on load or on a timer: Firefox resolves `"denied"` with the
console message above, and a denial in Chrome or Safari is remembered per site.

### Reading the state without prompting

`Notification.permission` is synchronous and does not prompt, which makes it the right check for
deciding whether to render the enable button at all. The Permissions API gives the same answer
asynchronously and also works inside a service worker.

```js
function notificationState() {
  if (!('Notification' in window)) return 'unsupported';
  return Notification.permission; // "default" | "granted" | "denied"
}

async function notificationStateInWorker() {
  const status = await navigator.permissions.query({ name: 'notifications' });
  return status.state; // "prompt" | "granted" | "denied"
}
```

The Permissions API spells the undecided state `"prompt"` where `Notification.permission`
says `"default"`; the two words mean the same thing.

### Detecting support and falling back to in-page alerts

On iOS the interface is absent in a browser tab, in Android WebView it is absent entirely, and
on every platform the user may have denied. One function covers the three cases and returns a
notifier the rest of the app can call.

```js
async function getNotifier() {
  if (!('Notification' in window) || !('serviceWorker' in navigator)) {
    return showInPageToast; // no system notifications here: render inside the page
  }
  if (Notification.permission !== 'granted') {
    return showInPageToast; // undecided or denied: do not prompt from here
  }
  const registration = await navigator.serviceWorker.getRegistration();
  if (!registration?.active) return showInPageToast;
  return (title, body) => registration.showNotification(title, { body });
}
```

The in-page toast is the fallback in every branch, so the calling code has one signature
whatever the platform answered.

:::observed
Firefox (English UI) prints three distinct messages to the Console for a refused request, each a
string in [dom.properties](https://github.com/mozilla-firefox/firefox/blob/main/dom/locales/en-US/chrome/dom/dom.properties)
(github.com): `The Notification permission may only be requested from inside a short running
user-generated event handler.` when the call is outside a gesture (Firefox 72+), `The Notification
permission may only be requested in a top-level document or same-origin iframe.` from a cross-origin
frame (Firefox 70+), and `The Notification permission may only be requested in a secure context.`
on plain HTTP. In each case the promise resolves `"denied"` and no doorhanger appears.
:::

## See also

- [Notifications API Standard: requestPermission()](https://notifications.spec.whatwg.org/#dom-notification-requestpermission) (whatwg.org)
- [Notification: requestPermission() static method](https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission_static) (developer.mozilla.org)
- [Notifications API](/reference/notifications/notifications-api/)
- [Web Push and PushManager.subscribe()](/reference/notifications/web-push/)
- [Web Push on iOS and iPadOS](/reference/notifications/ios-safari-push/)