EB
Development Tutorial Architecture

Chrome Extension Architecture: The Complete Developer's Guide for 2026

ET

ExtensionBooster Team

6 min read
Code editor with JavaScript

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

FieldPurposeRequired
manifest_versionMust be 3 for new extensionsYes
nameDisplay name (max 45 chars)Yes
versionSemantic version stringYes
permissionsAPI access requestsNo
host_permissionsWebsite access requestsNo

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

  1. Event-driven execution - Only runs when triggered
  2. No DOM access - Cannot use document or window
  3. Terminates when idle - Chrome kills inactive workers
  4. 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 AreaCapacitySyncUse Case
local10MBNoLarge data, cache
sync100KBYesUser preferences
session10MBNoTemporary data (cleared on browser close)
managedUnlimitedYesEnterprise 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 activeTab instead 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

  1. Navigate to chrome://extensions
  2. Enable “Developer mode”
  3. Click “Load unpacked”
  4. 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:


What’s Next?

Now that you understand the architecture:

  1. Start building - Create a simple extension using these concepts
  2. Migrate to MV3 - Use our MV2 to MV3 Converter if updating an existing extension
  3. 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.

Share this article

Related Articles