Chrome Extension Development (Manifest V3)
This skill covers everything needed to build, debug, and publish Chrome extensions with MV3. It is organized as a routing document: read this file first to understand the architecture and decision points, then load the relevant reference file for implementation details.
Reference files
Read only the reference files relevant to the current task. Each file is self-contained.
| File | When to read |
|---|
| INLINECODE0 | Setting up or modifying manifest.json, configuring icons, versioning |
| INLINECODE1 |
Background logic, lifecycle, state persistence, alarms, events |
|
references/content-scripts.md | Injecting code into pages, isolated/main world, dynamic injection, SPA handling, orphaning |
|
references/messaging-rpc.md | Communication between any contexts, typed protocols, RPC layer, async handler patterns |
|
references/ui-surfaces.md | Popup, options page, side panel, context menus, commands, notifications, omnibox, devtools panel |
|
references/storage.md | chrome.storage (local/sync/session), quotas, reactive patterns, framework hooks |
|
references/network-csp.md | HTTP requests from content scripts, CSP bypass relay, declarativeNetRequest, offscreen docs, CORS |
|
references/permissions.md | Required/optional permissions, host permissions, activeTab, runtime request flow |
|
references/web-accessible-resources.md | Exposing extension files to web pages, security implications |
|
references/typescript-build.md | TypeScript setup, project structure, build tools comparison, bundling |
|
references/publishing.md | Chrome Web Store submission, review process, rejection reasons, updates, privacy policy |
|
references/execution-contexts.md | Communication flow diagrams, per-context capabilities/limits, choosing the right messaging method |
|
references/debugging-mistakes.md | DevTools for extensions, testing SW termination, common gotchas, error patterns |
Architecture overview
A Chrome extension has up to 5 execution contexts that communicate via message passing:
CODEBLOCK0
Communication flows (labeled channels)
CODEBLOCK1
For detailed flow diagrams (three-layer bridge, cross-extension, storage broadcast) and a per-context breakdown of permissions, limits, and workarounds: → Read INLINECODE13
Communication methods at a glance
| Method | Direction | Best for |
|---|
| INLINECODE14 | Any ext context → SW | One-shot request/response (90% of cases) |
| INLINECODE15 |
SW → content script (by tabId) | Pushing data to a specific tab |
|
chrome.runtime.connect (Port) | Bidirectional | Streaming, progress, SW ↔ popup |
|
window.postMessage | Between worlds on same page | Page JS ↔ content script bridge |
|
chrome.storage.onChanged | Broadcast to all contexts | Settings sync, no messaging needed |
→ Full matrix with limits and edge cases: references/execution-contexts.md → Implementation patterns, typed protocols, RPC layer: INLINECODE20
Key architectural rules
- 1. Service worker is ephemeral. It terminates after 30s of inactivity. All state must be persisted to chrome.storage. All event listeners must be registered synchronously at the top level. Never use setTimeout/setInterval for anything beyond a few seconds. → Read INLINECODE21
- 2. Content scripts run in the page's origin. Network requests from content scripts are subject to the page's CSP and CORS. To bypass, relay through the service worker. → Read INLINECODE22
- 3. Messaging is the backbone. Every cross-context interaction uses chrome.runtime messaging. The #1 bug: forgetting to
return true from async message listeners. → Read INLINECODE24
- 4. Permissions determine CWS review speed. Broad host_permissions trigger manual review (weeks). activeTab + optional permissions = fast automated review. → Read INLINECODE25
- 5. Popup is destroyed on blur. Side panel persists. Choose based on interaction duration. → Read INLINECODE26
Decision tree: which context handles what?
"I need to run code when the user visits a page"
→ Content script. Static (manifest) for known URL patterns, dynamic (chrome.scripting) for user-triggered injection. Default to isolated world unless you need page JS access. → Read INLINECODE27
"I need to make an HTTP request to my API"
- - From popup/options/side panel: direct fetch() works (extension origin, no CSP issues)
- From content script on a page with restrictive CSP: relay through service worker
- From service worker: direct fetch() works (requires host_permissions for the target domain) → Read INLINECODE28
"I need to store user settings"
- - Settings that sync across devices: chrome.storage.sync (100KB limit)
- Large data or caches: chrome.storage.local (10MB, or unlimited with permission)
- Ephemeral state surviving SW restarts: chrome.storage.session → Read INLINECODE29
"I need to modify HTTP headers or block requests"
→ declarativeNetRequest (NOT webRequest, which lost blocking in MV3) → Read INLINECODE30
"I need the page's JavaScript to talk to my extension"
→ Three-layer bridge: page (window.postMessage) → content script → service worker → Read INLINECODE31
"I need to understand what each context can and cannot do"
→ Read references/execution-contexts.md — per-context cards listing chrome.\* access, DOM, network, storage, lifetime, hard limits, and practical workarounds.
"I need periodic background tasks"
→ chrome.alarms (minimum 30s interval). NOT setTimeout. → Read INLINECODE33
"I need DOM APIs in the background" (DOMParser, Canvas, Audio)
→ Offscreen document. One per extension, only chrome.runtime available. → Read INLINECODE34
"I need to authenticate with OAuth"
→ chrome.identity.launchWebAuthFlow() or chrome.identity.getAuthToken() (Google only) → Read references/service-worker.md (identity section)
Workflow: new extension from scratch
- 1. Define the manifest with minimum permissions. Start with
activeTab + scripting. → Read INLINECODE38
- 2. Set up TypeScript and build tooling (or use CRXJS for Vite-based dev). → Read INLINECODE39
- 3. Implement the service worker with all event listeners at the top level. → Read INLINECODE40
- 4. Add content scripts if you need page interaction. → Read INLINECODE41
- 5. Build UI surfaces (popup, options, side panel) as needed. → Read INLINECODE42
- 6. Wire up messaging between all contexts. → Read INLINECODE43
- 7. Test with DevTools, specifically test service worker termination. → Read INLINECODE44
- 8. Publish to Chrome Web Store. → Read INLINECODE45
Workflow: adding a feature to an existing extension
- 1. Identify which context the feature belongs to (see decision tree above).
- Read the relevant reference file(s) for that context.
- Check if new permissions are needed. Prefer optional_permissions for new capabilities. → Read INLINECODE46
- Update the manifest if adding new content scripts, UI surfaces, or permissions.
- Handle extension updates gracefully (content script orphaning). → Read
references/content-scripts.md (orphaning section)
Minimal manifest.json template
CODEBLOCK2
→ For the full manifest reference with all fields: INLINECODE48
Code patterns quick reference
Async message handler (the safe pattern)
CODEBLOCK3
CSP bypass relay (content script → service worker → API)
CODEBLOCK4
Persist state across SW restarts
CODEBLOCK5
Orphaned content script detection
CODEBLOCK6
What NOT to do
- - Do NOT use
eval(), new Function(), or load remote scripts. MV3 forbids it. - Do NOT use
setTimeout/setInterval for anything > 5s in service workers. - Do NOT register event listeners inside callbacks or async functions.
- Do NOT use
<all_urls> host permission unless absolutely necessary. - Do NOT rely on DevTools keeping the service worker alive during testing.
- Do NOT forget
return true in async message listeners. - Do NOT use
localStorage or sessionStorage in service workers (they don't exist there). - Do NOT assume content scripts survive extension updates.
- Do NOT use
webRequest blocking (removed in MV3). Use declarativeNetRequest. - Do NOT use
chrome.extension.getBackgroundPage() (removed in MV3).
Chrome 扩展开发 (Manifest V3)
本技能涵盖使用 MV3 构建、调试和发布 Chrome 扩展所需的一切内容。它被组织为一个路由文档:首先阅读此文件以了解架构和决策点,然后加载相关的参考文件以获取实现细节。
参考文件
仅读取与当前任务相关的参考文件。每个文件都是独立的。
| 文件 | 何时阅读 |
|---|
| references/manifest-v3.md | 设置或修改 manifest.json、配置图标、版本管理 |
| references/service-worker.md |
后台逻辑、生命周期、状态持久化、定时器、事件 |
| references/content-scripts.md | 向页面注入代码、隔离/主世界、动态注入、SPA 处理、孤立脚本 |
| references/messaging-rpc.md | 任意上下文间的通信、类型化协议、RPC 层、异步处理器模式 |
| references/ui-surfaces.md | 弹出窗口、选项页面、侧边栏、上下文菜单、命令、通知、多功能框、开发者工具面板 |
| references/storage.md | chrome.storage(本地/同步/会话)、配额限制、响应式模式、框架钩子 |
| references/network-csp.md | 内容脚本的 HTTP 请求、CSP 绕过中继、declarativeNetRequest、离屏文档、CORS |
| references/permissions.md | 必需/可选权限、主机权限、activeTab、运行时请求流程 |
| references/web-accessible-resources.md | 向网页暴露扩展文件、安全影响 |
| references/typescript-build.md | TypeScript 设置、项目结构、构建工具对比、打包 |
| references/publishing.md | Chrome 网上应用商店提交、审核流程、拒绝原因、更新、隐私政策 |
| references/execution-contexts.md | 通信流程图、各上下文能力/限制、选择正确的消息传递方式 |
| references/debugging-mistakes.md | 扩展的开发者工具、测试 SW 终止、常见陷阱、错误模式 |
架构概览
一个 Chrome 扩展最多有 5 个执行上下文,通过消息传递进行通信:
┌──────────────────────────────────────────────────────────┐
│ 扩展进程 │
│ ┌─────────────────┐ ┌───────┐ ┌─────────┐ ┌──────┐ │
│ │ 服务工作者 │ │ 弹出 │ │ 选项 │ │ 侧边 │ │
│ │ (后台) │ │ 窗口 │ │ 页面 │ │ 栏 │ │
│ │ - 无 DOM │ │ 完整 │ │ 完整 │ │ 完整 │ │
│ │ - 临时性 │ │ DOM │ │ DOM │ │ DOM │ │
│ │ - 所有 chrome.* │ │ 所有 │ │ 所有 │ │ 所有 │ │
│ │ API │ │ API │ │ API │ │ API │ │
│ └────────┬─────────┘ └───┬───┘ └────┬────┘ └──┬───┘ │
│ │ chrome.runtime.sendMessage / connect │ │
└───────────┼────────────────┼───────────┼──────────┼──────┘
│ │ │ │
chrome.tabs.sendMessage │ │ │
│ │ │ │
┌───────────┼────────────────┼───────────┼──────────┼──────┐
│ 网页 ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 内容脚本 │ │ 主世界脚本 │ │
│ │ (隔离世界) │◄──►│ (页面上下文) │ │
│ │ - 共享 DOM │ │ - 共享 DOM │ │
│ │ - 自有 JS 作用域 │ │ - 页面 JS 作用域 │ │
│ │ - chrome.runtime │ │ - 无 chrome.* API│ │
│ │ - chrome.storage │ │ - 完整页面访问 │ │
│ │ - 受 CSP 限制 │ │ - 受 CSP 限制 │ │
│ │ (仅网络) │ │ (完全) │ │
│ └──────────────────┘ └──────────────────┘ │
│ ▲ window.postMessage │
│ │ (通过共享 DOM) │
└──────────────────────────────────────────────────────────┘
通信流程(标记通道)
┌───────────────────────────────────────────────────────────────────────────┐
│ 扩展进程 │
│ │
│ ┌─────────────────┐ chrome.runtime ┌───────┐ ┌─────────┐ ┌──────┐ │
│ │ 服务工作者 │◄─.sendMessage()──│ 弹出 │ │ 选项 │ │ 侧边 │ │
│ │ (后台) │◄─.connect()──────│ 窗口 │ │ 页面 │ │ 栏 │ │
│ │ │ └───────┘ └─────────┘ └──────┘ │
│ │ - 无 DOM │ ┌────────────────────────────────────────────┐ │
│ │ - 临时性 30s │ │ SW 无法主动推送消息到这些页面。 │ │
│ │ - 所有 chrome.* │ │ 使用:端口 (.connect) 或 storage.onChanged │ │
│ └────────┬─────────┘ └────────────────────────────────────────────┘ │
│ │ │
│ chrome.storage.onChanged ◄── 在所有上下文中同时触发 │
│ │
└───────────┼──────────────────────────────────────────────────────────────┘
│ chrome.tabs.sendMessage(tabId, ...) [SW 必须知道 tabId]
│
┌───────────┼──────────────────────────────────────────────────────────────┐
│ 网页 ▼ │
│ ┌──────────────────┐ window.postMessage ┌──────────────────┐ │
│ │ 内容脚本 │◄───────────────────►│ 主世界脚本 │ │
│ │ (隔离世界) │ 自定义 DOM 事件 │ (页面上下文) │ │
│ │ │ │ │ │
│ │ chrome.runtime ───┼── 与 SW 双向通信 │ 无 chrome.* API │ │
│ │ chrome.storage │ │ 完整页面 JS │ │
│ │ 共享 DOM │ │ 共享 DOM │ │
│ │ 页面 CSP (网络) │ │ 页面 CSP (完全) │ │
│ └──────────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
有关详细流程图(三层桥接、跨扩展、存储广播)以及各上下文的权限、限制和解决方案的详细说明:→ 阅读 references/execution-contexts.md
通信方法一览
| 方法 | 方向 | 最佳用途 |
|---|
| chrome.runtime.sendMessage | 任意扩展上下文 → SW | 一次性请求/响应(90% 的情况) |
| chrome.tabs.sendMessage |
SW → 内容脚本(按 tabId) | 向特定标签页推送数据 |
| chrome.runtime.connect (端口) | 双向 | 流式传输、进度、SW ↔ 弹出窗口 |
| window.postMessage | 同一页面上的世界之间 | 页面 JS ↔ 内容脚本桥接 |
| chrome.storage.onChanged | 广播到所有上下文 | 设置同步,无需消息传递 |
→ 包含限制和边界情况的完整矩阵:references/execution-contexts.md → 实现模式、类型化协议、RPC 层:references/messaging-rpc.md
关键架构规则
- 1. 服务工作者是临时性的。 它在 30 秒不活动后终止。所有状态必须持久化到 chrome.storage。所有事件监听器必须在顶层同步注册。永远不要将 setTimeout/setInterval 用于超过几秒的操作。→ 阅读 references/service-worker.md
- 2. 内容脚本在页面的源中运行。 内容脚本的网络请求受页面的 CSP 和 CORS 限制。要绕过,通过服务工作者中继。→ 阅读 references/network-csp.md
- 3. 消息传递是核心。 每个跨