Build a Clipboard Manager Chrome Extension: Complete Step-by-Step Tutorial
Have you ever copied something important, then accidentally overwritten it with another copy? Or wished you could retrieve text you copied hours ago? These everyday frustrations are exactly what a clipboard manager extension solves. A well-designed clipboard history chrome extension can store multiple clipboard items, allowing users to access their copy paste manager history with a single click.
In this comprehensive tutorial, we will build a fully functional clipboard manager extension that lives in your browser toolbar. Users will be able to view their clipboard history, search through past copies, pin important items, and quickly re-copy any item back to the clipboard. By the end of this guide, you will have a complete clipboard manager extension that you can use daily and even publish to the Chrome Web Store.
This project is perfect for developers who want to learn Chrome extension development while building something genuinely useful. We will cover everything from setting up the project structure to implementing clipboard monitoring, storage persistence, search functionality, and a polished user interface.
Why Build a Clipboard Manager Extension
Before we start coding, let us discuss why building a clipboard manager extension is an excellent project choice. Copy paste manager tools are among the most popular categories in the Chrome Web Store for several compelling reasons.
First, they solve an immediate pain point. Every computer user experiences the frustration of losing important copied content. Whether it is a URL you spent time finding, a code snippet you need for a project, or contact information you typed out, accidentally overwriting it is infuriating. A clipboard history chrome extension eliminates this problem by keeping a persistent record of everything you copy.
Second, clipboard manager extensions are technically achievable for developers of all skill levels. You do not need complex backend infrastructure or database connections. Modern Chrome extensions can monitor clipboard changes and store history locally using the chrome.storage API, making the entire application client-side. This simplifies development significantly and reduces hosting costs to zero.
Third, there is significant demand in the market. Clipboard manager chrome extensions consistently rank among the top downloaded productivity tools. Building one teaches you transferable skills while potentially growing into a product with real users. The clipboard manager extension market is underserved, with many existing solutions being outdated or bloated with unnecessary features.
Project Overview and Features
Our clipboard manager extension will include the following features. First, automatic clipboard monitoring that tracks every copy action and stores it in history. Second, a popup interface displaying the clipboard history list with the most recent items at the top. Third, search functionality allowing users to quickly find specific items in their history. Fourth, pin functionality to keep important items at the top of the list permanently. Fifth, one-click re-copy that copies any historical item back to the clipboard. Sixth, delete functionality to remove individual items from history. Seventh, clear all option to reset the clipboard history. Eighth, storage persistence using chrome.storage to maintain history across browser sessions.
This feature set strikes the right balance between simplicity and usefulness. It is complex enough to teach valuable development concepts while remaining manageable for a single tutorial. The clipboard manager extension we build will be fast, lightweight, and privacy-focused with all data stored locally.
Setting Up the Project Structure
Every Chrome extension needs a specific file structure to function correctly. Let us set up our project directory first. Create a new folder named clipboard-manager-extension in your development workspace. Inside this folder, we will create the following files and directories.
The manifest.json file is the configuration file that tells Chrome about our extension. The popup.html file defines the user interface that appears when clicking the extension icon. The popup.css file styles our popup to make it visually appealing. The popup.js file contains the JavaScript logic for handling user interactions and clipboard monitoring. The background.js file runs in the background to monitor clipboard changes continuously. The icon.png file serves as the extension icon displayed in the browser toolbar. The icons folder will contain different sized icons for the extension.
Let us start by creating the manifest.json file, which is the most critical component of any Chrome extension.
{
"manifest_version": 3,
"name": "Clipboard Manager",
"version": "1.0",
"description": "A powerful clipboard manager extension for Chrome. Track your clipboard history, search past copies, and never lose important content again.",
"permissions": [
"storage",
"clipboardRead",
"clipboardWrite"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
This manifest uses Manifest V3, which is the current standard for Chrome extensions. We declare the storage permission so our extension can save clipboard history persistently. The clipboardRead and clipboardWrite permissions allow us to monitor and modify clipboard content. The background service worker enables continuous clipboard monitoring even when the popup is closed. The action property defines what happens when users click the extension icon—in this case, it opens our popup.html file.
Creating the Background Service Worker
The background service worker is the heart of our clipboard manager extension. It runs continuously in the background, monitoring the clipboard for changes and storing them in our history. Let us create the background.js file.
// background.js - Service Worker for Clipboard Manager Extension
// Maximum number of clipboard items to store
const MAX_HISTORY_ITEMS = 100;
// Initialize extension on install
chrome.runtime.onInstalled.addListener(() => {
// Initialize empty clipboard history
chrome.storage.local.set({ clipboardHistory: [] });
console.log('Clipboard Manager extension installed');
});
// Listen for clipboard changes using polling
// This is necessary because Chrome doesn't provide clipboard change events
let lastClipboardContent = '';
async function checkClipboard() {
try {
// Read clipboard content
const clipboardContent = await navigator.clipboard.readText();
// Check if content has changed and is not empty
if (clipboardContent && clipboardContent !== lastClipboardContent) {
lastClipboardContent = clipboardContent;
// Get existing history
const result = await chrome.storage.local.get('clipboardHistory');
let history = result.clipboardHistory || [];
// Check if item already exists (to avoid duplicates)
const existingIndex = history.findIndex(item => item.content === clipboardContent);
if (existingIndex !== -1) {
// Move existing item to top
const existingItem = history.splice(existingIndex, 1)[0];
existingItem.timestamp = Date.now();
history.unshift(existingItem);
} else {
// Add new item to history
const newItem = {
content: clipboardContent,
timestamp: Date.now(),
pinned: false
};
history.unshift(newItem);
}
// Trim history to max items (keep pinned items)
const pinnedItems = history.filter(item => item.pinned);
const unpinnedItems = history.filter(item => !item.pinned);
if (unpinnedItems.length > MAX_HISTORY_ITEMS) {
unpinnedItems.length = MAX_HISTORY_ITEMS;
}
history = [...pinnedItems, ...unpinnedItems];
// Save updated history
await chrome.storage.local.set({ clipboardHistory: history });
// Notify popup if open
chrome.runtime.sendMessage({ type: 'CLIPBOARD_UPDATED', history });
}
} catch (error) {
// Ignore errors from clipboard access (may happen when clipboard is empty)
if (error.name !== 'ClipboardItemNotSupportedError') {
console.log('Clipboard check error:', error);
}
}
}
// Check clipboard every second
setInterval(checkClipboard, 1000);
// Handle messages from popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_HISTORY') {
chrome.storage.local.get('clipboardHistory').then(result => {
sendResponse({ history: result.clipboardHistory || [] });
});
return true;
}
if (message.type === 'DELETE_ITEM') {
chrome.storage.local.get('clipboardHistory').then(result => {
const history = result.clipboardHistory || [];
const newHistory = history.filter((_, index) => index !== message.index);
chrome.storage.local.set({ clipboardHistory: newHistory });
sendResponse({ success: true });
});
return true;
}
if (message.type === 'PIN_ITEM') {
chrome.storage.local.get('clipboardHistory').then(result => {
const history = result.clipboardHistory || [];
history[message.index].pinned = !history[message.index].pinned;
// Reorder: pinned items first
const pinned = history.filter(item => item.pinned);
const unpinned = history.filter(item => !item.pinned);
const newHistory = [...pinned, ...unpinned];
chrome.storage.local.set({ clipboardHistory: newHistory });
sendResponse({ success: true });
});
return true;
}
if (message.type === 'COPY_ITEM') {
navigator.clipboard.writeText(message.content).then(() => {
lastClipboardContent = message.content; // Prevent re-adding
sendResponse({ success: true });
});
return true;
}
if (message.type === 'CLEAR_HISTORY') {
chrome.storage.local.get('clipboardHistory').then(result => {
const history = result.clipboardHistory || [];
const pinnedItems = history.filter(item => item.pinned);
chrome.storage.local.set({ clipboardHistory: pinnedItems });
sendResponse({ success: true });
});
return true;
}
});
This background service worker implements several key features. First, it uses polling to check the clipboard every second for changes. This is necessary because Chrome does not provide native clipboard change events. Second, it stores clipboard items with timestamps and pinned status. Third, it prevents duplicates by moving existing items to the top of the history rather than creating duplicates. Fourth, it maintains pinned items separately from regular history items, keeping them always accessible at the top. Fifth, it handles messages from the popup for various operations like deleting items, pinning items, copying items, and clearing history.
Building the Popup Interface
Now let us create the popup.html file that defines what users see when they click our extension. We will build a clean, functional interface that emphasizes ease of use.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clipboard Manager</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<header>
<h1>Clipboard Manager</h1>
<div class="header-actions">
<button id="clearAll" class="btn-secondary" title="Clear History">Clear All</button>
</div>
</header>
<div class="search-container">
<input type="text" id="search" placeholder="Search clipboard history...">
</div>
<div id="historyList" class="history-list">
<!-- Clipboard items will be inserted here -->
</div>
<div id="emptyState" class="empty-state" style="display: none;">
<p>No clipboard history yet</p>
<p class="sub-text">Copy something to get started!</p>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
This HTML structure provides a clean interface with a header containing the extension title and a clear all button. There is a search input for filtering clipboard history. The history list container will hold all clipboard items. An empty state message appears when there is no history to display.
Styling the Popup
Now let us create the popup.css file to make our extension visually appealing. We will use a modern, clean design with good visual hierarchy.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
width: 350px;
min-height: 400px;
max-height: 500px;
background-color: #f5f5f5;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background-color: #ffffff;
border-bottom: 1px solid #e0e0e0;
}
header h1 {
font-size: 18px;
font-weight: 600;
color: #333;
}
.btn-secondary {
padding: 6px 12px;
font-size: 12px;
background-color: #f0f0f0;
border: none;
border-radius: 4px;
cursor: pointer;
color: #666;
transition: background-color 0.2s;
}
.btn-secondary:hover {
background-color: #e0e0e0;
}
.search-container {
padding: 12px 16px;
background-color: #ffffff;
}
#search {
width: 100%;
padding: 10px 12px;
font-size: 14px;
border: 1px solid #e0e0e0;
border-radius: 6px;
outline: none;
transition: border-color 0.2s;
}
#search:focus {
border-color: #4285f4;
}
.history-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.clipboard-item {
background-color: #ffffff;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
cursor: pointer;
transition: box-shadow 0.2s, transform 0.1s;
position: relative;
}
.clipboard-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.clipboard-item.pinned {
border-left: 3px solid #4285f4;
}
.clipboard-content {
font-size: 13px;
color: #333;
line-height: 1.4;
word-break: break-word;
max-height: 60px;
overflow: hidden;
text-overflow: ellipsis;
}
.clipboard-content.url {
color: #1a73e8;
}
.clipboard-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
font-size: 11px;
color: #999;
}
.item-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.2s;
}
.clipboard-item:hover .item-actions {
opacity: 1;
}
.action-btn {
padding: 4px 8px;
font-size: 11px;
background-color: #f5f5f5;
border: none;
border-radius: 4px;
cursor: pointer;
color: #666;
}
.action-btn:hover {
background-color: #e8e8e8;
}
.action-btn.pin-btn.active {
color: #4285f4;
background-color: #e8f0fe;
}
.action-btn.delete-btn:hover {
color: #d93025;
background-color: #fce8e6;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
color: #666;
}
.empty-state p {
margin-bottom: 8px;
}
.empty-state .sub-text {
font-size: 13px;
color: #999;
}
/* Scrollbar styling */
.history-list::-webkit-scrollbar {
width: 6px;
}
.history-list::-webkit-scrollbar-track {
background: transparent;
}
.history-list::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 3px;
}
.history-list::-webkit-scrollbar-thumb:hover {
background-color: #999;
}
This CSS provides a polished, modern interface with proper spacing, typography, and visual feedback. The design includes hover effects on clipboard items, clear action buttons, pinned item styling, and a clean search input. The scrollbar is styled to match the overall design.
Implementing Popup Logic
Now let us create the popup.js file that handles user interactions and displays the clipboard history.
// popup.js - Popup Logic for Clipboard Manager Extension
let clipboardHistory = [];
let filteredHistory = [];
// Initialize popup
document.addEventListener('DOMContentLoaded', async () => {
await loadHistory();
setupEventListeners();
});
// Load clipboard history from storage
async function loadHistory() {
const response = await chrome.runtime.sendMessage({ type: 'GET_HISTORY' });
clipboardHistory = response.history || [];
filteredHistory = [...clipboardHistory];
renderHistory();
}
// Setup event listeners
function setupEventListeners() {
// Search functionality
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
if (query) {
filteredHistory = clipboardHistory.filter(item =>
item.content.toLowerCase().includes(query)
);
} else {
filteredHistory = [...clipboardHistory];
}
renderHistory();
});
// Clear all button
document.getElementById('clearAll').addEventListener('click', async () => {
if (confirm('Are you sure you want to clear all non-pinned items?')) {
await chrome.runtime.sendMessage({ type: 'CLEAR_HISTORY' });
await loadHistory();
}
});
}
// Render clipboard history
function renderHistory() {
const historyList = document.getElementById('historyList');
const emptyState = document.getElementById('emptyState');
historyList.innerHTML = '';
if (filteredHistory.length === 0) {
emptyState.style.display = 'flex';
return;
}
emptyState.style.display = 'none';
filteredHistory.forEach((item, index) => {
const itemElement = createItemElement(item, index);
historyList.appendChild(itemElement);
});
}
// Create HTML element for a clipboard item
function createItemElement(item, index) {
const div = document.createElement('div');
div.className = `clipboard-item ${item.pinned ? 'pinned' : ''}`;
const content = document.createElement('div');
content.className = `clipboard-content ${isUrl(item.content) ? 'url' : ''}`;
content.textContent = item.content;
content.title = item.content;
const meta = document.createElement('div');
meta.className = 'clipboard-meta';
const timestamp = document.createElement('span');
timestamp.textContent = formatTimestamp(item.timestamp);
const actions = document.createElement('div');
actions.className = 'item-actions';
const pinBtn = document.createElement('button');
pinBtn.className = `action-btn pin-btn ${item.pinned ? 'active' : ''}`;
pinBtn.textContent = item.pinned ? 'Unpin' : 'Pin';
pinBtn.addEventListener('click', (e) => {
e.stopPropagation();
togglePin(index);
});
const deleteBtn = document.createElement('button');
deleteBtn.className = 'action-btn delete-btn';
deleteBtn.textContent = 'Delete';
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
deleteItem(index);
});
actions.appendChild(pinBtn);
actions.appendChild(deleteBtn);
meta.appendChild(timestamp);
meta.appendChild(actions);
div.appendChild(content);
div.appendChild(meta);
// Click to copy
div.addEventListener('click', () => {
copyItem(item.content);
});
return div;
}
// Copy item to clipboard
async function copyItem(content) {
await chrome.runtime.sendMessage({
type: 'COPY_ITEM',
content: content
});
// Visual feedback
const btn = event.target.closest('.clipboard-item');
if (btn) {
btn.style.backgroundColor = '#e8f0fe';
setTimeout(() => {
btn.style.backgroundColor = '';
}, 200);
}
}
// Toggle pin status
async function togglePin(index) {
await chrome.runtime.sendMessage({
type: 'PIN_ITEM',
index: index
});
await loadHistory();
}
// Delete item from history
async function deleteItem(index) {
await chrome.runtime.sendMessage({
type: 'DELETE_ITEM',
index: index
});
await loadHistory();
}
// Check if text is a URL
function isUrl(text) {
try {
new URL(text);
return true;
} catch {
return false;
}
}
// Format timestamp to readable format
function formatTimestamp(timestamp) {
const now = Date.now();
const diff = now - timestamp;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60) return 'Just now';
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
if (days < 7) return `${days}d ago`;
return new Date(timestamp).toLocaleDateString();
}
// Listen for clipboard updates from background
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'CLIPBOARD_UPDATED') {
clipboardHistory = message.history || [];
filteredHistory = [...clipboardHistory];
// Re-apply search filter if search is active
const searchInput = document.getElementById('search');
if (searchInput.value) {
const query = searchInput.value.toLowerCase();
filteredHistory = clipboardHistory.filter(item =>
item.content.toLowerCase().includes(query)
);
}
renderHistory();
}
});
This JavaScript implements all the interactive features of our clipboard manager extension. It loads history from storage, renders the list with proper formatting, implements search filtering, handles pin and delete actions, and provides visual feedback when copying items. The code also handles URL detection to display URLs in a different color and formats timestamps in a human-readable way.
Creating Extension Icons
Every Chrome extension needs icons to display in the browser toolbar and Chrome Web Store. You will need to create three icon sizes: 16x16, 48x48, and 128x128 pixels. Create an icons folder in your project directory and add the following icons.
For development purposes, you can use placeholder icons or create simple ones using any image editor. The icons should represent a clipboard or clipboard with a history indicator. Make sure the icons have transparent backgrounds and use a consistent visual style.
If you do not have icons ready, you can use a placeholder approach by creating a simple colored square for now. However, for a production extension, you should invest time in creating professional icons that represent your product well.
Testing the Extension
Now that we have created all the necessary files, let us test our clipboard manager extension. First, make sure your project structure looks like this.
Your clipboard-manager-extension folder should contain the manifest.json file, popup.html file, popup.css file, popup.js file, background.js file, and an icons folder with icon16.png, icon48.png, and icon128.png files.
To test the extension in Chrome, follow these steps. Open Chrome and navigate to chrome://extensions/. Enable Developer mode using the toggle in the top right corner. Click the Load unpacked button in the top left. Select your clipboard-manager-extension folder. The extension should now appear in your Chrome toolbar.
Click the extension icon to open the popup. Try copying some text from any website. Wait a moment for the background service worker to detect the change. Click the extension icon again to see your copied text appear in the history. Test the pin, delete, and copy functionality to ensure everything works correctly.
If you encounter any issues, right-click the extension icon and select Inspect popup to open the developer console. Check the Console tab for any error messages that might help diagnose the problem.
Understanding the Clipboard API Limitations
It is important to understand some limitations of the clipboard API in Chrome extensions. The polling approach we use in background.js works well for most use cases but has some considerations.
First, there is a slight delay in detecting clipboard changes due to the one-second polling interval. For most users, this is imperceptible, but if you need real-time detection, you would need to reduce the interval at the cost of slightly higher resource usage.
Second, the clipboard API requires the page to be on a secure context (HTTPS) or localhost. This should not be an issue for extension background scripts, but it is worth noting.
Third, some websites may use techniques to prevent clipboard access or may have their own clipboard event handling that could interfere with detection. Our implementation is robust enough for most use cases.
Fourth, large clipboard items may impact storage and performance. Consider adding character limits in a production version if needed.
Enhancements and Future Improvements
Our clipboard manager extension is fully functional, but there are many enhancements you could add to make it even more powerful. Here are some suggestions for future improvements.
First, add categories and tags to organize clipboard items automatically. You could detect URLs, code snippets, email addresses, and phone numbers and categorize them accordingly.
Second, implement cloud sync to access your clipboard history across multiple devices. This would require a backend service and user authentication.
Third, add keyboard shortcuts for power users. For example, you could implement a global shortcut to open the clipboard manager from anywhere in Chrome.
Fourth, add support for images and other file types, not just text. The clipboard API supports various MIME types that you could explore.
Fifth, implement a favorites system separate from pinned items for even more organization options.
Sixth, add export and import functionality to backup and restore clipboard history.
Conclusion
Congratulations! You have successfully built a complete clipboard manager extension for Chrome. This extension includes all the essential features of a copy paste manager: automatic clipboard monitoring, history storage, search functionality, pin and delete options, and persistent storage across browser sessions.
The extension demonstrates several important Chrome extension development concepts. You learned how to create a background service worker for continuous monitoring. You implemented chrome.storage for persistent data storage. You built a popup interface with HTML, CSS, and JavaScript. You handled message passing between the popup and background scripts. You created a polished user interface with proper styling and visual feedback.
This clipboard manager extension is ready for personal use, and with some additional polish, it could be published to the Chrome Web Store. The code follows best practices and is structured in a way that makes it easy to extend with additional features.
Building a clipboard history chrome extension is an excellent portfolio project that demonstrates your ability to create practical, user-facing applications. The skills you learned in this tutorial transfer directly to building other types of Chrome extensions and web applications.
Project Files Summary
Here is a summary of all the files we created in this tutorial. The manifest.json file configures the extension with proper permissions and background service worker. The background.js file handles clipboard monitoring and storage. The popup.html file defines the user interface. The popup.css file provides the styling. The popup.js file implements user interactions and history display.
You now have a complete, working clipboard manager extension that solves real problems for users. Consider adding some final touches like improving the icons, adding a nice icon badge showing the number of items, and testing across different scenarios before using it daily or sharing it with others.
Turn Your Extension Into a Business
Ready to monetize? The Extension Monetization Playbook covers freemium models, Stripe integration, subscription architecture, and growth strategies for Chrome extension developers.
Built by theluckystrike at zovo.one