# Notification interface

> The Notification() constructor and its object: instance properties, the click, close, and error events, and why mobile browsers throw on the constructor.

`Notification` is the object that represents one displayed notification. `new Notification(title,
options)` creates and immediately shows a non-persistent notification from a page or dedicated
worker, and the same interface is handed to a service worker as `event.notification` for
persistent ones. Its instance properties mirror the options it was created with, and its events
report display, activation, dismissal, and failure.

## Syntax

```js
new Notification(title)
new Notification(title, options)
notification.close()
Notification.permission
Notification.maxActions
```

The constructor is exposed on `Window` and in dedicated and shared workers, and throws in a
`ServiceWorkerGlobalScope`, where `self.registration.showNotification()` is the replacement.
Desktop support is Chrome 20, Firefox 22, Safari 7, Edge 14 (BCD `api.Notification.Notification`).
On mobile the constructor exists but is unusable in Chrome for Android, Samsung Internet, and
Safari on iOS (details under Exceptions), so a PWA treats it as a desktop-only convenience.

## Members

Instance members are read-only and reflect the constructor's `options`.

| Member | Type | Meaning |
|---|---|---|
| `title`, `body`, `icon`, `image`, `badge` | string | The texts and image URLs passed at creation. Missing options read as `""`. |
| `tag` | string | Replacement key. A later notification with the same `tag` from the same origin replaces this one. |
| `data` | any | The structured-cloneable payload passed as `options.data`; `null` when none. |
| `dir`, `lang` | string | Text direction (`"auto"`, `"ltr"`, `"rtl"`) and BCP 47 language tag. |
| `requireInteraction`, `renotify`, `silent` | boolean (`silent` may be `null`) | Behaviour flags; see the [Notifications API](/reference/notifications/notifications-api/) parameter table for engine support. |
| `timestamp` | number | The time the notification is about, in milliseconds since the epoch; defaults to creation time. |
| `vibrate` | array of numbers | Vibration pattern, Chromium on Android. |
| `actions` | array | Always empty on a non-persistent notification; populated on `event.notification` in a service worker. |
| `navigate` | string | URL opened on activation, Safari 18.4. |

Events are `show` (displayed; not fired by Safari on iOS), `click`, `close`, and `error`
(display failed, for example because permission was revoked between the check and the call).
The static `Notification.permission` is covered in
[Notification.requestPermission()](/reference/notifications/permissions/); `Notification.maxActions`
returns the number of action buttons the engine will display, `2` in Chromium
(`kMaximumActions` in [notification.mojom](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/public/mojom/notifications/notification.mojom),
chromium.googlesource.com) and `undefined` in Safari, where the static is absent.

## Exceptions

| Exception | When |
|---|---|
| `TypeError` | The constructor is called in a service worker. Chromium's message is `Illegal constructor. Use ServiceWorkerRegistration.showNotification() instead.` ([notification.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/notifications/notification.cc), chromium.googlesource.com). |
| `TypeError` | The constructor is called in Chrome for Android or Samsung Internet, from any context; BCD records that the constructor always throws there (`api.Notification.Notification`, `chrome_android`). The message is the same `Illegal constructor` text. |
| `TypeError` | `options.actions` is non-empty. Chromium's message is `Actions are only supported for persistent notifications shown using ServiceWorkerRegistration.showNotification().` |
| `TypeError` | `renotify` is `true` with an empty `tag`, or `silent` is `true` with `vibrate` set. |
| `ReferenceError` | Safari on iOS and iPadOS in a browser tab: the `Notification` identifier does not exist unless the page is a Home Screen web app with a non-default manifest `display`. |

A denied or undecided permission is not an exception. The constructor returns a `Notification`
whose `error` event fires and nothing is displayed, which is why `Notification.permission` is
checked before the call.

## Platform availability

The interface is universal on desktop and absent or constructor-less on mobile: Chrome for
Android 42 and Samsung Internet 4 expose it for service worker use only, Safari on iOS 16.4
exposes it only inside installed web apps, and Android WebView has none (BCD
`api.Notification`). Chrome from version 49 does not show notifications in Incognito. The
instance members and events are supported wherever the interface is, with `show` missing on iOS
and `actions`, `badge`, `image`, `renotify`, `timestamp`, and `vibrate` Chromium-only.

## Examples

The first example is desktop-only by nature; the second and third are the shapes that also work on Android and iOS.

### Showing a non-persistent notification on desktop

With permission already granted, the constructor displays immediately. The `click` handler
focuses the window and closes the notification; without `close()` the OS keeps it until its own
timeout.

```js
function showOrderUpdate(orderId) {
  const notification = new Notification('Order shipped', {
    body: `Order #${orderId} is on its way.`,
    icon: '/icons/parcel.png',
    tag: `order-${orderId}`,
    data: { orderId },
  });
  notification.addEventListener('click', () => {
    window.focus();
    location.hash = `#order-${notification.data.orderId}`;
    notification.close();
  });
  notification.addEventListener('error', () => console.warn('Notification not shown'));
}
```

The browser closes a non-persistent notification on its own a few seconds after display and
keeps it out of the notification centre, so this shape suits "saved" confirmations rather than
messages the user returns to.

### Reading a persistent notification in the service worker

In a `notificationclick` handler the same interface arrives as `event.notification`, now with
`actions` populated and `close()` available.

```js
self.addEventListener('notificationclick', (event) => {
  const { tag, data, actions } = event.notification;
  event.notification.close();
  if (event.action === 'archive') {
    event.waitUntil(fetch(`/api/threads/${data.threadId}/archive`, { method: 'POST' }));
    return;
  }
  event.waitUntil(self.clients.openWindow(`/inbox#${tag}`));
});
```

`actions.length` here is at most `Notification.maxActions`; buttons beyond that limit were
dropped silently when the notification was shown.

### Detecting whether the constructor is usable

The reliable probe is to construct inside `try` after confirming the interface and permission.
Any failure routes to the persistent path, which is also the only path on Android and iOS.

```js
async function show(title, options) {
  if (!('Notification' in window) || Notification.permission !== 'granted') {
    return showInPage(title, options.body); // no interface, or no permission
  }
  try {
    return new Notification(title, options);
  } catch (err) {
    if (err.name !== 'TypeError') throw err;
    const registration = await navigator.serviceWorker.getRegistration();
    if (!registration?.active) return showInPage(title, options.body); // nothing to fall back to
    return registration.showNotification(title, options);
  }
}
```

Production code usually skips the constructor entirely and calls `showNotification()` on both
desktop and mobile; the probe is useful where a service worker is not registered.

:::observed
In Chrome for Android, `new Notification('x')` from the Console throws `Uncaught TypeError: Failed
to construct 'Notification': Illegal constructor. Use ServiceWorkerRegistration.showNotification()
instead.`, the string at the top of Chromium's
[notification.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/notifications/notification.cc)
(chromium.googlesource.com); the same source throws `Actions are only supported for persistent
notifications shown using ServiceWorkerRegistration.showNotification().` on desktop when
`options.actions` is non-empty. `Notification.maxActions` evaluates to `2` in Chrome on every
platform, the value of `kMaximumActions` in `notification.mojom`.
:::

## See also

- [Notifications API Standard: Notification interface](https://notifications.spec.whatwg.org/#notification) (whatwg.org)
- [Notification: Notification() constructor](https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification) (developer.mozilla.org)
- [Notifications API](/reference/notifications/notifications-api/)
- [Notification.requestPermission()](/reference/notifications/permissions/)
- [Notification actions and badge options](/reference/notifications/notification-actions-badge/)