Chrome Extension Development
Chrome Extensions are web applications that run in the browser context and have access to tabs, requests, storage, bookmarks, and page DOM. Extension architecture differs from a regular website: multiple isolated execution contexts, own message system, and separate sandbox.
Manifest V3
Google requires Manifest V3 for all new extensions since 2023. Key differences from MV2: Service Worker instead of Background Page, declarative declarativeNetRequest instead of dynamic webRequest.
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"description": "Extension description",
"permissions": ["storage", "tabs", "activeTab", "scripting"],
"host_permissions": ["https://*.example.com/*"],
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["https://*.target-site.com/*"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_idle"
}
],
"options_page": "options.html",
"icons": { "48": "icons/icon48.png", "128": "icons/icon128.png" }
}
Execution contexts
| Context | DOM access | Chrome API access | Lifetime |
|---|---|---|---|
| Service Worker | No | Full | By event |
| Popup | Own window only | Full | While open |
| Content Script | Tab page | Limited | While tab alive |
| Options Page | Own window only | Full | While open |
Message passing
Contexts don't share memory. Communication — via chrome.runtime.sendMessage / chrome.runtime.onMessage:
// Content script -> Background
chrome.runtime.sendMessage(
{ type: 'FETCH_DATA', url: window.location.href },
(response) => console.log('Got:', response)
);
// Background (service worker)
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'FETCH_DATA') {
fetch(message.url)
.then(r => r.json())
.then(data => sendResponse({ data }));
return true; // signal that response is async
}
});
For persistent connection — chrome.runtime.connect / Port:
// Popup
const port = chrome.runtime.connect({ name: 'popup' });
port.postMessage({ type: 'START_STREAM' });
port.onMessage.addListener(msg => updateUI(msg));
// Background
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'popup') {
port.onMessage.addListener(msg => {
// handling
});
}
});
Tab interaction
// Get active tab
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// Execute script on tab (MV3)
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => document.title,
});
// Inject CSS
await chrome.scripting.insertCSS({
target: { tabId: tab.id },
css: 'body { background: #fff !important; }',
});
Storage
chrome.storage.local — syncs between extension contexts, not cleared on reload:
// Write
await chrome.storage.local.set({ settings: { theme: 'dark', enabled: true } });
// Read
const { settings } = await chrome.storage.local.get('settings');
// Subscribe to changes
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.settings) {
applySettings(changes.settings.newValue);
}
});
chrome.storage.sync — syncs across user's devices via Google account (limit 100 KB, 512 bytes per key).
Declarative request blocking (MV3)
// rules.json
[{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "||ads.example.com^",
"resourceTypes": ["script", "image"]
}
}]
// manifest.json
"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}]
}
Timeline
Basic Chrome Extension with popup and content script: 3–5 days. Complex extension with background sync, custom API integration, and App Store submission: 2–3 weeks.







