| name | mv3-architecture |
| description | Plan Manifest V3 Chromium extensions around the right execution contexts, permissions, storage, and background model. Use when designing a new extension, migrating older background-page code, choosing between service workers and content scripts, or reviewing an extension manifest and folder layout. |
Manifest V3 Architecture
Overview
Start by deciding which extension context owns each responsibility. In a healthy
MV3 design, the service worker orchestrates events and browser APIs, content
scripts interact with page DOM, extension pages own user-facing UI, and
offscreen documents only exist for DOM-only work that cannot run in the worker.
Keep the manifest and permission budget narrow enough that every entry is easy
to justify.
Read these references as needed:
references/manifest-composition.md for how to shape manifest.json
references/service-worker-lifecycle.md for background orchestration in MV3
references/storage-and-permissions.md for state placement and permission
minimization
Core workflow
1) Define the product surface before the code layout
- Identify the user-triggered entry points first: popup, side panel, command,
context menu, page interaction, or a passive page enhancement.
- Prefer one primary invocation surface and one supporting surface. More than
that usually means the extension boundary is still unclear.
2) Assign runtime ownership explicitly
- Put browser event coordination, alarms, tab bookkeeping, and API calls in the
service worker.
- Put page reads and DOM mutations in content scripts.
- Put settings and long-lived UI state in extension pages such as popup, side
panel, or options.
- Reach for an offscreen document only when the job requires DOM access that is
unavailable in the service worker.
3) Draft the manifest from least privilege upward
- Start with
manifest_version, name, version, action, and
background.service_worker.
- Add
permissions, host_permissions, optional_permissions, and
optional_host_permissions only after mapping them back to a concrete feature.
- Decide whether
content_scripts should be static, dynamically registered, or
replaced with chrome.scripting.
- Keep
web_accessible_resources narrow and explicit.
4) Decide where state lives
- Use
chrome.storage.local for durable extension state on one machine.
- Use
chrome.storage.sync only for compact user preferences that benefit from
cross-browser sync.
- Use
chrome.storage.session or in-memory worker state for ephemeral runtime
coordination, but assume the worker can restart at any time.
- Use alarms and persisted checkpoints instead of assuming background code stays
resident.
5) Validate lifecycle assumptions
- Register worker listeners at module top level, not lazily after async setup.
- Assume the worker is cold-started frequently and can be suspended between
events.
- Keep startup paths idempotent so install/update/reload flows do not duplicate
registrations or corrupt storage.
Strong defaults
- Prefer one service worker entry module and fan out to focused helpers.
- Keep content scripts page-focused and thin.
- Treat permissions as product API surface, not implementation detail.
- Prefer optional host access or
activeTab where the feature allows it.
- Add
minimum_chrome_version only when a required API genuinely needs it.
Anti-patterns
- Treating the service worker like a persistent background page.
- Using content scripts as the main source of truth for app state.
- Adding broad host permissions because they are easier than runtime requests.
- Stuffing every capability into one popup instead of choosing the right
surface.
- Using offscreen documents as a generic workaround instead of a narrow bridge.
Notes
- Primary references:
https://developer.chrome.com/docs/extensions/mv3/manifest
https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers
https://developer.chrome.com/docs/extensions/reference/scripting/
- Chromium extension APIs evolve. When an API choice or manifest field might
have changed, consult current Chrome docs before implementation.