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.
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.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.