Skip to content

How Chrome Extensions Actually Work in 2026 (Manifest V3 Explained)

Chrome extensions5 min read

MV3 is four moving parts — manifest, service worker, content scripts, and UI — not a single script tag anymore.

Before you write a single line of extension code, you need the mental model. Most beginners jump into tutorials, copy-paste a manifest.json, and then get stuck the moment they need two parts of their extension to talk to each other.

This post is the architecture overview. No code — just the structure that makes everything else click.

MV2 Is Dead. MV3 Is the Only Path.

Manifest V2 extensions can no longer be uploaded to the Chrome Web Store. Existing MV2 extensions are being gradually disabled. If you're starting today, MV3 isn't optional — it's the only option.

The biggest change: persistent background pages are gone. MV3 replaces them with service workers that start on demand and terminate when idle. That means any data you store in a variable is gone the moment the worker goes idle — use chrome.storage instead (covered in How to Persist State in a Chrome Extension).

Other MV3 changes worth knowing:

  • chrome.scripting replaces inline chrome.tabs.executeScript
  • fetch replaces XMLHttpRequest in background contexts
  • Content Security Policy is stricter — no remote code execution
  • Declarative Net Request replaces the blocking webRequest API

The Four Components

Every Chrome extension is built from up to four pieces. Not all extensions use all four, but understanding each one is essential.

1. Popup

The small UI that appears when a user clicks your extension icon in the toolbar.

  • It's just an HTML page — you can use vanilla JS, React, Vue, whatever fits
  • It has no persistent state — every time the popup closes, it's destroyed and re-created on next open
  • It can call chrome.* APIs directly
  • It communicates with other components via message passing

Think of it as a tiny single-page app that lives in a bubble. If you need to do something after the popup closes, that work belongs in the service worker.

Ready to build one? Build Your First Chrome Extension with TypeScript from Scratch walks through a complete popup setup with Vite and CRXJS.

2. Background Service Worker

The "brain" of your extension. It runs in the background, listens for events, and coordinates everything.

  • Runs as a service worker, not a regular page — no DOM access, no window, no document
  • Terminates when idle (usually after ~30 seconds of inactivity) and restarts when an event fires
  • Handles events: alarms, messages from other components, web requests, tab changes
  • This is where you put logic that needs to survive after the popup closes

The termination behavior is the single most important thing to understand in MV3. If you store data in a global variable, it will disappear when the service worker terminates. Use chrome.storage instead — see How to Persist State in a Chrome Extension.

3. Content Script

Code that runs inside web pages the user visits.

  • Has full DOM access — it can read, modify, and inject elements into the page
  • Runs in an isolated world — it shares the DOM but not JavaScript variables with the page
  • Cannot call most chrome.* APIs directly — it must send messages to the service worker
  • Injected via the content_scripts field in manifest.json or dynamically via chrome.scripting.executeScript

Content scripts are how extensions interact with websites. An ad blocker's content script removes ad elements. A password manager's content script fills in form fields.

4. Options Page

A full-page settings UI for your extension.

  • Accessible via right-clicking the extension icon → "Options"
  • Just an HTML page like the popup, but it opens in a full tab
  • Good for configuration that doesn't fit in a small popup

Most simple extensions skip this. Once your extension has enough settings, add one.

How the Components Connect

Here's the part most tutorials skip. These four components run in separate execution contexts. They can't call each other's functions or share variables directly. They communicate through message passing.

┌─────────────┐     messages     ┌──────────────────┐
│   Popup     │ ◄──────────────► │  Service Worker   │
└─────────────┘                  └──────────────────┘
                                        ▲
                                        │ messages
                                        ▼
                                 ┌──────────────────┐
                                 │  Content Script   │
                                 │  (inside webpage) │
                                 └──────────────────┘

The service worker is the hub. The popup talks to it. Content scripts talk to it. The popup and content scripts don't typically talk to each other directly — they go through the service worker.

This message-passing architecture is where most beginners get stuck. If you want to get it right with TypeScript, Chrome Extension Message Passing with TypeScript: The Complete Guide covers typed messages, discriminated unions, and the async gotchas.

The Manifest Ties It All Together

The manifest.json is the configuration file that declares which components your extension uses:

manifest.json
├── action.default_popup → "popup.html"
├── background.service_worker → "background.js"
├── content_scripts → [{ matches, js }]
├── options_page → "options.html"
└── permissions → what APIs you can use

If a component isn't declared in the manifest, it doesn't exist. The manifest is also where you declare permissions — and getting permissions right is the difference between smooth Web Store approval and repeated rejections.

Which Components Do You Actually Need?

It depends on what your extension does:

Popup-only (simplest) — A calculator, color picker, or quick-reference tool. Just a popup, no background or content scripts.

Popup + Service Worker — The popup triggers actions, the service worker handles them. Good for extensions that need to do work after the popup closes, like setting alarms or making API calls.

Popup + Service Worker + Content Script — The most common architecture for extensions that interact with web pages. This is what ResistGate uses: the content script intercepts navigation, the service worker manages challenge state, and the popup shows settings.

Content Script only — Auto-running modifications to web pages. Ad blockers, readability tools, dark mode injectors.

Mental Model Summary

Think of a Chrome extension as a set of isolated programs that share a communication bus:

  • The popup is the face — small, temporary, user-triggered
  • The service worker is the brain — event-driven, stateless (by design), always listening
  • The content script is the hands — it touches web pages
  • The options page is the settings panel
  • Messages are how they all talk
  • The manifest is the wiring diagram

Once this clicks, the rest — tutorials, API docs, Stack Overflow answers — starts to make sense on its own.

Where to Go from Here

Now that you have the architecture, go build something:

  1. Hands-on startBuild Your First Chrome Extension with TypeScript from Scratch takes you from empty folder to working popup in 30 minutes.
  2. CommunicationChrome Extension Message Passing with TypeScript is the guide you'll need the moment your extension has more than one component.
  3. State managementHow to Persist State in a Chrome Extension covers the storage patterns that MV3's service worker model demands.

This is part of the Chrome Extensions from Zero to Product series, where I walk through everything I learned building ResistGate and Amethyst — from first popup to published product.

Notes from the build

More from the Chrome extension trenches

Architecture, storage, message passing, and real lessons from building ResistGate and Amethyst. No fluff.

By subscribing, you agree to receive Orlando's emails. No spam. Unsubscribe anytime.

How Chrome Extensions Actually Work in 2026 (Manifest V3 Explained) | Orlando Ascanio