Build a Notification Center Chrome Extension: Complete 2025 Guide
Modern web users juggle dozens of applications that send notifications—email clients, Slack, GitHub, project management tools, calendar apps, and social media platforms. This notification overload creates chaos, with important alerts getting buried in browser tabs or system notifications that vanish before you can act on them. Building a notification center Chrome extension that consolidates these alerts into a unified hub solves a real problem for millions of users.
In this comprehensive guide, you will learn how to build a production-ready notification center extension from scratch. We will cover everything from understanding the architecture to implementing advanced features like notification filtering, priority systems, and persistent storage. By the end of this tutorial, you will have a fully functional unified notifications chrome extension that users can install and use immediately.
Why Build a Notification Center Extension?
The average knowledge worker receives over 100 notifications per day across various applications. This constant interruption fragment reduces productivity and increases cognitive load. A well-designed alert manager extension addresses these pain points by providing a centralized location for all notifications.
The Problem with Scattered Notifications
Browser notifications are ephemeral by design. They appear, wait for user attention, and disappear—often while you are focused on another task. System notifications pile up in the notification center but remain siloed by application. Users find themselves:
- Switching between multiple tabs to check different notification sources
- Missing critical alerts because they were focused on a different application
- Unable to review notification history from more than a few hours ago
- Lacking the ability to filter or prioritize which notifications matter most
The Solution: Unified Notification Center
A notification center extension solves these problems by aggregating notifications into a single, accessible interface. Users can:
- View all notifications in chronological order regardless of source
- Filter by application, type, or priority level
- Search through notification history
- Take quick actions directly from the notification
- Receive sound or visual alerts for high-priority notifications
This is why notification aggregator tools consistently rank among the most popular productivity extensions in the Chrome Web Store. Building one is both a valuable learning exercise and a potentially commercial product.
Understanding Chrome Notification APIs
Before writing code, you need to understand how Chrome handles notifications. Chrome provides several APIs that work together to create a robust notification system.
The Notifications API
The Chrome Notifications API (chrome.notifications) allows extensions to create system notifications that appear in the user’s operating system notification center. These notifications persist until the user dismisses them or clicks on them.
// Creating a basic notification
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon128.png',
title: 'New Message',
message: 'You have a new message from John Doe',
priority: 1
}, (notificationId) => {
console.log('Notification created:', notificationId);
});
The Alarms API
The Alarms API (chrome.alarms) enables your extension to schedule code to run at specific times or intervals. This is essential for fetching new notifications periodically:
// Schedule an alarm to check for new notifications every 5 minutes
chrome.alarms.create('checkNotifications', {
periodInMinutes: 5
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'checkNotifications') {
fetchNewNotifications();
}
});
The Storage API
The Chrome Storage API (chrome.storage) provides a way to persist data across browser sessions. This is critical for storing notification history:
// Storing a notification
chrome.storage.local.set({
notifications: [{ id: '123', title: 'Test', timestamp: Date.now() }]
});
// Retrieving notifications
chrome.storage.local.get('notifications', (result) => {
console.log(result.notifications);
});
The Badge API
The Badge API allows you to display a number on the extension icon, indicating unread notification count:
// Set badge to show 5 unread notifications
chrome.action.setBadgeText({ text: '5' });
chrome.action.setBadgeBackgroundColor({ color: '#FF0000' });
Project Structure and Setup
Now let us set up the project structure for our notification center extension. We will use Manifest V3, the latest Chrome extension platform.
Directory Layout
notification-center/
├── manifest.json
├── popup/
│ ├── popup.html
│ ├── popup.css
│ └── popup.js
├── background/
│ └── background.js
├── icons/
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── styles/
└── notification.css
Manifest Configuration
Create the manifest.json file with the necessary permissions:
{
"manifest_version": 3,
"name": "Notification Center",
"version": "1.0.0",
"description": "Unified notification center for Chrome - aggregate all your notifications in one place",
"permissions": [
"notifications",
"storage",
"alarms",
"activeTab",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup/popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"background": {
"service_worker": "background/background.js"
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
This manifest grants the necessary permissions for notifications, storage, alarms, and the ability to interact with web pages.
Building the Background Service Worker
The background service worker is the brain of your extension. It handles notification creation, storage, and periodic fetching.
Core Background Script
Create background/background.js:
// Background service worker for Notification Center
// Initialize default storage
chrome.runtime.onInstalled.addListener(() => {
chrome.storage.local.set({
notifications: [],
settings: {
maxNotifications: 100,
enableSound: true,
enableDesktop: true,
autoClear: false,
categories: ['email', 'social', 'chat', 'other']
}
});
// Set up periodic alarm for fetching notifications
chrome.alarms.create('fetchNotifications', {
periodInMinutes: 1
});
});
// Handle alarm for periodic notification fetching
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'fetchNotifications') {
fetchAllNotifications();
}
});
// Main function to aggregate notifications from various sources
async function fetchAllNotifications() {
// This would connect to actual notification sources
// For demo, we simulate incoming notifications
const mockNotifications = generateMockNotifications();
// Store new notifications
chrome.storage.local.get('notifications', (result) => {
const existingNotifications = result.notifications || [];
const updatedNotifications = [...mockNotifications, ...existingNotifications]
.slice(0, 100); // Keep only last 100
chrome.storage.local.set({ notifications: updatedNotifications });
updateBadge(updatedNotifications.length);
});
}
// Update the extension badge with unread count
function updateBadge(count) {
chrome.action.setBadgeText({
text: count > 0 ? count.toString() : ''
});
chrome.action.setBadgeBackgroundColor({ color: '#4CAF50' });
}
// Generate mock notifications for demonstration
function generateMockNotifications() {
const types = ['email', 'social', 'chat', 'alert'];
const messages = [
{ type: 'email', title: 'New Email', message: 'You have a new message from colleague' },
{ type: 'social', title: 'GitHub Notification', message: 'New issue comment on your repository' },
{ type: 'chat', title: 'Slack Message', message: 'New message in #general channel' },
{ type: 'alert', title: 'Calendar Reminder', message: 'Meeting starts in 15 minutes' }
];
// Randomly decide if we add a new notification
if (Math.random() > 0.7) {
const randomMsg = messages[Math.floor(Math.random() * messages.length)];
return [{
id: Date.now().toString(),
title: randomMsg.title,
message: randomMsg.message,
type: randomMsg.type,
timestamp: Date.now(),
read: false,
priority: Math.floor(Math.random() * 3)
}];
}
return [];
}
// Handle notification clicks
chrome.notifications.onClicked.addListener((notificationId) => {
console.log('Notification clicked:', notificationId);
// Mark as read and open relevant content
markNotificationAsRead(notificationId);
});
function markNotificationAsRead(id) {
chrome.storage.local.get('notifications', (result) => {
const notifications = result.notifications.map(n =>
n.id === id ? { ...n, read: true } : n
);
chrome.storage.local.set({ notifications });
updateBadge(notifications.filter(n => !n.read).length);
});
}
This background script handles notification storage, badge updates, and periodic fetching. In a production extension, you would replace the mock notification generation with actual API calls to services like Gmail, Slack, or GitHub.
Creating the Popup Interface
The popup is what users see when they click the extension icon. It displays all stored notifications in a clean, organized interface.
HTML Structure
Create popup/popup.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notification Center</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="notification-center">
<header class="header">
<h1>Notification Center</h1>
<div class="header-actions">
<button id="markAllRead" class="btn-secondary">Mark All Read</button>
<button id="clearAll" class="btn-danger">Clear All</button>
</div>
</header>
<div class="filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="email">Email</button>
<button class="filter-btn" data-filter="social">Social</button>
<button class="filter-btn" data-filter="chat">Chat</button>
<button class="filter-btn" data-filter="alert">Alerts</button>
</div>
<div id="notificationList" class="notification-list">
<!-- Notifications will be rendered here -->
</div>
<div id="emptyState" class="empty-state hidden">
<p>No notifications yet</p>
</div>
<footer class="footer">
<button id="settingsBtn" class="btn-link">Settings</button>
<span class="notification-count" id="notificationCount">0 unread</span>
</footer>
</div>
<script src="popup.js"></script>
</body>
</html>
Styling the Popup
Create popup/popup.css with a modern, clean design:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
width: 380px;
min-height: 400px;
background: #f5f5f5;
color: #333;
}
.notification-center {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
padding: 16px;
background: #fff;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 18px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 8px;
}
.filters {
padding: 12px 16px;
background: #fff;
border-bottom: 1px solid #e0e0e0;
display: flex;
gap: 8px;
overflow-x: auto;
}
.filter-btn {
padding: 6px 12px;
border: none;
border-radius: 16px;
background: #f0f0f0;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
transition: all 0.2s;
}
.filter-btn:hover {
background: #e0e0e0;
}
.filter-btn.active {
background: #4285f4;
color: #fff;
}
.notification-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.notification-item {
background: #fff;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.notification-item:hover {
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.notification-item.unread {
border-left: 4px solid #4285f4;
}
.notification-item .type-icon {
width: 24px;
height: 24px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-right: 8px;
}
.notification-item.email .type-icon { background: #EA4335; color: #fff; }
.notification-item.social .type-icon { background: #4285f4; color: #fff; }
.notification-item.chat .type-icon { background: #34A853; color: #fff; }
.notification-item.alert .type-icon { background: #FBBC05; color: #fff; }
.notification-item .title {
font-weight: 600;
font-size: 14px;
margin-bottom: 4px;
}
.notification-item .message {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.notification-item .timestamp {
font-size: 11px;
color: #999;
}
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.hidden {
display: none !important;
}
.footer {
padding: 12px 16px;
background: #fff;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.btn-secondary {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
cursor: pointer;
font-size: 12px;
}
.btn-danger {
padding: 6px 12px;
border: none;
border-radius: 4px;
background: #ff4444;
color: #fff;
cursor: pointer;
font-size: 12px;
}
.btn-link {
border: none;
background: none;
color: #4285f4;
cursor: pointer;
font-size: 13px;
}
.notification-count {
font-size: 12px;
color: #666;
}
Popup JavaScript Logic
Create popup/popup.js to handle all the interaction logic:
// Popup script for Notification Center
let currentFilter = 'all';
let notifications = [];
// Initialize popup
document.addEventListener('DOMContentLoaded', () => {
loadNotifications();
setupEventListeners();
});
// Load notifications from storage
function loadNotifications() {
chrome.storage.local.get('notifications', (result) => {
notifications = result.notifications || [];
renderNotifications();
updateNotificationCount();
});
}
// Render notifications based on current filter
function renderNotifications() {
const container = document.getElementById('notificationList');
const emptyState = document.getElementById('emptyState');
const filteredNotifications = currentFilter === 'all'
? notifications
: notifications.filter(n => n.type === currentFilter);
if (filteredNotifications.length === 0) {
container.innerHTML = '';
emptyState.classList.remove('hidden');
return;
}
emptyState.classList.add('hidden');
container.innerHTML = filteredNotifications.map(notification => `
<div class="notification-item ${notification.type} ${notification.read ? '' : 'unread'}"
data-id="${notification.id}">
<span class="type-icon">${getTypeIcon(notification.type)}</span>
<div class="title">${escapeHtml(notification.title)}</div>
<div class="message">${escapeHtml(notification.message)}</div>
<div class="timestamp">${formatTimestamp(notification.timestamp)}</div>
</div>
`).join('');
// Add click handlers
container.querySelectorAll('.notification-item').forEach(item => {
item.addEventListener('click', () => handleNotificationClick(item.dataset.id));
});
}
// Handle notification click
function handleNotificationClick(id) {
markAsRead(id);
// In a real extension, you would open the relevant URL
window.close();
}
// Mark notification as read
function markAsRead(id) {
notifications = notifications.map(n =>
n.id === id ? { ...n, read: true } : n
);
chrome.storage.local.set({ notifications });
renderNotifications();
updateNotificationCount();
}
// Mark all as read
function markAllAsRead() {
notifications = notifications.map(n => ({ ...n, read: true }));
chrome.storage.local.set({ notifications });
renderNotifications();
updateNotificationCount();
}
// Clear all notifications
function clearAll() {
notifications = [];
chrome.storage.local.set({ notifications: [] });
renderNotifications();
updateNotificationCount();
}
// Update notification count badge
function updateNotificationCount() {
const unreadCount = notifications.filter(n => !n.read).length;
document.getElementById('notificationCount').textContent =
`${unreadCount} unread`;
// Update extension badge
chrome.action.setBadgeText({
text: unreadCount > 0 ? unreadCount.toString() : ''
});
}
// Set up event listeners
function setupEventListeners() {
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilter = btn.dataset.filter;
renderNotifications();
});
});
// Mark all read button
document.getElementById('markAllRead').addEventListener('click', markAllAsRead);
// Clear all button
document.getElementById('clearAll').addEventListener('click', clearAll);
// Settings button
document.getElementById('settingsBtn').addEventListener('click', () => {
// Open settings page
chrome.runtime.openOptionsPage();
});
}
// Utility functions
function getTypeIcon(type) {
const icons = {
email: '✉',
social: '👥',
chat: '💬',
alert: '⚠'
};
return icons[type] || '📌';
}
function formatTimestamp(timestamp) {
const now = Date.now();
const diff = now - timestamp;
if (diff < 60000) return 'Just now';
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
return new Date(timestamp).toLocaleDateString();
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Advanced Features for Production
While the basic notification center works, a production-ready extension needs additional features. Let me outline how to implement them.
Priority System
Implement a priority system to highlight important notifications:
// In background.js - Priority levels
const priorities = {
high: { color: '#FF0000', sound: true, desktop: true },
medium: { color: '#FFA500', sound: false, desktop: true },
low: { color: '#808080', sound: false, desktop: false }
};
function createPriorityNotification(notification) {
const priority = priorities[notification.priority === 2 ? 'high' :
notification.priority === 1 ? 'medium' : 'low'];
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon128.png',
title: notification.title,
message: notification.message,
priority: notification.priority,
eventTime: notification.timestamp
});
}
Notification Grouping
Group notifications by source or time period:
function groupNotifications(notifications) {
const groups = {
today: [],
yesterday: [],
older: []
};
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today.getTime() - 86400000);
notifications.forEach(n => {
const notifDate = new Date(n.timestamp);
if (notifDate >= today) {
groups.today.push(n);
} else if (notifDate >= yesterday) {
groups.yesterday.push(n);
} else {
groups.older.push(n);
}
});
return groups;
}
Integration with Real Services
To make your extension truly useful, integrate with actual notification sources:
- Email (Gmail API): Use the Gmail API to fetch new emails
- Slack (Slack API): Connect to Slack webhooks or RTM API
- GitHub (GitHub API): Monitor repositories for notifications
- Calendar (Google Calendar API): Fetch upcoming events
Each integration requires OAuth authentication and API key setup. The Chrome Identity API (chrome.identity) handles OAuth flows seamlessly.
Best Practices for Notification Extensions
Building a notification center extension comes with responsibilities. Follow these best practices to create a product users trust.
Respect User Privacy
- Only request permissions you absolutely need
- Store notification data locally when possible
- Provide clear options for users to delete their data
- Never send notification content to external servers without consent
Optimize Performance
- Use service workers efficiently—avoid keeping them alive unnecessarily
- Limit the number of stored notifications (100-500 is usually sufficient)
- Use efficient data structures for quick filtering and searching
- Implement lazy loading for notification lists
Provide Clear Value
- Give users control over which sources to include
- Allow granular notification filtering
- Support notification customization (sound, duration, display options)
- Provide easy ways to mark notifications as read or dismiss them
Test Thoroughly
- Test across different Chrome versions and operating systems
- Verify notification behavior when the browser is closed
- Test with multiple notification sources simultaneously
- Ensure the extension works in Incognito mode if applicable
Publishing Your Extension
Once your notification center extension is ready, you can publish it to the Chrome Web Store:
- Prepare your extension: Run
chrome.runtime.reload()in developer mode to test - Create a ZIP file: Package all extension files (exclude unnecessary files)
- Create a Developer Account: Sign up at the Chrome Web Store Developer Dashboard
- Upload your extension: Submit the ZIP file and fill in the store listing
- Wait for Review: Google typically reviews within 24-72 hours
For the store listing, use keywords like “notification center extension,” “unified notifications chrome,” and “alert manager extension” in your description to improve SEO visibility.
Conclusion
Building a notification center Chrome extension is a rewarding project that teaches you advanced Chrome extension development concepts while creating a genuinely useful tool. You have learned how to work with the Notifications API, Alarms API, Storage API, and Badge API. You have created a complete extension with a modern popup interface, background service worker, and persistent storage.
The foundation you have built can be extended with real API integrations, advanced filtering, notification scheduling, and cross-device synchronization. Users increasingly need help managing notification overload—your unified notifications chrome extension addresses a real pain point with significant market demand.
Start with this base implementation, test it thoroughly, and gradually add features that transform it from a simple notification aggregator into a full-fledged alert manager extension that users cannot live without.
Next Steps
To continue developing your notification center extension:
- Add real integrations: Connect to Gmail, Slack, GitHub, and other services
- Implement notification actions: Allow users to reply, archive, or dismiss from notifications
- Add keyboard shortcuts: Enable power users to navigate notifications quickly
- Create a settings page: Provide comprehensive customization options
- Implement sync: Use chrome.storage.sync to share settings across devices
The Chrome extension platform continues to evolve. Stay updated with the latest Manifest V3 changes and Chrome Web Store policies to ensure your extension remains compliant and performant.