Working with Browser Notifications
The Notifications API lets a web page display system-level notifications via the Notification interface. Its basic workflow is straightforward: check the current permission state, request permission if it hasn't been decided yet, and then fire off a notification once you have granted permission.
Permission States and the Initial Setup
A site's permission state lives in the read-only Notification.permission property. There are three possible values:
default—the user hasn't made a choice; permission must be requested.granted—the user has allowed notifications.denied—the user has blocked them.
A simple way to report the current state is to call a helper function like showPermission() on page load and log the value of Notification.permission to the console.
function showPermission() {
const permission = Notification.permission;
console.log('Permission:', permission);
document.querySelector('output').textContent = permission;
}
Requesting Permission
To ask the user for permission, you call Notification.requestPermission(). This method triggers a popup where the user can allow or block notifications for your site. It returns a promise that resolves with the resulting permission string.
async function requestPermission() {
const permission = await Notification.requestPermission();
console.log('Permission:', permission);
}
The user's response shapes what happens next:
- Allow:
Notification.permissionbecomesgranted, and the site can show notifications. Future calls torequestPermission()resolve tograntedwithout a popup. - Block:
Notification.permissionbecomesdenied, and the site can't show notifications. Future calls resolve todeniedwithout a popup. - Dismissal: The permission remains
default, and the popup will show again on future calls. If this keeps happening, the browser may decide to block the site entirely and setNotification.permissiontodenied.
On that last point, notice that browser behavior on repeatedly dismissed popups is still evolving. The practical implication is to always request permission in response to a user gesture—a button click, for example—so the request feels expected rather than intrusive.
Sending a Notification
To display a notification after you have permission, you instantiate a new Notification object. The constructor takes two arguments: a required title string and an optional options object that defines body text, icons, vibration patterns, and other data.
function sendNotification() {
const title = 'Test title';
const options = {
body: 'Test body',
icon: 'images/icon.png',
badge: 'images/badge.png'
};
new Notification(title, options);
}
If the user hasn't granted permission, the constructor will throw an error. You can observe this failure mode by attaching an onerror event handler:
const notification = new Notification(title, options);
notification.onerror = (error) => {
console.log('ERROR ALERT', 'Could not send notification');
console.log(error);
};
To reproduce the error:
- Reset the site's notification permission to its default by clicking the lock icon in the URL bar.
- Click the Request permission button and select Block on the popup.
- Click Send notification. The
onerrorhandler logsCould not send notificationand the event object to the console.
Useful Notification Options
Modern platforms support a rich set of fields in the options object. These include body text, icons for different contexts, actions with unique IDs, badge icons for mobile, a silent flag, and vibration patterns. Keep in mind that browsers and devices implement these options differently, so testing your implementation across a few environments is worthwhile before shipping it.
The complete field set, as documented on MDN, looks like this:
{
body: '',
dir: 'auto',
lang: '',
tag: '',
icon: '',
badge: '',
image: '',
data: {},
vibrate: [],
renotify: false,
requireInteraction: false,
silent: false,
timestamp: 0,
actions: [
{ action: '', title: '', icon: '' }
]
}
One useful strategy when you only need to support the fundamentals is to bind the permission request to a specific button click. Not only does that give the user context for the popup, it also avoids triggering the browser's dismissal-heuristic fallback logic in the first place.



