How to Build a Chrome Extension from Scratch: A Complete Step‑by‑Step Guide

This article walks you through the fundamentals of Chrome extension development, covering its advantages, required prerequisites, manifest file creation, core components such as popup pages, background scripts, content scripts, and context menus, complete with practical code examples and configuration details.

FunTester
FunTester
FunTester
How to Build a Chrome Extension from Scratch: A Complete Step‑by‑Step Guide

Advantages of Chrome Extensions

Simple and fast operation – extensions can be launched directly from the toolbar or via keyboard shortcuts without navigating to a separate web page.

Low development cost – only a manifest.json file that follows Chrome’s specification is required; existing front‑end code can be reused.

High development efficiency – the UI is lightweight, so most functionality can be built quickly with HTML, CSS and JavaScript.

Preparation

You should be comfortable with HTML, CSS and JavaScript, understand the Chrome extension APIs and the Manifest V3 specification, and have a code editor (e.g., WebStorm, VS Code) ready.

Step 1: Create manifest.json

The manifest defines metadata, permissions, and component wiring. A minimal Manifest V3 example:

{
  "manifest_version": 3,
  "name": "FunTester Extension",
  "version": "1.0",
  "description": "A simple Chrome extension example.",
  "icons": {
    "48": "icon48.png",
    "128": "icon128.png"
  },
  "permissions": ["tabs", "storage", "contextMenus"],
  "background": {"service_worker": "background.js"},
  "action": {"default_popup": "popup.html", "default_icon": "icon48.png"},
  "content_scripts": [{"matches": ["<all_urls>"], "js": ["content.js"]}],
  "web_accessible_resources": [{"resources": ["content.js"], "matches": ["<all_urls>"]}]
}

Key fields in manifest.json

manifest_version : currently 3 (or 2 for legacy extensions).

name , version , description : basic extension information.

icons : icon files for the toolbar and Chrome Web Store.

permissions : list of required Chrome APIs (e.g., tabs, storage, contextMenus).

background : declares the service worker ( background.js) that runs in the extension’s background context.

action : configures the default popup UI shown when the user clicks the extension icon.

content_scripts : scripts injected into matching pages.

web_accessible_resources : resources that web pages are allowed to load from the extension.

Common features

Popup page

The popup appears when the user clicks the extension icon. It is defined by the action.default_popup field and consists of an HTML file with optional CSS and JavaScript.

{
  "manifest_version": 3,
  "name": "FunTester Extension",
  "version": "1.0",
  "action": {"default_popup": "popup.html", "default_icon": "icon48.png"}
}

Example popup.html:

<!DOCTYPE html>
<html>
<head>
  <title>Popup</title>
  <style>
    body {width:300px;height:200px;font-family:Arial;padding:10px;}
    button {margin-top:20px;}
  </style>
</head>
<body>
  <h1>Hello, World!</h1>
  <p>This is a simple popup example.</p>
  <button id="myButton">Click me</button>
  <script src="popup.js"></script>
</body>
</html>

Handle the button click in popup.js:

document.addEventListener('DOMContentLoaded', function() {
  document.getElementById('myButton').addEventListener('click', function() {
    alert('Button clicked!');
  });
});

Background script

The background script runs as a service worker (MV3) or as a persistent script (MV2) and can manage lifecycle events, storage, messaging, and network requests.

MV3 configuration (already shown in the manifest) uses service_worker. For reference, MV2 uses:

{
  "manifest_version": 2,
  "background": {"scripts": ["background.js"], "persistent": false}
}

Typical background tasks:

Installation handling

chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    console.log('Extension installed');
    chrome.storage.local.set({initialized: true});
  } else if (details.reason === 'update') {
    console.log('Extension updated to version ' + chrome.runtime.getManifest().version);
  }
});

Message passing

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'GREETINGS') {
    console.log('Received message from', sender.tab ? 'content script' : 'popup', ':', message.payload);
    sendResponse({message: 'Hello from background script'});
  }
});

Browser event listeners

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    console.log('Tab updated:', tabId);
  }
});

Persistent storage

// Set a value
chrome.storage.local.set({key: 'value'}, () => console.log('Value is set'));
// Get the value
chrome.storage.local.get(['key'], (result) => console.log('Current value:', result.key));

Periodic tasks

setInterval(() => console.log('Periodic task running...'), 60000); // every minute

Network requests

function fetchData() {
  fetch('https://api.example.com/data')
    .then(r => r.json())
    .then(data => console.log(data))
    .catch(err => console.error('Error fetching data:', err));
}
setInterval(fetchData, 3600000); // every hour

Content scripts

Content scripts run in the context of matched web pages, allowing DOM manipulation and interaction.

{
  "manifest_version": 3,
  "content_scripts": [{"matches": ["<all_urls>"], "js": ["content.js"]}]
}

Example content.js that injects a div into the page:

console.log('Content script loaded');

document.addEventListener('DOMContentLoaded', function() {
  const div = document.createElement('div');
  div.textContent = 'This content is injected by Chrome extension.';
  document.body.appendChild(div);
});

Typical responsibilities include DOM operations, event listening, data injection, content filtering, messaging with background or popup scripts, data collection, page state monitoring, and integration with third‑party services.

Context (right‑click) menu

To add a custom context‑menu entry, declare the contextMenus permission and create the menu in the background script.

"permissions": ["contextMenus"]
// Create a menu item
chrome.contextMenus.create({
  id: "myContextMenu",
  title: "Do Something",
  contexts: ["page", "selection", "link"]
});

// Listen for clicks
chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "myContextMenu") {
    console.log('Context menu item clicked:', info);
    // Perform desired action here
  }
});
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendJavaScriptChrome ExtensionWeb DevelopmentManifest V3
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.