March 27, 202610 min read

Build a Chrome Extension: Step-by-Step (No Prior Experience Needed)

Learn to build Chrome extensions with Manifest V3. Popups, content scripts, service workers, storage, and a complete bookmark manager project.

chrome-extension javascript browser project tutorial
Ad 336x280

Chrome extensions are one of the most satisfying things you can build as a developer. You write a bit of JavaScript, and suddenly your browser does something it couldn't before. No server needed. No deployment pipeline. Just HTML, CSS, JS, and a manifest file.

And the barrier to entry is low. If you can write basic JavaScript, you can build a Chrome extension today. Not tomorrow, not after another course. Today.

By the end of this tutorial, you'll understand every piece of a Chrome extension and you'll have built a functional bookmark manager with tags, search, and folder organization.

How Chrome Extensions Work

A Chrome extension is a zip file containing web files (HTML, CSS, JS, images) plus a manifest.json that tells Chrome what the extension does and what permissions it needs.

There are three main execution contexts:

  1. Popup -- The little window that appears when you click the extension icon. Standard HTML page.
  2. Content Scripts -- JavaScript that runs inside web pages. Can read and modify the DOM of any page you visit.
  3. Background Service Worker -- Runs independently. Handles events, manages state, coordinates between components. No DOM access.
These three contexts can't share variables directly. They communicate through Chrome's messaging APIs. This is the part that confuses people, and we'll cover it thoroughly.

Project Setup

Create a folder for your extension:

my-extension/
  manifest.json
  popup.html
  popup.css
  popup.js
  background.js
  content.js
  icons/
    icon16.png
    icon48.png
    icon128.png

The Manifest: manifest.json (V3)

Every extension starts here. Manifest V3 is the current standard -- V2 is deprecated and Chrome is phasing it out.

{
  "manifest_version": 3,
  "name": "My First Extension",
  "version": "1.0",
  "description": "A simple Chrome extension",
  "permissions": ["storage", "tabs", "activeTab"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

Key fields:

  • manifest_version: 3 -- Required. Use 3, not 2.
  • permissions -- What APIs your extension can access. Only request what you need.
  • action -- Defines the toolbar icon and popup.
  • background.service_worker -- Your background script (replaces background.page from V2).
  • content_scripts -- Scripts injected into matching pages.

Loading Your Extension for Development

  1. Open chrome://extensions
  2. Enable "Developer mode" (top right toggle)
  3. Click "Load unpacked"
  4. Select your extension folder
Every time you change code, click the refresh icon on your extension card. For content script changes, you also need to refresh the target web page.

Building the Popup

The popup is just an HTML page. It loads when the user clicks your extension icon.

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h1>Quick Bookmark</h1>
    <button id="saveBtn">Save This Page</button>
    <div id="status"></div>
    <div id="bookmarks"></div>
  </div>
  <script src="popup.js"></script>
</body>
</html>
/ popup.css /
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
width: 350px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding: 16px;
}

.container {
display: flex;
flex-direction: column;
gap: 12px;
}

h1 {
font-size: 18px;
color: #1a1a1a;
}

button {
padding: 10px 16px;
background: #4285f4;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}

button:hover {
background: #3367d6;
}

.bookmark-item {
padding: 8px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 13px;
}

.bookmark-item a {
color: #1a73e8;
text-decoration: none;
}

// popup.js
document.addEventListener('DOMContentLoaded', () => {
  const saveBtn = document.getElementById('saveBtn');
  const status = document.getElementById('status');
  const bookmarksList = document.getElementById('bookmarks');

// Save current tab
saveBtn.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

const bookmark = {
title: tab.title,
url: tab.url,
savedAt: new Date().toISOString()
};

// Get existing bookmarks
const { bookmarks = [] } = await chrome.storage.local.get('bookmarks');
bookmarks.unshift(bookmark);
await chrome.storage.local.set({ bookmarks });

status.textContent = 'Saved!';
setTimeout(() => { status.textContent = ''; }, 2000);
renderBookmarks(bookmarks);
});

// Load bookmarks on popup open
loadBookmarks();

async function loadBookmarks() {
const { bookmarks = [] } = await chrome.storage.local.get('bookmarks');
renderBookmarks(bookmarks);
}

function renderBookmarks(bookmarks) {
bookmarksList.innerHTML = bookmarks.slice(0, 10).map(b =>
<div class="bookmark-item">
<a href="${b.url}" target="_blank">${b.title}</a>
</div>
).join('');
}
});

Note: You can't use inline scripts in Chrome extensions (Content Security Policy). Always use separate .js files and link them with