Custom New Tab Page Chrome Extension Tutorial — Complete Guide (2025)
Every Chrome user interacts with the new tab page dozens of times per day. By default, Chrome displays a simple page with a search box and frequently visited sites. However, with the chrome new tab extension capability, you can replace this default experience with something personalized and powerful. Whether you want to display a custom dashboard, a productivity widget, or a beautiful visual theme, building a custom new tab page extension opens up tremendous possibilities.
In this comprehensive tutorial, we will walk through the complete process of creating a custom new tab page Chrome extension from scratch using Manifest V3. You will learn how to override the default newtab page, build an engaging user interface, add useful features like bookmarks and weather widgets, persist user preferences, and publish your extension to the Chrome Web Store.
Understanding the New Tab Override Feature
Chrome provides a powerful feature that allows extensions to replace the default newtab page with a custom one. This is known as the “newtab override” and is one of the few features that does not require users to grant explicit permission. When your extension is installed and enabled, Chrome will automatically display your custom page whenever a user opens a new tab.
The newtab override is particularly valuable because it provides constant visibility for your extension. Unlike popup-based extensions that users must consciously open, your custom new tab page appears naturally throughout the browsing experience. This makes it ideal for productivity dashboards, note-taking applications, weather widgets, or any extension that benefits from frequent user interaction.
It is important to note that Chrome has specific policies around newtab overrides. Your extension must provide a genuinely useful replacement experience, and Google may reject extensions that simply redirect to other websites or provide minimal functionality. Focus on creating value for users through meaningful features and a polished interface.
Project Setup and Manifest Configuration
Every Chrome extension begins with the manifest.json file. This configuration file tells Chrome about your extension’s capabilities, permissions, and file structure. For a newtab override extension, we will use Manifest V3, which is the current standard.
Create a new directory for your project called custom-newtab and add the following manifest.json file:
{
"manifest_version": 3,
"name": "My Dashboard - Custom New Tab",
"version": "1.0.0",
"description": "A personalized dashboard for your new tab page with widgets and customization options",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"chrome_url_overrides": {
"newtab": "newtab.html"
},
"permissions": [
"storage",
"topSites",
"bookmarks"
],
"action": {
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
}
The key configuration here is chrome_url_overrides. By specifying "newtab": "newtab.html", we tell Chrome to display our custom HTML file whenever a new tab is opened instead of the default Chrome newtab page.
The permissions we have included serve important purposes. The storage permission allows us to persist user preferences and data. The topSites permission provides access to the user’s most frequently visited websites, which is perfect for displaying a quick-access section. The bookmarks permission enables reading the user’s bookmarks to display them on the dashboard.
Building the New Tab HTML Interface
Create the newtab.html file in your project directory. This will be the main interface that users see when they open a new tab. We will build a modern, responsive dashboard with multiple sections:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Dashboard</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header class="header">
<div class="search-container">
<form id="searchForm" class="search-form">
<input type="text" id="searchInput" class="search-input" placeholder="Search the web..." autocomplete="off">
<button type="submit" class="search-button">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
</form>
</div>
<div class="datetime">
<div class="time" id="currentTime">--:--</div>
<div class="date" id="currentDate">Loading...</div>
</div>
</header>
<main class="main-content">
<section class="widget" id="bookmarksWidget">
<h2 class="widget-title">Bookmarks</h2>
<div class="bookmarks-grid" id="bookmarksGrid">
<div class="loading">Loading bookmarks...</div>
</div>
</section>
<section class="widget" id="topsitesWidget">
<h2 class="widget-title">Frequently Visited</h2>
<div class="topsites-grid" id="topsitesGrid">
<div class="loading">Loading sites...</div>
</div>
</section>
<section class="widget" id="notesWidget">
<h2 class="widget-title">Quick Notes</h2>
<textarea id="quickNotes" class="notes-textarea" placeholder="Write your notes here..."></textarea>
</section>
</main>
<footer class="footer">
<button id="settingsBtn" class="footer-button">⚙️ Settings</button>
<button id="themeBtn" class="footer-button">🎨 Change Theme</button>
</footer>
</div>
<div id="settingsModal" class="modal hidden">
<div class="modal-content">
<div class="modal-header">
<h2>Settings</h2>
<button class="close-btn" id="closeSettings">×</button>
</div>
<div class="modal-body">
<div class="setting-item">
<label for="backgroundInput">Background Image URL</label>
<input type="text" id="backgroundInput" placeholder="https://example.com/image.jpg">
</div>
<div class="setting-item">
<label>
<input type="checkbox" id="showBookmarks"> Show Bookmarks
</label>
</div>
<div class="setting-item">
<label>
<input type="checkbox" id="showTopSites" checked> Show Frequently Visited
</label>
</div>
<div class="setting-item">
<label>
<input type="checkbox" id="showNotes" checked> Show Quick Notes
</label>
</div>
</div>
</div>
</div>
<script src="newtab.js"></script>
</body>
</html>
This HTML structure provides a comprehensive dashboard with multiple useful sections. The search bar at the top allows users to perform web searches directly from their new tab. The datetime display keeps users informed of the current time and date. The bookmarks and frequently visited sections provide quick access to important sites. The notes section allows for quick jotting down of thoughts.
Styling Your Custom New Tab Page
Create a styles.css file to make your dashboard visually appealing. We will implement a modern design with support for custom backgrounds and themes:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
min-height: 100vh;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transition: background-image 0.5s ease-in-out;
color: #ffffff;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.3));
z-index: -1;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
margin-bottom: 30px;
}
.search-form {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.15);
border-radius: 50px;
padding: 8px 20px;
backdrop-filter: blur(10px);
width: 400px;
transition: all 0.3s ease;
}
.search-form:focus-within {
background: rgba(255, 255, 255, 0.25);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.search-input {
flex: 1;
border: none;
background: transparent;
color: #ffffff;
font-size: 16px;
padding: 8px;
outline: none;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.7);
}
.search-button {
background: transparent;
border: none;
color: #ffffff;
cursor: pointer;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
}
.search-button:hover {
opacity: 0.8;
}
.datetime {
text-align: right;
}
.time {
font-size: 48px;
font-weight: 300;
line-height: 1;
}
.date {
font-size: 18px;
opacity: 0.8;
margin-top: 5px;
}
.main-content {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.widget {
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 20px;
backdrop-filter: blur(10px);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.widget:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
}
.widget-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.bookmarks-grid,
.topsites-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 12px;
}
.bookmark-item,
.topsite-item {
display: flex;
flex-direction: column;
align-items: center;
text-decoration: none;
color: #ffffff;
padding: 10px;
border-radius: 8px;
transition: background 0.2s;
cursor: pointer;
}
.bookmark-item:hover,
.topsite-item:hover {
background: rgba(255, 255, 255, 0.1);
}
.bookmark-icon,
.topsite-icon {
width: 32px;
height: 32px;
border-radius: 8px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
font-size: 16px;
font-weight: 600;
}
.bookmark-title,
.topsite-title {
font-size: 11px;
text-align: center;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0.9;
}
.notes-textarea {
width: 100%;
min-height: 200px;
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 8px;
padding: 15px;
color: #ffffff;
font-size: 14px;
line-height: 1.6;
resize: vertical;
outline: none;
font-family: inherit;
}
.notes-textarea::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.notes-textarea:focus {
background: rgba(255, 255, 255, 0.15);
}
.footer {
display: flex;
justify-content: center;
gap: 15px;
padding: 15px 0;
}
.footer-button {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #ffffff;
padding: 10px 20px;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
backdrop-filter: blur(10px);
transition: all 0.2s;
}
.footer-button:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.loading {
grid-column: 1 / -1;
text-align: center;
padding: 20px;
opacity: 0.7;
}
/* Modal Styles */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal.hidden {
display: none;
}
.modal-content {
background: #2a2a2a;
border-radius: 16px;
width: 90%;
max-width: 500px;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
}
.modal-header h2 {
font-size: 20px;
font-weight: 600;
}
.close-btn {
background: transparent;
border: none;
color: #ffffff;
font-size: 28px;
cursor: pointer;
padding: 0;
line-height: 1;
}
.close-btn:hover {
opacity: 0.7;
}
.modal-body {
padding: 20px;
}
.setting-item {
margin-bottom: 20px;
}
.setting-item label {
display: block;
margin-bottom: 8px;
font-size: 14px;
}
.setting-item input[type="text"] {
width: 100%;
padding: 10px;
border: none;
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 14px;
}
.setting-item input[type="checkbox"] {
margin-right: 10px;
}
/* Theme Classes */
.theme-dark {
background: linear-gradient(135deg, #1a1a2e, #16213e);
}
.theme-light {
background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
}
.theme-light .search-form,
.theme-light .widget,
.theme-light .footer-button,
.theme-light .notes-textarea,
.theme-light .modal-content {
background: rgba(0, 0, 0, 0.05);
color: #333;
}
.theme-light .search-input,
.theme-light .notes-textarea,
.theme-light .modal-header h2,
.theme-light .close-btn,
.theme-light .bookmark-title,
.theme-light .topsite-title,
.theme-light .widget-title {
color: #333;
}
.theme-light .search-input::placeholder {
color: rgba(0, 0, 0, 0.5);
}
The CSS provides a beautiful glassmorphism design that adapts to different themes. The use of semi-transparent backgrounds with blur effects creates a modern, sophisticated look. The responsive grid layout ensures the dashboard looks good on various screen sizes.
Implementing the JavaScript Functionality
Create a newtab.js file to handle all the interactive functionality:
// Initialize the new tab page
document.addEventListener('DOMContentLoaded', async () => {
// Load user settings
await loadSettings();
// Set up the clock
updateDateTime();
setInterval(updateDateTime, 1000);
// Load bookmarks
loadBookmarks();
// Load frequently visited sites
loadTopSites();
// Load notes
loadNotes();
// Set up event listeners
setupEventListeners();
// Apply saved theme
applyTheme();
});
// Update clock and date
function updateDateTime() {
const now = new Date();
// Update time
const timeElement = document.getElementById('currentTime');
timeElement.textContent = now.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true
});
// Update date
const dateElement = document.getElementById('currentDate');
dateElement.textContent = now.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
// Load bookmarks from Chrome
async function loadBookmarks() {
try {
const bookmarkTree = await chrome.bookmarks.getTree();
const bookmarksGrid = document.getElementById('bookmarksGrid');
// Get top-level bookmarks (first level children)
const topBookmarks = [];
if (bookmarkTree[0] && bookmarkTree[0].children) {
collectBookmarks(bookmarkTree[0].children, topBookmarks, 0, 8);
}
if (topBookmarks.length === 0) {
bookmarksGrid.innerHTML = '<div class="loading">No bookmarks found</div>';
return;
}
bookmarksGrid.innerHTML = topBookmarks.map(bookmark => {
const initial = bookmark.title.charAt(0).toUpperCase();
return `
<a href="${bookmark.url}" class="bookmark-item" target="_blank">
<div class="bookmark-icon">${initial}</div>
<div class="bookmark-title">${escapeHtml(bookmark.title)}</div>
</a>
`;
}).join('');
} catch (error) {
console.error('Error loading bookmarks:', error);
document.getElementById('bookmarksGrid').innerHTML =
'<div class="loading">Unable to load bookmarks</div>';
}
}
function collectBookmarks(children, result, depth, max) {
if (result.length >= max || depth > 1) return;
for (const node of children) {
if (result.length >= max) break;
if (node.url) {
result.push(node);
}
if (node.children) {
collectBookmarks(node.children, result, depth + 1, max);
}
}
}
// Load frequently visited sites
async function loadTopSites() {
try {
const topSites = await chrome.topSites.get();
const topsitesGrid = document.getElementById('topsitesGrid');
const displaySites = topSites.slice(0, 8);
if (displaySites.length === 0) {
topsitesGrid.innerHTML = '<div class="loading">No frequently visited sites</div>';
return;
}
topsitesGrid.innerHTML = displaySites.map(site => {
const domain = new URL(site.url).hostname;
const initial = domain.charAt(0).toUpperCase();
return `
<a href="${site.url}" class="topsite-item" target="_blank">
<div class="topsite-icon">${initial}</div>
<div class="topsite-title">${escapeHtml(site.title || domain)}</div>
</a>
`;
}).join('');
} catch (error) {
console.error('Error loading top sites:', error);
document.getElementById('topsitesGrid').innerHTML =
'<div class="loading">Unable to load top sites</div>';
}
}
// Load and save notes
async function loadNotes() {
try {
const result = await chrome.storage.local.get('quickNotes');
const notesTextarea = document.getElementById('quickNotes');
if (result.quickNotes) {
notesTextarea.value = result.quickNotes;
}
// Auto-save notes on change
notesTextarea.addEventListener('input', async () => {
await chrome.storage.local.set({
quickNotes: notesTextarea.value
});
});
} catch (error) {
console.error('Error loading notes:', error);
}
}
// Load settings from storage
async function loadSettings() {
try {
const result = await chrome.storage.local.get([
'backgroundImage',
'theme',
'showBookmarks',
'showTopSites',
'showNotes'
]);
// Set default values
const settings = {
backgroundImage: result.backgroundImage || '',
theme: result.theme || 'dark',
showBookmarks: result.showBookmarks !== false,
showTopSites: result.showTopSites !== false,
showNotes: result.showNotes !== false
};
// Apply background
if (settings.backgroundImage) {
document.body.style.backgroundImage = `url('${settings.backgroundImage}')`;
document.getElementById('backgroundInput').value = settings.backgroundImage;
} else {
document.body.classList.add('theme-dark');
}
// Toggle widget visibility
document.getElementById('bookmarksWidget').style.display =
settings.showBookmarks ? 'block' : 'none';
document.getElementById('topsitesWidget').style.display =
settings.showTopSites ? 'block' : 'none';
document.getElementById('notesWidget').style.display =
settings.showNotes ? 'block' : 'none';
// Set checkbox states
document.getElementById('showBookmarks').checked = settings.showBookmarks;
document.getElementById('showTopSites').checked = settings.showTopSites;
document.getElementById('showNotes').checked = settings.showNotes;
} catch (error) {
console.error('Error loading settings:', error);
}
}
// Apply theme
function applyTheme() {
chrome.storage.local.get('theme', (result) => {
const theme = result.theme || 'dark';
document.body.className = theme === 'light' ? 'theme-light' : 'theme-dark';
});
}
// Set up all event listeners
function setupEventListeners() {
// Search form
document.getElementById('searchForm').addEventListener('submit', (e) => {
e.preventDefault();
const query = document.getElementById('searchInput').value.trim();
if (query) {
window.location.href = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
}
});
// Settings button
document.getElementById('settingsBtn').addEventListener('click', () => {
document.getElementById('settingsModal').classList.remove('hidden');
});
// Close settings
document.getElementById('closeSettings').addEventListener('click', () => {
document.getElementById('settingsModal').classList.add('hidden');
});
// Theme button
document.getElementById('themeBtn').addEventListener('click', () => {
const currentTheme = document.body.classList.contains('theme-light') ? 'light' : 'dark';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
chrome.storage.local.set({ theme: newTheme });
applyTheme();
});
// Background input
document.getElementById('backgroundInput').addEventListener('change', (e) => {
const url = e.target.value.trim();
chrome.storage.local.set({ backgroundImage: url });
if (url) {
document.body.style.backgroundImage = `url('${url}')`;
} else {
document.body.style.backgroundImage = '';
}
});
// Widget visibility toggles
document.getElementById('showBookmarks').addEventListener('change', (e) => {
chrome.storage.local.set({ showBookmarks: e.target.checked });
document.getElementById('bookmarksWidget').style.display =
e.target.checked ? 'block' : 'none';
});
document.getElementById('showTopSites').addEventListener('change', (e) => {
chrome.storage.local.set({ showTopSites: e.target.checked });
document.getElementById('topsitesWidget').style.display =
e.target.checked ? 'block' : 'none';
});
document.getElementById('showNotes').addEventListener('change', (e) => {
chrome.storage.local.set({ showNotes: e.target.checked });
document.getElementById('notesWidget').style.display =
e.target.checked ? 'block' : 'none';
});
// Close modal on outside click
document.getElementById('settingsModal').addEventListener('click', (e) => {
if (e.target.id === 'settingsModal') {
e.target.classList.add('hidden');
}
});
}
// Utility: Escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
This JavaScript implementation provides comprehensive functionality including a working search bar, real-time clock, bookmark integration, frequently visited sites, persistent notes with auto-save, theme switching, and a settings modal for customization. All data is stored using Chrome’s storage API, ensuring user preferences persist across sessions.
Testing Your Extension
Before publishing, you need to test your extension locally. Chrome provides a simple way to load unpacked extensions for testing:
- Open Chrome and navigate to
chrome://extensions/ - Enable “Developer mode” using the toggle in the top right corner
- Click “Load unpacked” button
- Select your
custom-newtabproject directory - Open a new tab to see your custom newtab page in action
Test all the features thoroughly:
- Verify the search functionality works and redirects to Google
- Check that bookmarks are loading correctly
- Confirm frequently visited sites are displayed
- Test the notes feature and verify persistence after closing and reopening
- Try changing themes and backgrounds
- Toggle widget visibility settings
Make adjustments as needed based on your testing results.
Publishing to Chrome Web Store
Once your extension is working correctly, you can publish it to the Chrome Web Store. Follow these steps:
Prepare for Publication
Before submitting, ensure you have:
- Icons: Create icon files at 16x16, 48x48, and 128x128 pixels
- Privacy policy: Required if your extension accesses user data (bookmarks, browsing history)
- Screenshots: Add at least one screenshot (1280x800 or 640x400 pixels)
- Verify ownership: Verify any domains you link to in your extension’s description
Submit Your Extension
- Navigate to the Chrome Web Store Developer Dashboard
- Create a new item and upload your extension as a ZIP file
- Fill in the store listing details with a compelling description
- Submit for review
Google typically reviews extensions within a few days. Ensure your extension follows all policies to avoid rejection.
Advanced Features to Consider
Once you have the basic custom new tab page working, consider adding these advanced features:
Weather Widget
Integrate a weather API to display local weather conditions on your dashboard. Users appreciate seeing the weather at a glance when they open a new tab.
Task Integration
Connect with task management services like Todoist, Trello, or Google Tasks to display upcoming tasks directly on the new tab page.
Focus Mode
Add a distraction-free mode that hides all widgets and displays only the time and a simple to-do list, perfect for productivity sessions.
Custom Shortcuts
Allow users to define custom keyboard shortcuts for quickly accessing specific bookmarks or performing actions.
Data Sync
Implement Chrome Sync to allow users to sync their settings and notes across multiple devices.
Conclusion
Building a custom new tab page Chrome extension is an excellent project that combines web development skills with the unique capabilities of browser extensions. In this tutorial, you have learned how to set up a Manifest V3 extension, configure the newtab override, build a modern dashboard interface with HTML and CSS, implement interactive functionality with JavaScript, test your extension locally, and prepare it for publication.
The custom new tab page you have created provides constant visibility to users, making it an ideal platform for productivity tools, information displays, or personalized experiences. With the foundation you have built here, you can continue to add features and refine the user experience to create a truly valuable extension.
Remember to focus on providing genuine value to users, as Chrome’s policies require newtab overrides to be functional and useful. With creativity and attention to user needs, your custom new tab page extension can become a valuable tool for thousands of Chrome users.
Built by theluckystrike at zovo.one