Frontend Development 16 min read

A Complete Guide to Building Chrome Browser Extensions: Manifest, Background, Content & Popup Scripts

This tutorial explains what browser extensions are, walks through creating a simple Chrome extension with a manifest file, adds icons and a popup page, details background, content‑scripts, inject‑scripts, permissions, inter‑script communication, and demonstrates a practical HTTP‑Header extension example.

ByteFE
ByteFE
ByteFE
A Complete Guide to Building Chrome Browser Extensions: Manifest, Background, Content & Popup Scripts

Browser extensions are tools built with web technologies (HTML, CSS, JavaScript) that add functionality not provided by the browser itself.

Example Extensions

Examples include FeHelper.JSON (JSON formatting, encoding, markdown, code minification), a QR‑code generator, and SwitchyOmega Proxy.

Hello World Extension

The core of any Chrome extension is a manifest.json placed in the root folder. A minimal manifest looks like:

{
  "manifest_version": 2,
  "name": "my-plugin",
  "version": "0.1.0"
}

After creating the manifest, open chrome://extensions/ , enable Developer Mode, and load the unpacked extension folder. The extension appears with an icon in the toolbar.

Making the Extension Look Like a Real Plugin

To give the extension an icon, description, and a popup page, add the following fields to the manifest:

{
  ...
  "description": "This is a description",
  "icons": {"84": "./icon/ball.png"},
  "browser_action": {
    "default_icon": "./icon/ball.png",
    "default_title": "My Plugin",
    "default_popup": "./html/popup.html"
  }
}

The directory structure now includes icon/ball.png , html/popup.html , and the manifest.

popup.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>my-plugin</title>
</head>
<body>
  <p style="width:200px;text-align:center;">hello world!!</p>
</body>
</html>

After reloading the extension, the toolbar shows the new icon and clicking it opens the popup.

manifest.json Configuration Details

background

The background entry defines a persistent page or scripts that run as long as the browser is open. Example:

{
  "background": {
    "page": "./html/background.html"
    // or
    "scripts": ["./js/background.js"]
  }
}

Only one of page or scripts may be used. Setting "persistent": false makes the background unload when idle.

content_scripts

Content scripts are injected into matching pages and can modify the DOM but not the page’s JavaScript context. Example configuration:

{
  "content_scripts": [
    {
      "matches": ["
"],
      "js": ["./js/content.js"],
      "css": ["./css/style.css"],
      "run_at": "document_start"
    },
    {
      "matches": ["https://www.baidu.com/"],
      "js": ["./js/other.js"],
      "run_at": "document_start"
    }
  ]
}

Sample content.js simply logs a message:

console.log('hello, from content.js');

Both content.js and other.js run on pages that match their rules.

inject_scripts

Inject scripts are added to the page via DOM insertion, allowing access to the page’s JavaScript. They must be listed in web_accessible_resources and loaded with chrome.extension.getURL :

{
  "content_scripts": [{
    "matches": ["
"],
    "js": ["./js/content.js"],
    "run_at": "document_end"
  }],
  "web_accessible_resources": ["js/inject.js"]
}
function mockApi() {
  console.log('this is from inject.js');
}

In content.js the script is injected:

(function() {
  let path = 'js/inject.js';
  let script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = chrome.extension.getURL(path);
  script.onload = function() { this.parentNode.removeChild(this); };
  document.body.appendChild(script);
})();

permissions

Extensions need explicit permissions for storage, web requests, notifications, etc. Example:

{
  "permissions": [
    "contextMenus",
    "tabs",
    "notifications",
    "webRequest",
    "webRequestBlocking",
    "storage"
  ]
}

Communication Between Scripts

popup ↔ background

// popup.js
var backend = chrome.extension.getBackgroundPage();
backend.test();

Background can obtain popup views via chrome.extension.getViews({type:'popup'}) .

content ↔ background

// content script
chrome.runtime.sendMessage('message content', (res) => {
  console.log('from background:', res);
});

// background
chrome.runtime.onMessage.addListener(function(message, sender, callback) {
  console.log(message);
  callback && callback('yes this from background');
});

Background can also send messages to a specific tab using chrome.tabs.query and chrome.tabs.sendMessage .

inject ↔ content

// inject.js
window.postMessage({"test": "Hello!"}, '*');
// content script
window.addEventListener('message', function(message) {
  console.log('Received:', message.data);
}, false);

Hands‑On Exercise: HTTP Header Extension

The exercise builds an extension that dynamically adds custom HTTP headers to all requests.

Background Design

const headers = [
  {key: 'Content-Type', value: 'application/x-www-form-urlencoded', enable: false},
  {key: 'Test-Header', value: 'Custom Value', enable: true}
];
function getHeaders() { return headers; }
function addHeader(h) { headers.push(h); }
function deleteHeader(i) { headers.splice(i,1); }
function toggleHeader(i) { headers[i].enable = !headers[i].enable; }
chrome.runtime.onInstalled.addListener(function(){
  chrome.webRequest.onBeforeSendHeaders.addListener(requestHandle, {urls:['
']}, ['blocking','requestHeaders']);
});
function requestHandle(request){
  let reqHeaders = request.requestHeaders;
  headers.forEach(item=>{ if(item.enable){ reqHeaders.push({name:item.key,value:item.value}); } });
  return {requestHeaders: reqHeaders};
}

Popup UI

// popup.js
window.onload = function(){
  let bg = chrome.extension.getBackgroundPage();
  let hdrs = bg.getHeaders();
  createElement(hdrs);
};
function addHeader(){
  let bg = chrome.extension.getBackgroundPage();
  let key = document.querySelector('.key').value;
  let value = document.querySelector('.value').value;
  bg.addHeader({key, value, enable:true});
  createElement({key,value,enable:true});
}
function toggleHeader(i){ chrome.extension.getBackgroundPage().toggleHeader(i); }
function delHeader(i){ chrome.extension.getBackgroundPage().deleteHeader(i); }

After adding a header via the popup, opening the developer console on any page shows the new request header.

Summary

Many capabilities and permissions must be declared in manifest.json .

background, content‑scripts, popup, and inject‑scripts have distinct permission sets and communication patterns; understanding their roles enables powerful extensions.

Debugging can be done in the background page, popup, or directly in the page’s DevTools.

For more details see the official Chrome extension documentation and related blog posts.

JavaScriptChrome ExtensionManifestPopupContent ScriptBackground ScriptWebRequest
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login 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.