Chrome Extension Architecture: The Complete Developer's Guide for 2026
ExtensionBooster Team
Understanding Chrome Extension Architecture
Chrome extensions are powerful tools that enhance browser functionality. Unlike traditional web apps, extensions operate across multiple contexts with distinct capabilities and limitations. This guide breaks down every core component you need to master.
The Manifest File: Your Extension’s Blueprint
Every extension starts with manifest.json - the configuration file that tells Chrome everything about your extension.
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"description": "What your extension does",
"permissions": ["storage", "activeTab"],
"host_permissions": ["https://*.example.com/*"],
"background": {
"service_worker": "service-worker.js"
},
"content_scripts": [{
"matches": ["https://*.example.com/*"],
"js": ["content.js"]
}],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
Key Manifest Fields
| Field | Purpose | Required |
|---|---|---|
manifest_version | Must be 3 for new extensions | Yes |
name | Display name (max 45 chars) | Yes |
version | Semantic version string | Yes |
permissions | API access requests | No |
host_permissions | Website access requests | No |
Service Workers: The Background Brain
Service workers replaced persistent background pages in Manifest V3. They handle events, coordinate components, and manage extension-wide logic.
Key Characteristics
- Event-driven execution - Only runs when triggered
- No DOM access - Cannot use
documentorwindow - Terminates when idle - Chrome kills inactive workers
- Single instance - One worker per extension
Common Service Worker Patterns
// Listen for installation
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
// First-time setup
chrome.storage.local.set({ settings: defaultSettings });
}
});
// Handle messages from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'getData') {
fetchData().then(sendResponse);
return true; // Keep channel open for async response
}
});
// Schedule periodic tasks
chrome.alarms.create('dailySync', { periodInMinutes: 1440 });
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'dailySync') {
performDailySync();
}
});
State Persistence Challenge
Since service workers terminate, you cannot rely on global variables:
// DON'T DO THIS - data lost when worker terminates
let userData = {};
// DO THIS - persist in storage
chrome.storage.session.set({ userData: data });
chrome.storage.session.get(['userData'], (result) => {
const userData = result.userData || {};
});
Content Scripts: Interacting with Web Pages
Content scripts run in the context of web pages, allowing you to read and modify page content.
Injection Methods
1. Declarative (manifest.json):
{
"content_scripts": [{
"matches": ["https://*.github.com/*"],
"js": ["content.js"],
"css": ["styles.css"],
"run_at": "document_idle"
}]
}
2. Programmatic (from service worker):
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content.js']
});
Isolated World Model
Content scripts operate in an isolated JavaScript environment:
- Can access: DOM, window object (limited), extension APIs
- Cannot access: Page’s JavaScript variables, functions, or objects
- Shared with page: The DOM structure only
// Content script can read DOM
const title = document.querySelector('h1').textContent;
// But cannot access page's JS
// window.pageVariable is undefined
Messaging: Connecting Components
Extensions coordinate through message passing between service workers, content scripts, and popups.
One-Time Messages
// Send from content script
chrome.runtime.sendMessage({ action: 'save', data: pageData });
// Receive in service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'save') {
saveData(message.data);
sendResponse({ success: true });
}
return true; // Required for async sendResponse
});
Long-Lived Connections
For ongoing communication, use ports:
// Content script opens connection
const port = chrome.runtime.connect({ name: 'dataStream' });
port.postMessage({ type: 'subscribe' });
port.onMessage.addListener((message) => {
handleUpdate(message);
});
// Service worker handles connection
chrome.runtime.onConnect.addListener((port) => {
if (port.name === 'dataStream') {
port.onMessage.addListener((message) => {
if (message.type === 'subscribe') {
// Start sending updates
}
});
}
});
Storage API: Persisting Data
Chrome provides four storage areas with different characteristics:
| Storage Area | Capacity | Sync | Use Case |
|---|---|---|---|
local | 10MB | No | Large data, cache |
sync | 100KB | Yes | User preferences |
session | 10MB | No | Temporary data (cleared on browser close) |
managed | Unlimited | Yes | Enterprise policies |
Storage Patterns
// Save data
await chrome.storage.local.set({
user: { name: 'John', lastVisit: Date.now() }
});
// Retrieve data
const { user } = await chrome.storage.local.get('user');
// Listen for changes
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.user) {
console.log('User updated:', changes.user.newValue);
}
});
Permissions: Security and Trust
Permissions define what your extension can access. Request only what you need.
Permission Types
1. API Permissions - Access to Chrome APIs:
{
"permissions": ["storage", "alarms", "notifications"]
}
2. Host Permissions - Access to websites:
{
"host_permissions": [
"https://*.google.com/*",
"https://api.example.com/*"
]
}
3. Optional Permissions - Request at runtime:
chrome.permissions.request({
permissions: ['tabs'],
origins: ['https://*.github.com/*']
}, (granted) => {
if (granted) {
// Permission granted
}
});
Best Practices
- Use
activeTabinstead of broad host permissions when possible - Request optional permissions only when the user needs that feature
- Explain why you need each permission in your store listing
Offscreen Documents: DOM Without UI
When service workers need DOM functionality, use the Offscreen API:
// Create offscreen document
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DOM_PARSER'],
justification: 'Parse HTML content'
});
// Use DOM in offscreen.html
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
Development Workflow
Loading Your Extension
- Navigate to
chrome://extensions - Enable “Developer mode”
- Click “Load unpacked”
- Select your extension folder
Debugging Tips
- Service worker: Inspect via “service worker” link on extensions page
- Content scripts: Open DevTools on the target page, check Sources panel
- Popup: Right-click popup, select “Inspect”
Hot Reloading
During development, reload your extension after changes:
- Click the reload icon on
chrome://extensions - Or use a development extension for auto-reload
What’s Next?
Now that you understand the architecture:
- Start building - Create a simple extension using these concepts
- Migrate to MV3 - Use our MV2 to MV3 Converter if updating an existing extension
- Optimize your listing - Use our Screenshot Makeup tool for professional store assets
Ready to publish? Create your developer showcase on ExtensionBooster and get discovered by users looking for extensions like yours.