Chrome Notifications API Complete Reference
15 min readChrome Notifications API Reference
The chrome.notifications API lets you create rich desktop notifications using templates. These are system-level notifications that appear outside the browser window.
Permissions
{
"permissions": ["notifications"]
}
No user-facing warning for this permission. The OS may prompt users to allow notifications from Chrome separately.
See the notifications permission reference for details.
Notification Templates
Chrome supports four notification template types:
basic
Simple notification with icon, title, and message.
chrome.notifications.create("basic-example", {
type: "basic",
iconUrl: "icon128.png",
title: "Hello",
message: "This is a basic notification",
});
image
Like basic, but with a large image below the message.
chrome.notifications.create("image-example", {
type: "image",
iconUrl: "icon128.png",
title: "New Photo",
message: "Check out this image",
imageUrl: "screenshot.png",
});
list
Shows a list of items with titles and messages.
chrome.notifications.create("list-example", {
type: "list",
iconUrl: "icon128.png",
title: "3 New Emails",
message: "You have unread messages",
items: [
{ title: "Alice", message: "Hey, are you free tomorrow?" },
{ title: "Bob", message: "Meeting notes attached" },
{ title: "Carol", message: "PR review requested" },
],
});
progress
Shows a progress bar.
chrome.notifications.create("progress-example", {
type: "progress",
iconUrl: "icon128.png",
title: "Downloading...",
message: "file.zip",
progress: 45, // 0–100
});
NotificationOptions
| Property | Type | Required | Description |
|---|---|---|---|
type |
TemplateType |
Yes | "basic", "image", "list", "progress" |
iconUrl |
string |
Yes | Icon for the notification (relative to extension root) |
title |
string |
Yes | Primary title text |
message |
string |
Yes | Body text |
contextMessage |
string |
No | Additional context text (shown smaller) |
priority |
number |
No | -2 to 2 (default 0). Higher = more prominent |
eventTime |
number |
No | Timestamp for the notification |
buttons |
ButtonOptions[] |
No | Up to 2 action buttons |
imageUrl |
string |
No | Large image (type: "image" only) |
items |
ItemOptions[] |
No | List items (type: "list" only) |
progress |
number |
No | 0–100 (type: "progress" only) |
requireInteraction |
boolean |
No | Stay visible until user dismisses |
silent |
boolean |
No | Suppress sound |
Buttons
Notifications support up to 2 action buttons:
chrome.notifications.create("with-buttons", {
type: "basic",
iconUrl: "icon128.png",
title: "New Update Available",
message: "Version 2.0 is ready to install",
buttons: [
{ title: "Update Now" },
{ title: "Later" },
],
requireInteraction: true,
});
Core Methods
chrome.notifications.create(notificationId?, options)
Create and display a notification.
// With explicit ID (allows updating/clearing later)
const id = await chrome.notifications.create("my-notification", {
type: "basic",
iconUrl: "icon128.png",
title: "Title",
message: "Message body",
});
// Auto-generated ID
const id = await chrome.notifications.create({
type: "basic",
iconUrl: "icon128.png",
title: "Title",
message: "Message body",
});
console.log("Notification ID:", id);
chrome.notifications.update(notificationId, options)
Update an existing notification.
// Update progress
await chrome.notifications.update("progress-example", {
progress: 75,
message: "75% complete...",
});
// Update message
await chrome.notifications.update("my-notification", {
message: "Updated message content",
});
Returns true if the notification existed and was updated, false otherwise.
chrome.notifications.clear(notificationId)
Dismiss a notification.
const wasClosed = await chrome.notifications.clear("my-notification");
chrome.notifications.getAll()
Get all currently visible notifications.
const notifications = await chrome.notifications.getAll();
// Returns Record<string, boolean> — keys are notification IDs
const activeIds = Object.keys(notifications);
console.log(`${activeIds.length} active notifications`);
chrome.notifications.getPermissionLevel()
Check if the user has allowed notifications.
const level = await chrome.notifications.getPermissionLevel();
// "granted" or "denied"
if (level === "denied") {
console.log("User has disabled notifications for Chrome");
}
Events
chrome.notifications.onClicked
User clicked the notification body.
chrome.notifications.onClicked.addListener((notificationId) => {
console.log("Notification clicked:", notificationId);
// Common pattern: open a page
chrome.tabs.create({ url: "https://example.com" });
chrome.notifications.clear(notificationId);
});
chrome.notifications.onButtonClicked
User clicked one of the notification’s action buttons.
chrome.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => {
if (notificationId === "update-available") {
if (buttonIndex === 0) {
// "Update Now" clicked
performUpdate();
} else {
// "Later" clicked
snoozeUpdate();
}
}
chrome.notifications.clear(notificationId);
});
chrome.notifications.onClosed
User dismissed the notification (or it expired).
chrome.notifications.onClosed.addListener((notificationId, byUser) => {
console.log(`Notification ${notificationId} closed`);
console.log(byUser ? "Dismissed by user" : "Closed programmatically or expired");
});
chrome.notifications.onPermissionLevelChanged
User changed notification permissions at the OS level.
chrome.notifications.onPermissionLevelChanged.addListener((level) => {
console.log("Permission level changed to:", level);
});
Using with @theluckystrike/webext-messaging
Notification system controlled from the popup:
// shared/messages.ts
type Messages = {
sendNotification: {
request: { title: string; message: string; requireInteraction?: boolean };
response: { notificationId: string };
};
getActiveNotifications: {
request: void;
response: string[];
};
clearAllNotifications: {
request: void;
response: { cleared: number };
};
};
// background.ts
import { createMessenger } from "@theluckystrike/webext-messaging";
const msg = createMessenger<Messages>();
msg.onMessage({
sendNotification: async ({ title, message, requireInteraction }) => {
const notificationId = await chrome.notifications.create({
type: "basic",
iconUrl: "icon128.png",
title,
message,
requireInteraction: requireInteraction || false,
});
return { notificationId };
},
getActiveNotifications: async () => {
const all = await chrome.notifications.getAll();
return Object.keys(all);
},
clearAllNotifications: async () => {
const all = await chrome.notifications.getAll();
const ids = Object.keys(all);
await Promise.all(ids.map((id) => chrome.notifications.clear(id)));
return { cleared: ids.length };
},
});
Using with @theluckystrike/webext-storage
Track notification preferences and history:
import { defineSchema, createStorage } from "@theluckystrike/webext-storage";
const schema = defineSchema({
notificationPrefs: {
enabled: true,
silent: false,
maxPerHour: 10,
},
notificationLog: [] as Array<{
id: string;
title: string;
timestamp: number;
clicked: boolean;
}>,
});
const storage = createStorage({ schema, area: "local" });
async function showNotification(title: string, message: string) {
const prefs = await storage.get("notificationPrefs");
if (!prefs.enabled) return null;
// Rate limiting
const log = await storage.get("notificationLog");
const oneHourAgo = Date.now() - 60 * 60 * 1000;
const recentCount = log.filter((n) => n.timestamp > oneHourAgo).length;
if (recentCount >= prefs.maxPerHour) return null;
const id = await chrome.notifications.create({
type: "basic",
iconUrl: "icon128.png",
title,
message,
silent: prefs.silent,
});
log.push({ id, title, timestamp: Date.now(), clicked: false });
await storage.set("notificationLog", log.slice(-100)); // keep last 100
return id;
}
chrome.notifications.onClicked.addListener(async (notificationId) => {
const log = await storage.get("notificationLog");
const entry = log.find((n) => n.id === notificationId);
if (entry) {
entry.clicked = true;
await storage.set("notificationLog", log);
}
});
Common Patterns
Notification with click-to-open
const urlMap = new Map<string, string>();
function notifyWithLink(title: string, message: string, url: string) {
const id = `link-${Date.now()}`;
urlMap.set(id, url);
chrome.notifications.create(id, {
type: "basic",
iconUrl: "icon128.png",
title,
message,
});
}
chrome.notifications.onClicked.addListener((id) => {
const url = urlMap.get(id);
if (url) {
chrome.tabs.create({ url });
urlMap.delete(id);
}
chrome.notifications.clear(id);
});
Progress notification for long tasks
async function processWithProgress(items: string[]) {
const notifId = "processing";
await chrome.notifications.create(notifId, {
type: "progress",
iconUrl: "icon128.png",
title: "Processing...",
message: `0 of ${items.length}`,
progress: 0,
});
for (let i = 0; i < items.length; i++) {
await processItem(items[i]);
const pct = Math.round(((i + 1) / items.length) * 100);
await chrome.notifications.update(notifId, {
progress: pct,
message: `${i + 1} of ${items.length}`,
});
}
await chrome.notifications.clear(notifId);
await chrome.notifications.create("done", {
type: "basic",
iconUrl: "icon128.png",
title: "Complete",
message: `Processed ${items.length} items`,
});
}
Auto-dismiss after timeout
async function showTemporaryNotification(title: string, message: string, durationMs: number) {
const id = await chrome.notifications.create({
type: "basic",
iconUrl: "icon128.png",
title,
message,
});
// Use alarms instead of setTimeout (survives SW termination)
await chrome.alarms.create(`dismiss-${id}`, {
delayInMinutes: durationMs / 60000,
});
}
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name.startsWith("dismiss-")) {
const notifId = alarm.name.replace("dismiss-", "");
chrome.notifications.clear(notifId);
}
});
Gotchas
-
iconUrlis required and must be a relative path to a file bundled with your extension, or a data URL. Remote URLs are not allowed. -
Maximum 2 buttons. You cannot add more than 2 action buttons.
-
OS may suppress notifications. macOS, Windows, and ChromeOS all have notification settings that can prevent notifications from appearing. Always check
getPermissionLevel(). -
requireInteractionmay be ignored on some platforms. macOS in particular may still auto-dismiss notifications regardless of this setting. -
Notification IDs are strings. If you omit the ID, Chrome generates a UUID. Use explicit IDs when you need to update or clear notifications later.
-
listandimagetypes have limited support. On some platforms (especially macOS), list items and images may not render as expected. Thebasictype is the most reliable cross-platform. -
Notifications don’t persist across browser restarts. All notifications are cleared when Chrome is closed. Use alarms + storage to re-show them if needed.
-
update()returns false if the notification has already been dismissed. Don’t rely on update succeeding.
Related
- notifications permission
- Alarms API
- Runtime API
- Chrome notifications API docs
Frequently Asked Questions
How do I show a notification?
Use chrome.notifications.create() with an ID, notification options, and optional callback.
Can I add action buttons to notifications?
Yes, include an array of “buttons” in your notification options to add clickable action buttons.
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.