Building Your First Chrome Extension: A Self-Taught Developer's Walkthrough
Building your first Chrome extension is one of the better small projects a self-taught developer can take on. The scope is contained, the technology stack is straightforward, the result is something you actually use, and the learning crosses multiple practical web development areas. After helping a few developers through their first extension projects, I’ve found a pattern that works.
This is a working walkthrough of building a useful extension from scratch in 2026. The project — a simple bookmark organiser that improves on Chrome’s built-in functionality — is concrete enough to be motivating and contained enough to actually finish.
What You’ll Need
The setup for Chrome extension development is minimal:
A text editor or IDE. VS Code is the standard recommendation. Whatever you’re comfortable with works.
Chrome itself. The extension development workflow uses Chrome’s developer mode.
A working knowledge of HTML, CSS, and JavaScript. The extension code is just web code with some specific APIs.
Familiarity with Chrome DevTools for debugging.
That’s about it. No build tools required for a simple extension. No frameworks required. No backend infrastructure required.
Understanding the Manifest V3 Reality
Chrome extension development in 2026 has fully transitioned to Manifest V3. The older Manifest V2 patterns are no longer supported. This affects what tutorials and example code remain relevant.
The key Manifest V3 differences from V2:
Service workers replace background pages. Long-running background logic happens in service workers that can be terminated and restarted by Chrome.
Network request handling has changed substantially. The webRequest API still exists but with limitations. The declarativeNetRequest API is the preferred approach for many request manipulation use cases.
The permissions model is more granular. Users approve specific permissions and you should request only what you actually need.
Many older extension patterns no longer work without modification. Tutorial code from 2020-2022 often needs significant adaptation.
The practical advice is to follow current 2025-2026 tutorials rather than older material, and to specifically check that any example code you copy works in current Chrome versions.
The Project Structure
A basic Chrome extension is a folder containing a few specific files:
manifest.json — the extension’s configuration file that tells Chrome what the extension does and what permissions it needs.
A background script (typically background.js) — the service worker that handles persistent logic.
Content scripts if needed — scripts that run in the context of web pages the extension affects.
UI files — HTML, CSS, and JavaScript for any popup or options page the extension shows.
Icons — images in specific sizes for the extension’s various display contexts.
For our bookmark organiser project, the structure looks like:
bookmark-organiser/
├── manifest.json
├── background.js
├── popup.html
├── popup.css
├── popup.js
├── icons/
│ ├── icon-16.png
│ ├── icon-32.png
│ ├── icon-48.png
│ └── icon-128.png
The Manifest File
The manifest.json for our extension:
{
"manifest_version": 3,
"name": "Bookmark Organiser",
"version": "1.0.0",
"description": "Organise bookmarks with tags and search",
"permissions": ["bookmarks", "storage"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
},
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}
The manifest tells Chrome what the extension is called, what version of the manifest format it uses, what permissions it needs, and what user-facing elements it provides.
The Popup
The popup.html is what appears when the user clicks the extension icon. For our bookmark organiser, we want a simple interface with a search box and a list of bookmarks:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<input type="text" id="search" placeholder="Search bookmarks...">
<div id="bookmark-list"></div>
<script src="popup.js"></script>
</body>
</html>
The popup.css handles basic styling — making the popup look reasonable rather than the unstyled default appearance.
The popup.js handles the logic — loading bookmarks, responding to search input, and displaying results.
The Bookmarks API
The Chrome bookmarks API gives the extension access to read and modify the user’s bookmarks. Loading all bookmarks:
chrome.bookmarks.getTree((bookmarkTreeNodes) => {
// bookmarkTreeNodes is an array containing the bookmark tree
// Process it to extract all bookmarks
});
For our extension, we want to flatten the bookmark tree into a list:
function flattenBookmarks(nodes, result = []) {
for (const node of nodes) {
if (node.url) {
result.push({
id: node.id,
title: node.title,
url: node.url
});
}
if (node.children) {
flattenBookmarks(node.children, result);
}
}
return result;
}
chrome.bookmarks.getTree((tree) => {
const allBookmarks = flattenBookmarks(tree);
// Now we have a flat array of all bookmarks
});
Implementing Search
The search functionality is straightforward — filter the bookmark list based on what the user types:
const searchInput = document.getElementById('search');
const bookmarkList = document.getElementById('bookmark-list');
let allBookmarks = [];
function renderBookmarks(bookmarks) {
bookmarkList.innerHTML = '';
for (const bookmark of bookmarks) {
const item = document.createElement('div');
item.className = 'bookmark-item';
const link = document.createElement('a');
link.href = bookmark.url;
link.textContent = bookmark.title;
link.target = '_blank';
item.appendChild(link);
bookmarkList.appendChild(item);
}
}
searchInput.addEventListener('input', () => {
const query = searchInput.value.toLowerCase();
if (!query) {
renderBookmarks(allBookmarks);
return;
}
const filtered = allBookmarks.filter(bookmark =>
bookmark.title.toLowerCase().includes(query) ||
bookmark.url.toLowerCase().includes(query)
);
renderBookmarks(filtered);
});
chrome.bookmarks.getTree((tree) => {
allBookmarks = flattenBookmarks(tree);
renderBookmarks(allBookmarks);
});
Adding Tags
The bookmark organiser improves on Chrome’s built-in functionality by adding tags. The Chrome bookmarks API doesn’t natively support tags, so we store tag data ourselves using the storage API:
async function getTags(bookmarkId) {
const result = await chrome.storage.local.get(`tags_${bookmarkId}`);
return result[`tags_${bookmarkId}`] || [];
}
async function setTags(bookmarkId, tags) {
await chrome.storage.local.set({
[`tags_${bookmarkId}`]: tags
});
}
The UI for tag management can be added to the bookmark display with appropriate input handling.
Loading and Testing
Loading the extension for testing:
- Open Chrome and navigate to chrome://extensions/
- Enable “Developer mode” via the toggle in the top right
- Click “Load unpacked”
- Select your extension folder
- The extension appears in your toolbar
Any changes to the extension code require reloading via the refresh button on the extension card in chrome://extensions/.
The debugging workflow uses Chrome’s standard DevTools. Right-click the extension icon and select “Inspect popup” to debug the popup. The background service worker can be debugged from the extension card by clicking “service worker”.
The Common Pitfalls
A few issues that typically catch first-time extension developers:
Permissions confusion. The manifest needs to list every API the extension uses. Missing permissions produce silent failures.
Service worker lifecycle. The service worker can be terminated by Chrome. Don’t rely on in-memory state persisting between events.
Content script isolation. Content scripts run in an isolated world that doesn’t share the page’s JavaScript context. Communication with the page requires specific patterns.
Cross-origin requests. The fetch API in extensions has different rules than in regular web pages. Network requests need careful handling.
Async/await with Chrome APIs. The older Chrome APIs used callbacks. The newer ones support promises. Mixing the patterns can produce subtle bugs.
The Chrome extension documentation is generally good but spread across many pages. Bookmark the API reference for the specific APIs you’re using.
Publishing to the Chrome Web Store
After developing the extension, publishing involves:
Creating a developer account on the Chrome Web Store (one-time fee).
Packaging the extension as a zip file containing the manifest and all required files.
Submitting through the Chrome Web Store developer dashboard with description, screenshots, and required metadata.
Waiting for review — typically a few days to a week.
Addressing any issues identified in review.
The review process has become more demanding over the years. Extensions that request broad permissions, that handle sensitive user data, or that have complex functionality face more scrutiny. Be prepared for a back-and-forth with reviewers.
For your first extension, the review process is good experience even if you don’t ultimately publish widely. The discipline of preparing the extension for review forces good development practices.
What to Build Next
Once you’ve built your first extension, the natural progression is to either:
Improve the first extension based on actual use. The features that matter become obvious once you’re using it daily.
Build a second extension addressing a different need. The second is much faster than the first because the patterns are familiar.
Contribute to an existing open-source extension. The codebases for many existing extensions are accessible and contributions are welcomed.
The Chrome extension category has become more competitive over the years but there’s still room for thoughtful tools addressing specific user needs. The bar for being useful is generally lower than for being widely adopted.
The Skills You’ve Built
Building this extension has exercised:
Web development fundamentals — HTML, CSS, JavaScript working together.
Browser API usage with documentation reading and example adaptation.
Asynchronous JavaScript patterns including promises and async/await.
Event-driven programming with user interaction handling.
Data persistence with the storage API.
Distribution and publishing of software to end users.
These skills transfer directly to broader web development, browser-based tool development, and product thinking about user-facing software. The Chrome extension is one of the few projects that practically combines all of these skills at a tractable scale for a first independent project.
The Bigger Lesson
The Chrome extension project illustrates something I’ve found useful in self-taught development generally — small, complete projects that produce something you actually use teach more than large incomplete projects that aim for impressive scope. The discipline of finishing something, dealing with the unglamorous deployment work, and using your own creation reveals what software development actually involves in ways that tutorial completion doesn’t.
For self-taught developers looking for projects that teach real skills, Chrome extensions remain among the best options. The scope is right, the technology is accessible, the result is tangible, and the experience prepares you for larger projects that build on the same fundamentals.
Build the small thing that you’d actually use. Ship it. Use it. The lessons that come out of that complete cycle are what move you forward as a developer.