Controlling what happens when notifications arrive
Visual options decide how a notification looks, but the behavior — what happens after it's shown — is governed by a separate set of options. By default, showNotification() with only visual parameters produces a few predictable outcomes: clicking the notification does nothing (it doesn't even close), each notification stacks separately rather than replacing older ones, the platform may play sound or vibrate, and visibility duration varies by platform (Android keeps them until dismissed, desktop auto-hides them).
These defaults can all be changed with a handful of options: tag, renotify, silent, and requireInteraction. Handling clicks and action buttons requires adding a listener in the service worker.
Listening for notification clicks
To make a notification do something when clicked — and to close it — register a 'notificationclick' event listener in the service worker:
self.addEventListener('notificationclick', (event) => {
const clickedNotification = event.notification;
clickedNotification.close();
// Do something as the result of the notification click
const promiseChain = doSomething();
event.waitUntil(promiseChain);
});
The clicked notification is exposed as event.notification, giving you access to its properties and methods. The example above closes it and then executes additional logic, such as opening a window or making an API call.
Action buttons and inline replies
Action buttons provide a second layer of interaction beyond a simple click. In the notificationclick event, the event.action property contains the action value you specified when calling showNotification(). For example, with the following options:
const title = 'Actions Notification';
const options = {
actions: [
{
action: 'coffee-action',
title: 'Coffee',
type: 'button',
icon: '/images/demos/action-1-128x128.png',
},
{
action: 'doughnut-action',
type: 'button',
title: 'Doughnut',
icon: '/images/demos/action-2-128x128.png',
},
{
action: 'gramophone-action',
type: 'button',
title: 'Gramophone',
icon: '/images/demos/action-3-128x128.png',
},
{
action: 'atom-action',
type: 'button',
title: 'Atom',
icon: '/images/demos/action-4-128x128.png',
},
],
};
registration.showNotification(title, options);
...the event.action value will be one of 'coffee-action', 'doughnut-action', 'gramophone-action', or 'atom-action'. You can branch on that value to handle both plain clicks and action-button clicks:
self.addEventListener('notificationclick', (event) => {
if (!event.action) {
// Was a normal notification click
console.log('Notification Click.');
return;
}
switch (event.action) {
case 'coffee-action':
console.log("User ❤️️'s coffee.");
break;
case 'doughnut-action':
console.log("User ❤️️'s doughnuts.");
break;
case 'gramophone-action':
console.log("User ❤️️'s music.");
break;
case 'atom-action':
console.log("User ❤️️'s science.");
break;
default:
console.log(`Unknown action clicked: '${event.action}'`);
break;
}
});
If you enable an inline reply on a notification action:
const title = 'Poll';
const options = {
body: 'Do you like this photo?',
image: '/images/demos/cat-image.jpg',
icon: '/images/demos/icon-512x512.png',
badge: '/images/demos/badge-128x128.png',
actions: [
{
action: 'yes',
type: 'button',
title: '👍 Yes',
},
{
action: 'no',
type: 'text',
title: '👎 No (explain why)',
placeholder: 'Type your explanation here',
},
],
};
registration.showNotification(title, options);
...the text the user typed is available in event.reply:
self.addEventListener('notificationclick', (event) => {
const reply = event.reply;
// Do something with the user's reply
const promiseChain = doSomething(reply);
event.waitUntil(promiseChain);
});
Grouping and replacing with tag
The tag option is a string identifier that groups notifications. Its practical effect is best shown by example. Display a notification tagged 'message-group-1':
const title = 'Notification 1 of 3';
const options = {
body: "With 'tag' of 'message-group-1'",
tag: 'message-group-1',
};
registration.showNotification(title, options);

Then display one with a different tag, 'message-group-2':
const title = 'Notification 2 of 3';
const options = {
body: "With 'tag' of 'message-group-2'",
tag: 'message-group-2',
};
registration.showNotification(title, options);

Now show a third notification, reusing the original tag 'message-group-1'. The first matching notification closes and is replaced by this new one:
const title = 'Notification 3 of 3';
const options = {
body: "With 'tag' of 'message-group-1'",
tag: 'message-group-1',
};
registration.showNotification(title, options);
Even though showNotification() ran three times, the user ends up with two visible notifications:

In short, tag means: if any currently displayed notification has the same tag as a newly shown one, the old one is closed first.
Renotify, silent, and requireInteraction
A key detail about tag: replacing a notification happens silently — no sound or vibration. To change that, use renotify. This is primarily relevant on mobile devices, where setting renotify: true makes the replacement vibrate and play a system sound. Chat applications are a typical use case:
const title = 'Notification 2 of 2';
const options = {
tag: 'renotify',
renotify: true,
};
registration.showNotification(title, options);
The opposite control is silent. Setting it to true suppresses vibration, sound, and even turning on the device display. This suits notifications that don't require immediate attention:
const title = 'Silent Notification';
const options = {
silent: true,
};
registration.showNotification(title, options);
One more platform difference: on desktop Chrome a notification auto-hides after a period of time, while on Android it remains until the user dismisses it. The requireInteraction option forces a notification to stay visible on all platforms until the user clicks or closes it:
const title = 'Require Interaction Notification';
const options = {
requireInteraction: true,
};
registration.showNotification(title, options);
Use this sparingly. Forcing users to stop and dismiss a notification can be intrusive, so reserve requireInteraction for cases where a response is genuinely required.



