Notifications API
What are Browser Notifications?
The Notifications API allows you to display system notifications outside the browser window, even when the page isn’t visible. Perfect for chat apps, reminders, and real-time updates.
if ('Notification' in window) {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
new Notification('Hello!', {
body: 'This is a browser notification',
icon: '/icon.png'
});
}
});
}
Requirements:
- HTTPS (or localhost)
- User permission
- Service Worker (for background notifications)
Checking for Support
function supportsNotifications() {
return 'Notification' in window;
}
if (supportsNotifications()) {
console.log('Notifications supported');
} else {
console.log('Notifications not supported');
// Show fallback (e.g., in-page alerts)
}
Permission States
Notifications require explicit user permission:
console.log(Notification.permission);
// 'default' - not asked yet
// 'granted' - user allowed
// 'denied' - user blocked
Requesting Permission
Basic Request
async function requestNotificationPermission() {
if (!('Notification' in window)) {
console.log('Notifications not supported');
return 'unsupported';
}
if (Notification.permission === 'granted') {
return 'granted';
}
if (Notification.permission === 'denied') {
return 'denied';
}
// Request permission
const permission = await Notification.requestPermission();
return permission;
}
// Usage
const permission = await requestNotificationPermission();
if (permission === 'granted') {
console.log('Permission granted!');
} else {
console.log('Permission denied');
}
User-Friendly Permission Flow
async function askForNotificationPermission() {
// Don't ask if already decided
if (Notification.permission !== 'default') {
return Notification.permission;
}
// Show explanation modal first
const userWants = await showPermissionModal({
title: 'Enable Notifications',
message: 'Get notified about new messages and important updates. You can change this anytime in settings.'
});
if (!userWants) {
return 'default';
}
// Request permission
const permission = await Notification.requestPermission();
if (permission === 'granted') {
showSuccessMessage('Notifications enabled!');
} else {
showInfoMessage('You can enable notifications later in settings');
}
return permission;
}
Creating Notifications
Basic Notification
function showBasicNotification() {
if (Notification.permission === 'granted') {
new Notification('Hello!');
}
}
With Options
function showNotification() {
if (Notification.permission === 'granted') {
const options = {
body: 'You have 3 new messages',
icon: '/images/icon.png',
badge: '/images/badge.png',
image: '/images/banner.png',
tag: 'message-notification', // Unique ID (replaces existing)
requireInteraction: false, // Auto-dismiss
silent: false, // Play sound
vibrate: [200, 100, 200], // Vibration pattern (ms)
data: { userId: 123 }, // Custom data
actions: [ // Action buttons (requires Service Worker)
{ action: 'reply', title: 'Reply' },
{ action: 'close', title: 'Close' }
]
};
const notification = new Notification('New Messages', options);
}
}
Notification Events
function createInteractiveNotification() {
const notification = new Notification('New Message', {
body: 'Alice: Hey, are you there?',
icon: '/alice-avatar.png',
tag: 'message-1',
data: { messageId: 42, sender: 'alice' }
});
// Clicked
notification.onclick = (event) => {
event.preventDefault(); // Prevent default browser behavior
window.focus();
openChatWindow(notification.data.sender);
notification.close();
};
// Closed
notification.onclose = () => {
console.log('Notification closed');
};
// Error
notification.onerror = () => {
console.error('Notification error');
};
// Shown
notification.onshow = () => {
console.log('Notification displayed');
};
// Auto-close after 5 seconds
setTimeout(() => {
notification.close();
}, 5000);
}
Notification Options
Tag (Replace Existing)
// First notification
new Notification('Downloading...', {
tag: 'download-status',
body: '0% complete'
});
// This REPLACES the previous notification (same tag)
setTimeout(() => {
new Notification('Downloading...', {
tag: 'download-status',
body: '50% complete'
});
}, 2000);
setTimeout(() => {
new Notification('Download Complete!', {
tag: 'download-status',
body: 'Your file is ready'
});
}, 4000);
Icons and Images
new Notification('Photo Uploaded', {
body: 'vacation.jpg uploaded successfully',
icon: '/icon-72x72.png', // Small icon (next to title)
badge: '/badge-96x96.png', // Monochrome badge (Android)
image: '/vacation-preview.jpg' // Large image (below text)
});
Vibration Pattern
// Vibrate: 200ms on, 100ms off, 200ms on
new Notification('Reminder', {
body: 'Meeting in 5 minutes',
vibrate: [200, 100, 200]
});
// Custom pattern
new Notification('Alert!', {
vibrate: [100, 50, 100, 50, 100, 50, 100]
});
Practical Examples
1. Message Notification System
class MessageNotifier {
constructor() {
this.notifications = new Map();
}
async init() {
const permission = await requestNotificationPermission();
return permission === 'granted';
}
showNewMessage(sender, message) {
if (Notification.permission !== 'granted') return;
const notification = new Notification(`New message from ${sender}`, {
body: message,
icon: `/avatars/${sender}.png`,
tag: `msg-${sender}`, // One notification per sender
requireInteraction: true,
data: { sender, message }
});
notification.onclick = () => {
window.focus();
this.openChat(sender);
notification.close();
};
this.notifications.set(sender, notification);
}
updateUnreadCount(sender, count) {
const existingNotification = this.notifications.get(sender);
if (existingNotification) {
existingNotification.close();
}
this.showNewMessage(sender, `${count} unread messages`);
}
clearNotification(sender) {
const notification = this.notifications.get(sender);
if (notification) {
notification.close();
this.notifications.delete(sender);
}
}
openChat(sender) {
window.location.href = `/chat/${sender}`;
}
}
// Usage
const notifier = new MessageNotifier();
await notifier.init();
// New message received
notifier.showNewMessage('Alice', 'Hey, are you free?');
// More messages from Alice
notifier.updateUnreadCount('Alice', 3);
2. Reminder System
class ReminderSystem {
constructor() {
this.reminders = [];
}
async addReminder(title, message, delayMs) {
const id = Date.now();
const timeoutId = setTimeout(() => {
this.showReminder(id, title, message);
}, delayMs);
this.reminders.push({ id, title, message, timeoutId });
return id;
}
showReminder(id, title, message) {
if (Notification.permission !== 'granted') return;
const notification = new Notification(title, {
body: message,
icon: '/reminder-icon.png',
requireInteraction: true,
tag: `reminder-${id}`,
actions: [
{ action: 'snooze', title: 'Snooze 5 min' },
{ action: 'dismiss', title: 'Dismiss' }
]
});
notification.onclick = () => {
this.handleReminderClick(id);
notification.close();
};
// Remove from active reminders
this.reminders = this.reminders.filter(r => r.id !== id);
}
handleReminderClick(id) {
console.log('Reminder clicked:', id);
window.focus();
}
cancelReminder(id) {
const reminder = this.reminders.find(r => r.id === id);
if (reminder) {
clearTimeout(reminder.timeoutId);
this.reminders = this.reminders.filter(r => r.id !== id);
}
}
}
// Usage
const reminders = new ReminderSystem();
// Set reminder for 1 minute from now
await reminders.addReminder(
'Meeting',
'Team standup in 1 minute',
60000
);
3. Download Progress
class DownloadNotifier {
constructor() {
this.activeDownloads = new Map();
}
startDownload(fileId, filename) {
const notification = new Notification('Download Started', {
body: filename,
tag: `download-${fileId}`,
icon: '/download-icon.png'
});
this.activeDownloads.set(fileId, { notification, filename });
}
updateProgress(fileId, percent) {
const download = this.activeDownloads.get(fileId);
if (!download) return;
// Close old notification
download.notification.close();
// Show updated progress
const notification = new Notification('Downloading...', {
body: `${download.filename} - ${percent}%`,
tag: `download-${fileId}`,
icon: '/download-icon.png',
silent: true // Don't make sound for progress updates
});
download.notification = notification;
}
completeDownload(fileId) {
const download = this.activeDownloads.get(fileId);
if (!download) return;
download.notification.close();
const notification = new Notification('Download Complete', {
body: `${download.filename} is ready`,
tag: `download-${fileId}`,
icon: '/success-icon.png',
requireInteraction: true
});
notification.onclick = () => {
this.openDownloadFolder();
notification.close();
};
this.activeDownloads.delete(fileId);
}
openDownloadFolder() {
// Open downloads folder or file
console.log('Opening downloads');
}
}
// Usage
const downloader = new DownloadNotifier();
downloader.startDownload(1, 'vacation-photos.zip');
downloader.updateProgress(1, 25);
downloader.updateProgress(1, 50);
downloader.updateProgress(1, 75);
downloader.completeDownload(1);
4. Real-Time Updates
class UpdateNotifier {
constructor() {
this.lastNotificationTime = 0;
this.minInterval = 60000; // Minimum 1 minute between notifications
}
canShowNotification() {
const now = Date.now();
return (now - this.lastNotificationTime) >= this.minInterval;
}
showUpdate(title, message, type = 'info') {
if (Notification.permission !== 'granted') return;
if (!this.canShowNotification()) return; // Rate limiting
const icons = {
info: '/info-icon.png',
warning: '/warning-icon.png',
error: '/error-icon.png',
success: '/success-icon.png'
};
const notification = new Notification(title, {
body: message,
icon: icons[type] || icons.info,
tag: `update-${type}`,
requireInteraction: type === 'error'
});
this.lastNotificationTime = Date.now();
return notification;
}
showSystemUpdate(version) {
return this.showUpdate(
'Update Available',
`Version ${version} is ready to install`,
'info'
);
}
showError(message) {
return this.showUpdate(
'Error Occurred',
message,
'error'
);
}
}
// Usage
const updater = new UpdateNotifier();
updater.showSystemUpdate('2.0.0');
Service Worker Notifications
For persistent notifications (work even when page is closed):
// In your main page
async function showPersistentNotification() {
if ('serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.ready;
await registration.showNotification('Persistent Notification', {
body: 'This works even if the page is closed',
icon: '/icon.png',
actions: [
{ action: 'open', title: 'Open App' },
{ action: 'dismiss', title: 'Dismiss' }
],
tag: 'persistent-notification'
});
}
}
// In service-worker.js
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'open') {
event.waitUntil(
clients.openWindow('/')
);
}
});
Best Practices
✅ DO:
// Check permission before showing
if (Notification.permission === 'granted') {
new Notification('Message');
}
// Use tags to replace old notifications
new Notification('Status', {
tag: 'status-update', // Replaces previous with same tag
body: 'Processing...'
});
// Provide context in notification
new Notification('New Message', {
body: 'Alice: Hey there!',
icon: '/alice-avatar.png',
data: { senderId: 123 } // For click handling
});
// Handle clicks
notification.onclick = () => {
window.focus();
notification.close();
};
// Rate limit notifications
const lastNotification = Date.now();
if (Date.now() - lastNotification > 60000) {
showNotification();
}
// Close notifications when done
notification.close();
❌ DON’T:
// Don't spam users
for (let i = 0; i < 100; i++) {
new Notification(`Message ${i}`); // ❌ Annoying!
}
// Don't show without permission
new Notification('Hi'); // ❌ Check permission first
// Don't show notifications for everything
new Notification('Mouse moved'); // ❌ Irrelevant
// Don't forget to require HTTPS
// http://example.com ❌
// Don't use vague messages
new Notification('Update'); // ❌ Update about what?
// Don't leave notifications open forever
// Always close or set requireInteraction appropriately
Notification Patterns
Grouping Multiple Notifications
function showGroupedNotifications(messages) {
if (messages.length === 1) {
new Notification(messages[0].sender, {
body: messages[0].text
});
} else {
new Notification(`${messages.length} New Messages`, {
body: messages.map(m => `${m.sender}: ${m.text}`).join('\n'),
tag: 'grouped-messages'
});
}
}
Silent Background Updates
new Notification('Background sync complete', {
body: 'Your data is up to date',
silent: true, // No sound
tag: 'bg-sync',
requireInteraction: false // Auto-dismiss
});
Summary
Check Support:
if ('Notification' in window) {
// Notifications supported
}
Request Permission:
const permission = await Notification.requestPermission();
// 'granted', 'denied', or 'default'
Show Notification:
if (Notification.permission === 'granted') {
const notification = new Notification('Title', {
body: 'Message text',
icon: '/icon.png',
tag: 'unique-id',
data: { customData: 'value' }
});
notification.onclick = () => {
window.focus();
notification.close();
};
}
Key Options:
body- Message texticon- Small icon imagetag- Unique ID (replaces existing)requireInteraction- Don’t auto-dismisssilent- No soundvibrate- Vibration patterndata- Custom data for events
Events:
onclick- User clickedonclose- Notification closedonerror- Error occurredonshow- Notification displayed
Requirements:
- HTTPS (or localhost)
- User permission
- Modern browser
Next: Continue exploring browser APIs!