Build a YouTube Timestamp Extension: Complete Guide to Video Bookmarking
YouTube has become the world’s largest video library, with users watching billions of hours of content daily. Whether you are watching tutorials, lectures, conference talks, or podcasts, there is often a need to remember specific moments in videos. Creating a YouTube timestamp extension allows users to bookmark specific points in videos, add notes to those timestamps, and easily return to those moments later. This is a practical project that solves real problems and teaches valuable skills in Chrome extension development.
In this comprehensive guide, we will walk through building a fully functional video bookmark chrome extension. You will learn how to interact with the YouTube player API, store timestamps and notes using Chrome’s storage API, and create a intuitive popup interface for managing your video bookmarks.
Why Build a YouTube Timestamp Extension?
The demand for timestamp bookmarking tools on YouTube is substantial. Content creators, students, researchers, and casual viewers all have reasons to mark and return to specific moments in videos. A well-designed YouTube timestamp extension can serve multiple use cases:
Use Cases for Timestamp Bookmarking
Educational Purposes: Students watching lecture videos often need to mark important explanations or concepts. Instead of scrubbing through lengthy videos to find that one explanation, they can simply click a bookmark and return immediately.
Content Creators: Video creators who watch reference materials need to mark inspiration points, interesting techniques, or citations they want to include in their own content.
Research and Review: Professionals researching topics through YouTube need to mark multiple reference points across many videos. A timestamp extension makes this workflow much more efficient.
Entertainment Bookmarking: Even casual viewers benefit from marking funny moments, plot points in movies, or sections they want to show friends later.
Building this extension teaches you valuable skills including YouTube player API integration, Chrome storage synchronization, content script injection, and popup UI development. These skills transfer directly to many other extension projects.
Project Architecture and Components
Before writing any code, we need to understand the architecture of our YouTube timestamp extension. A typical Chrome extension with timestamp bookmarking functionality consists of several key components:
The Manifest File
The manifest.json file defines the extension’s configuration, permissions, and entry points. Our extension needs permissions to interact with YouTube pages, store data, and display a popup interface.
Content Script
The content script runs in the context of YouTube pages. It communicates with the YouTube player API to extract current timestamps, inject UI elements into the page, and listen for user interactions.
Popup Interface
The popup provides the main user interface for viewing, managing, and organizing saved timestamps. Users can see their bookmarks, add notes, and navigate to saved timestamps.
Background Service Worker
While our extension can function without a complex background script, having one allows for advanced features like syncing bookmarks across devices or handling cross-tab state management.
Setting Up the Manifest
Let us start by creating the manifest file. We will use Manifest V3, which is the current standard for Chrome extensions:
{
"manifest_version": 3,
"name": "YouTube Timestamp Manager",
"version": "1.0.0",
"description": "Bookmark timestamps and add notes to YouTube videos",
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"*://*.youtube.com/*"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["*://*.youtube.com/*"],
"js": ["content.js"],
"css": ["content.css"]
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
The manifest declares several important permissions. The storage permission allows us to save timestamps and notes persistently. The scripting permission enables us to inject content scripts programmatically if needed. The host_permissions for youtube.com is critical as it allows our extension to access YouTube pages and interact with the video player.
Building the Content Script
The content script is the heart of our extension. It runs on YouTube pages and handles all interactions with the video player. Let us build a comprehensive content script that captures timestamps and provides in-page functionality:
// content.js - Main content script for YouTube Timestamp Manager
(function() {
'use strict';
// Check if we are on a valid YouTube video page
function isVideoPage() {
return window.location.pathname === '/watch' &&
new URLSearchParams(window.location.search).has('v');
}
// Get the current video ID from the URL
function getVideoId() {
const params = new URLSearchParams(window.location.search);
return params.get('v');
}
// Get the current video title
function getVideoTitle() {
const titleElement = document.querySelector('h1.ytd-video-primary-info-renderer') ||
document.querySelector('h1.title');
return titleElement ? titleElement.textContent.trim() : 'Untitled Video';
}
// Get current timestamp from YouTube player
function getCurrentTimestamp() {
const videoElement = document.querySelector('video');
if (videoElement) {
return Math.floor(videoElement.currentTime);
}
return 0;
}
// Format seconds into HH:MM:SS or MM:SS
function formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
// Create and inject the timestamp bookmark button
function injectBookmarkButton() {
// Check if button already exists
if (document.getElementById('timestamp-bookmark-btn')) {
return;
}
// Find the like button container (good location for our button)
const actionBar = document.querySelector('#top-level-buttons-computed');
if (!actionBar) {
return;
}
const button = document.createElement('button');
button.id = 'timestamp-bookmark-btn';
button.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal';
button.innerHTML = `
<span class="bookmark-icon">🔖</span>
<span class="bookmark-text">Bookmark</span>
`;
button.title = 'Add timestamp bookmark';
button.addEventListener('click', handleBookmarkClick);
// Insert after the like button
const likeButton = actionBar.querySelector('#segmented-like-button');
if (likeButton && likeButton.nextSibling) {
actionBar.insertBefore(button, likeButton.nextSibling);
} else {
actionBar.appendChild(button);
}
}
// Handle bookmark button click
async function handleBookmarkClick(event) {
event.preventDefault();
event.stopPropagation();
const timestamp = getCurrentTimestamp();
const videoId = getVideoId();
const videoTitle = getVideoTitle();
const formattedTime = formatTime(timestamp);
// Create a new bookmark entry
const bookmark = {
id: Date.now().toString(),
videoId: videoId,
videoTitle: videoTitle,
timestamp: timestamp,
formattedTime: formattedTime,
note: '',
createdAt: new Date().toISOString()
};
// Get existing bookmarks from storage
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const bookmarks = result.youtubeBookmarks || [];
// Add new bookmark
bookmarks.unshift(bookmark);
// Save back to storage
await chrome.storage.local.set({ youtubeBookmarks: bookmarks });
// Show visual feedback
showNotification(`Timestamp saved at ${formattedTime}`);
}
// Show a temporary notification
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'timestamp-notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 10);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 2000);
}
// Initialize the content script
function init() {
if (!isVideoPage()) {
return;
}
// Wait for YouTube to load the video controls
const observer = new MutationObserver((mutations) => {
const actionBar = document.querySelector('#top-level-buttons-computed');
if (actionBar && !document.getElementById('timestamp-bookmark-btn')) {
injectBookmarkButton();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Also try immediately in case the page is already loaded
injectBookmarkButton();
}
// Start the content script
init();
})();
The content script performs several important functions. First, it detects whether the user is on a YouTube video page by checking the URL. Then it finds the video player element and extracts the current playback position. When the user clicks our bookmark button, the script captures the current timestamp along with the video ID and title, then saves this information to Chrome’s local storage.
We also inject a notification element into the page to give users feedback when they successfully save a timestamp. The MutationObserver ensures our button appears even when navigating between videos without a full page reload, which is common on YouTube.
Creating the Popup Interface
The popup provides the main user interface for managing bookmarks. Let us create an intuitive and functional popup:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Timestamp Manager</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="popup-container">
<header class="popup-header">
<h1>🔖 Timestamp Manager</h1>
<p class="subtitle">Your YouTube video bookmarks</p>
</header>
<div class="current-video" id="currentVideo">
<div class="video-info">
<span class="label">Current Video:</span>
<span class="video-title" id="videoTitle">Not a YouTube video page</span>
</div>
<div class="timestamp-display" id="currentTimestamp">
0:00
</div>
</div>
<div class="bookmarks-section">
<h2>Saved Bookmarks</h2>
<div class="bookmarks-list" id="bookmarksList">
<div class="empty-state" id="emptyState">
<p>No bookmarks yet</p>
<p class="hint">Click the bookmark button on any YouTube video to save timestamps</p>
</div>
</div>
</div>
<footer class="popup-footer">
<button id="clearAllBtn" class="danger-btn">Clear All</button>
</footer>
</div>
<script src="popup.js"></script>
</body>
</html>
Now let us create the CSS for a clean, modern popup interface:
/* popup.css - Styling for the timestamp manager popup */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 360px;
min-height: 400px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #1a1a1a;
color: #ffffff;
}
.popup-container {
padding: 16px;
}
.popup-header {
text-align: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #333;
}
.popup-header h1 {
font-size: 18px;
margin-bottom: 4px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.popup-header .subtitle {
font-size: 12px;
color: #888;
}
.current-video {
background: #2a2a2a;
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
}
.video-info {
margin-bottom: 8px;
}
.video-info .label {
font-size: 11px;
color: #888;
display: block;
margin-bottom: 2px;
}
.video-info .video-title {
font-size: 13px;
color: #fff;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.timestamp-display {
font-size: 28px;
font-weight: 600;
color: #ff0000;
font-family: monospace;
}
.bookmarks-section h2 {
font-size: 14px;
color: #888;
margin-bottom: 12px;
}
.bookmarks-list {
max-height: 280px;
overflow-y: auto;
}
.bookmark-item {
background: #2a2a2a;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
cursor: pointer;
transition: background 0.2s;
}
.bookmark-item:hover {
background: #3a3a3a;
}
.bookmark-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.bookmark-title {
font-size: 13px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
.bookmark-time {
font-size: 14px;
font-weight: 600;
color: #ff0000;
font-family: monospace;
}
.bookmark-note {
font-size: 12px;
color: #888;
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid #444;
}
.bookmark-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
.bookmark-actions button {
padding: 4px 8px;
font-size: 11px;
border: none;
border-radius: 4px;
cursor: pointer;
background: #444;
color: #fff;
}
.bookmark-actions button:hover {
background: #555;
}
.bookmark-actions button.delete {
background: #662222;
}
.bookmark-actions button.delete:hover {
background: #883333;
}
.empty-state {
text-align: center;
padding: 24px;
color: #666;
}
.empty-state p {
margin-bottom: 8px;
}
.empty-state .hint {
font-size: 11px;
color: #555;
}
.popup-footer {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #333;
text-align: center;
}
.danger-btn {
padding: 8px 16px;
background: #662222;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.danger-btn:hover {
background: #883333;
}
/* Scrollbar styling */
.bookmarks-list::-webkit-scrollbar {
width: 6px;
}
.bookmarks-list::-webkit-scrollbar-track {
background: #2a2a2a;
}
.bookmarks-list::-webkit-scrollbar-thumb {
background: #444;
border-radius: 3px;
}
Now let us create the popup JavaScript that handles all the interactions:
// popup.js - Popup interface logic
document.addEventListener('DOMContentLoaded', async () => {
const bookmarksList = document.getElementById('bookmarksList');
const emptyState = document.getElementById('emptyState');
const videoTitleEl = document.getElementById('videoTitle');
const currentTimestampEl = document.getElementById('currentTimestamp');
const clearAllBtn = document.getElementById('clearAllBtn');
let currentVideoId = null;
// Initialize the popup
async function init() {
// Get current tab info
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab.url && tab.url.includes('youtube.com/watch')) {
const url = new URL(tab.url);
currentVideoId = url.searchParams.get('v');
// Try to get video title from the tab
if (tab.title) {
videoTitleEl.textContent = tab.title.replace(' - YouTube', '');
}
// Get current timestamp from content script
try {
const response = await chrome.tabs.sendMessage(tab.id, { action: 'getTimestamp' });
if (response && response.timestamp !== undefined) {
currentTimestampEl.textContent = formatTime(response.timestamp);
}
} catch (e) {
console.log('Could not get current timestamp');
}
}
// Load bookmarks
loadBookmarks();
// Set up clear all button
clearAllBtn.addEventListener('click', handleClearAll);
}
// Format seconds to HH:MM:SS or MM:SS
function formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
// Load and display bookmarks
async function loadBookmarks() {
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const bookmarks = result.youtubeBookmarks || [];
// Clear current list
bookmarksList.innerHTML = '';
if (bookmarks.length === 0) {
bookmarksList.innerHTML = `
<div class="empty-state">
<p>No bookmarks yet</p>
<p class="hint">Click the bookmark button on any YouTube video to save timestamps</p>
</div>
`;
return;
}
// Render bookmarks
bookmarks.forEach(bookmark => {
const item = createBookmarkElement(bookmark);
bookmarksList.appendChild(item);
});
}
// Create a bookmark DOM element
function createBookmarkElement(bookmark) {
const item = document.createElement('div');
item.className = 'bookmark-item';
item.innerHTML = `
<div class="bookmark-header">
<span class="bookmark-title" title="${bookmark.videoTitle}">${bookmark.videoTitle}</span>
<span class="bookmark-time">${bookmark.formattedTime}</span>
</div>
${bookmark.note ? `<div class="bookmark-note">${bookmark.note}</div>` : ''}
<div class="bookmark-actions">
<button class="jump-btn">Jump to</button>
<button class="edit-btn">Edit Note</button>
<button class="delete">Delete</button>
</div>
`;
// Jump to timestamp
item.querySelector('.jump-btn').addEventListener('click', async (e) => {
e.stopPropagation();
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab.url && tab.url.includes(`v=${bookmark.videoId}`)) {
// Already on the same video, just jump to timestamp
await chrome.tabs.sendMessage(tab.id, {
action: 'seekTo',
timestamp: bookmark.timestamp
});
} else {
// Navigate to the video at the timestamp
const url = `https://www.youtube.com/watch?v=${bookmark.videoId}&t=${bookmark.timestamp}`;
await chrome.tabs.update(tab.id, { url });
}
});
// Edit note
item.querySelector('.edit-btn').addEventListener('click', async (e) => {
e.stopPropagation();
const note = prompt('Enter a note for this timestamp:', bookmark.note || '');
if (note !== null) {
await updateBookmarkNote(bookmark.id, note);
loadBookmarks();
}
});
// Delete bookmark
item.querySelector('.delete').addEventListener('click', async (e) => {
e.stopPropagation();
await deleteBookmark(bookmark.id);
loadBookmarks();
});
return item;
}
// Update a bookmark's note
async function updateBookmarkNote(bookmarkId, note) {
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const bookmarks = result.youtubeBookmarks || [];
const index = bookmarks.findIndex(b => b.id === bookmarkId);
if (index !== -1) {
bookmarks[index].note = note;
await chrome.storage.local.set({ youtubeBookmarks: bookmarks });
}
}
// Delete a bookmark
async function deleteBookmark(bookmarkId) {
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const bookmarks = result.youtubeBookmarks || [];
const filtered = bookmarks.filter(b => b.id !== bookmarkId);
await chrome.storage.local.set({ youtubeBookmarks: filtered });
}
// Clear all bookmarks
async function handleClearAll() {
if (confirm('Are you sure you want to delete all bookmarks?')) {
await chrome.storage.local.set({ youtubeBookmarks: [] });
loadBookmarks();
}
}
// Initialize
init();
});
The popup provides comprehensive bookmark management functionality. Users can view all their saved timestamps, see which video each bookmark belongs to, add or edit notes for each timestamp, jump directly to a saved timestamp in the current video, navigate to a saved timestamp in a different video, and delete individual bookmarks or clear all bookmarks at once.
Enhancing with Additional Features
While our basic youtube timestamp extension is fully functional, there are several enhancements that would make it even more useful for users seeking youtube notes functionality:
Adding Notes Support in Content Script
Update the content script to allow adding notes directly from the video page:
// Extended bookmark handler with note input
async function handleBookmarkClick(event) {
event.preventDefault();
event.stopPropagation();
const timestamp = getCurrentTimestamp();
const videoId = getVideoId();
const videoTitle = getVideoTitle();
const formattedTime = formatTime(timestamp);
// Prompt for a note
const note = prompt('Add a note for this timestamp (optional):', '');
const bookmark = {
id: Date.now().toString(),
videoId: videoId,
videoTitle: videoTitle,
timestamp: timestamp,
formattedTime: formattedTime,
note: note || '',
createdAt: new Date().toISOString()
};
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const bookmarks = result.youtubeBookmarks || [];
bookmarks.unshift(bookmark);
await chrome.storage.local.set({ youtubeBookmarks: bookmarks });
showNotification(note ? `Saved: ${formattedTime} - ${note}` : `Timestamp saved at ${formattedTime}`);
}
Keyboard Shortcuts
Adding keyboard shortcuts makes timestamp bookmarking even faster. Add this to your manifest:
"commands": {
"bookmark-timestamp": {
"suggested_key": {
"default": "Ctrl+Shift+B",
"mac": "Command+Shift+B"
},
"description": "Bookmark current YouTube timestamp"
}
}
Then handle the command in your background script:
// background.js
chrome.commands.onCommand.addListener(async (command) => {
if (command === 'bookmark-timestamp') {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab.url && tab.url.includes('youtube.com/watch')) {
await chrome.tabs.sendMessage(tab.id, { action: 'bookmarkNow' });
}
}
});
Export and Import Functionality
Users often want to back up their bookmarks or share them with others. Add export functionality:
// Export bookmarks to JSON file
async function exportBookmarks() {
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const bookmarks = result.youtubeBookmarks || [];
const blob = new Blob([JSON.stringify(bookmarks, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `youtube-timestamps-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}
// Import bookmarks from JSON file
async function importBookmarks(file) {
const reader = new FileReader();
reader.onload = async (e) => {
try {
const imported = JSON.parse(e.target.result);
if (Array.isArray(imported)) {
const result = await chrome.storage.local.get(['youtubeBookmarks']);
const existing = result.youtubeBookmarks || [];
// Merge, avoiding duplicates by ID
const existingIds = new Set(existing.map(b => b.id));
const newBookmarks = imported.filter(b => !existingIds.has(b.id));
await chrome.storage.local.set({
youtubeBookmarks: [...newBookmarks, ...existing]
});
alert(`Imported ${newBookmarks.length} new bookmarks`);
loadBookmarks();
}
} catch (err) {
alert('Error importing bookmarks: ' + err.message);
}
};
reader.readAsText(file);
}
Testing Your Extension
Before publishing your youtube timestamp extension, thorough testing is essential. Here is our recommended testing process:
Manual Testing Checklist
Load your extension in developer mode by navigating to chrome://extensions, enabling Developer mode, and clicking “Load unpacked”. Select your extension folder. Test each of the following scenarios:
Navigate to various YouTube videos and verify the bookmark button appears in the correct location. Click the bookmark button and verify the timestamp is captured accurately. Open the popup and verify all saved bookmarks appear correctly. Click on a bookmark to jump to that timestamp. Edit a bookmark note and verify it saves correctly. Delete individual bookmarks and verify they are removed. Test the clear all function. Navigate to a different video and verify the current video information updates. Test keyboard shortcuts if implemented.
Edge Cases to Consider
Your extension should handle several edge cases gracefully. When a video is paused, the timestamp should still be captured accurately. For videos with unusual URL formats, the video ID extraction should work correctly. If storage is full or unavailable, the user should see an appropriate error message. When there are zero bookmarks, the empty state should display properly. Long video titles and notes should be truncated with ellipsis.
Publishing Your Extension
Once testing is complete, you can publish your video bookmark chrome extension to the Chrome Web Store. The publishing process involves several steps:
Prepare your store listing with a clear name, compelling description, and appropriate screenshots. Compress your extension into a ZIP file. Navigate to the Chrome Web Store Developer Dashboard and create a new item. Upload your ZIP file and fill in the required information. Pay the one-time developer registration fee if you have not already. Submit for review.
When writing your store listing, emphasize the key benefits: easy one-click timestamp bookmarking, note-taking capabilities for youtube notes, organized bookmark management, and fast video navigation. Use the keywords naturally throughout your description: youtube timestamp extension, video bookmark chrome, and youtube notes.
Conclusion
Building a YouTube timestamp extension is an excellent project that teaches valuable Chrome extension development skills while creating a genuinely useful tool. The extension we built in this guide captures timestamps directly from YouTube videos, stores them persistently using Chrome storage, provides an intuitive popup interface for management, supports notes for each timestamp, and allows quick navigation to saved moments.
These same principles apply to many other extension projects. Understanding how to interact with web page APIs, manage persistent storage, and create intuitive user interfaces are skills that transfer directly to building other productivity tools, social media utilities, developer tools, and more.
The Chrome extension ecosystem continues to grow, and extensions that solve specific problems like timestamp bookmarking find ready audiences among YouTube’s billions of users. Whether you keep this extension for personal use or publish it to the Chrome Web Store, you have built a functional tool that demonstrates real software development capabilities.
Consider extending this project further with features like timestamp categories, search functionality across all bookmarks, cloud sync for cross-device access, or sharing bookmark collections with others. The foundation we built here supports all of these enhancements and more.