build-chrome-extension
Skill13 starsChanged 13 days agoMITView on GitHub
Use if building or debugging a Chrome MV3 extension — manifest v3, service_worker, content_scripts.
We split this skill into the rules it gives an agent
26 rules. About 0 are common, 15 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈15
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 25
Do NOT use this skill when:
- Distinctive: line 96
Ask: *"Which extension surfaces exist, which Chrome APIs are required, which permissions can be deferred to runtime?"*
- Distinctive: line 106
Ask: *"Greenfield, existing framework build, or hand-written manifest?"*
- Distinctive: line 117
Ask: *"Which context owns the state, which owns the DOM, which messages cross the boundary?"*
- Distinctive: line 119
Read the matching reference before writing code:
- Distinctive: line 134
Ask: *"Can Chrome load this exact folder, and do all manifest paths exist there?"*
- Distinctive: line 136
Run from the skill directory against your build folder:
- Very specific: line 152
Read scripts/check-mv3-manifest.md and scripts/preflight-extension.md before modifying either script.
- Distinctive: line 156
Ask: *"What failure only appears once Chrome loads the extension?"*
- Distinctive: line 161
Test content-script injection on allowed and disallowed URLs.
- Very specific: line 162
Verify the runtime permission request UX (chrome.permissions.request) before the host call.
- Very specific: line 164
Read references/testing/testing-guide.md for extension-specific tests and references/testing/debugging.md for service-worker, popup, …
- Distinctive: line 168
Ask: *"Could a reviewer state the single purpose, each permission's need, the data use, and the remote-code posture in one sentence each?"*
- Distinctive: line 171
Run both bundled scripts against that production build.
- Very specific: line 173
Create the zip from inside the built-output folder (so manifest.json sits at the zip root).
- Very specific: line 175
Read references/publishing/web-store.md before submission.
- Distinctive: line 204
Never generate or preserve Manifest V2 for new work.
- Very specific: line 205
Never load src/ in Chrome unless the framework explicitly emits loadable code there.
- Distinctive: line 206
Never hold durable state in module-scope globals inside the service worker.
- Very specific: line 207
Never rely on setTimeout/setInterval to keep the service worker alive.
- Very specific: line 208
Never use blocking webRequest for normal MV3 network modification.
- Very specific: line 209
Never request <all_urls> without a feature-level justification and a documented narrower alternative.
- Very specific: line 210
Never expose arbitrary background fetch through unauthenticated content-script messages.
- Distinctive: line 211
Never ship remote executable code or undeclared remote-code behavior.
- Very specific: line 212
Always validate built-output paths against manifest.json before claiming done.
- Distinctive: line 213
Always make each permission explainable in one sentence.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Build Chrome Extension
Build, debug, package, and ship Chrome Manifest V3 extensions. Optimize for restart-safe service workers, isolated content scripts, least-privilege permissions, and Web Store review readiness.
When To Use
Trigger when the request matches any of:
- creating a new Chrome MV3 extension or scaffolding from a framework (WXT, Plasmo, CRXJS, Vite)
- editing or generating
manifest.jsonwithmanifest_version: 3,service_worker,content_scripts,host_permissions,action, orside_panelfields - implementing or debugging
chrome.runtime,chrome.storage,chrome.alarms,chrome.scripting,chrome.tabs,chrome.declarativeNetRequest,chrome.sidePanel, orchrome.offscreenAPIs - connecting a Chrome extension to desktop apps or local daemons (Raycast Companion, local WebSocket servers, Native Messaging hosts)
- fixing MV3 service-worker lifecycle issues: idle termination, top-level listener registration, async
onMessagereturns,chrome.alarmsvssetInterval - bridging content scripts between ISOLATED and MAIN worlds, or routing fetches through the service worker for cross-origin host permissions
- migrating an MV2 extension to MV3 (background page → service worker, blocking
webRequest→declarativeNetRequest, browser_action → action) - validating a built
dist/or.output/chrome-mv3/folder, packaging the Web Store zip, writing privacy/permission justifications, or preparing review notes - choosing or comparing Chrome MV3 frameworks for a new project
Do NOT use this skill when:
- the target is Firefox, Safari, or a cross-browser polyfill (
browser.*namespace,web-exttooling, Safari App Extensions) - the request is generic browser automation outside an extension context — out of scope
- publishing a regular npm package or library — route to
publish-npm-package - building a different platform's extension surface (Raycast script command, VS Code extension, Edge enterprise-policy package) — route to that platform's skill
Pinned Defaults — Apply Before Generating Code
| Key | Default |
| Manifest target | manifest_version: 3 only; never generate MV2 |
| Greenfield framework | WXT unless the repo already chose another MV3 tool |
| Persistent state | chrome.storage.local; chrome.storage.session for ephemeral restart-safe state |
| Background model | Event-driven service worker, listeners registered synchronously at top level |
| Periodic work | chrome.alarms — never setTimeout/setInterval for background scheduling |
| Network modification | chrome.declarativeNetRequest — never blocking webRequest |
| Permissions | Least privilege; prefer optional_permissions and optional_host_permissions granted via chrome.permissions.request |
| Loaded folder | Built output only: WXT .output/chrome-mv3-dev/ or .output/chrome-mv3/; Plasmo build/chrome-mv3-*; CRXJS/Vite dist/ |
| Desktop IPC transport | Local WebSocket for unprivileged daemons; Native Messaging for installer-backed system tools |
| Package preflight | Run scripts/check-mv3-manifest.sh and scripts/preflight-extension.sh against the production build before zipping |
MV3 Footguns — Keep In Working Memory
These are the failures that recur across every MV3 build. Internalize before writing code:
- Service workers idle out (~30s default). Global module-scope state disappears between events. Persist before each await and rehydrate at event entry.
- Register
chrome.runtime.onMessage,onInstalled,onStartup, alarm, and tab listeners synchronously at top level. Late-registered listeners miss wake-up events. setTimeout/setIntervalcannot keep a service worker alive and will not fire reliably across idle cycles. Usechrome.alarms.createwithperiodInMinutes >= 0.5.- In Chrome 148+,
chrome.runtime.onMessagelisteners natively support returning aPromiseor usingasync. For backward compatibility (<148 and cross-browser), async handlers must synchronouslyreturn trueand invokesendResponsewhen complete; otherwise the message channel closes immediately and the sender receivesundefined(or rejects with "The message port closed before a response was received"). - Local WebSocket connections in service workers terminate after ~30 seconds of inactivity. You must maintain an active heartbeat ping (<30s interval, e.g. 24s) to reset Chrome 116+ idle timers. Idle open sockets do not keep the worker alive.
- Local WebSockets listening on 127.0.0.1 are vulnerable to Cross-Site WebSocket Hijacking (CSWSH) from malicious web pages. Daemons MUST validate the
Originheader againstchrome-extension://<id>and require an authentication token during HTTP Upgrade. - Native Messaging hosts must NEVER write debug logs or unformatted output to standard output (
stdout). Any non-framed stdout output corrupts the 32-bit length prefix and crashes the connection. Send logs tostderr. - Messages sent from a Native Messaging host to Chrome are strictly capped at 1 MB. Exceeding 1 MB terminates the process immediately.
- Content scripts run in an isolated world by default. The page's JS, frameworks, and
window.*globals are invisible. Useworld: "MAIN"only for page-JS access, then bridge withpostMessageplus a same-origin token. - Extension-origin
fetchrequires matchinghost_permissions. Content-scriptfetchis bound by the page's origin and CORS rules — route privileged requests through the service worker viachrome.runtime.sendMessage. - MV3 CSP forbids inline
<script>,eval(),new Function(), and remote executable code in extension pages. Bundle everything; no CDN-loaded scripts. (Sandboxed pages inmanifest.sandboxare the sole exception whereunsafe-evalis permitted). - Hand-written
manifest.jsonpaths must point at built artifacts (e.g.background.js,content.js,popup.html), neversrc/*.tsor unbuilt source. - Requesting
<all_urls>or broad host permissions at install time triggers Web Store review friction. PreferactiveTabplus optional host grants.
Decision Rules
| Decision | Default | Escalate when |
| Background state | chrome.storage.session/local | Transactions, large indexes, binary blobs → IndexedDB |
| Periodic work | chrome.alarms | Real-time stream → design reconnect + queue |
| Network modification | declarativeNetRequest | Read-only observation → non-blocking webRequest |
| Page data fetch | Service worker fetch with host permissions | Page origin is sufficient → content-script fetch |
| Page JS access | world: "MAIN" bridge with payload validation | DOM-only access → stay in ISOLATED world |
| UI state persistence | chrome.storage.session | Must survive browser restart → local or sync |
| Host access | activeTab or optional host grants | Extension is non-functional without install-time host access |
| Side panel vs popup | Side panel for persistent companion UI | Quick action or short form → popup |
| Offscreen document | DOM/canvas/clipboard/audio/Worker from service worker | Popup/options/content script can own it → skip |
| Desktop communication | Local WebSocket (ws://127.0.0.1:<port>) with CSWSH protection | Deep OS integration / enterprise distribution / zero local port exposure → chrome.runtime.connectNative |
Routing Boundary
| Request shape | Action |
| "Build a Chrome extension" | Use this skill; target MV3 |
| "Build for Chrome and Firefox" | Keep Chrome MV3 here; cross-browser layer is out of scope |
| "Automate a website in a browser" | Out of scope (general browser automation) |
| "Write Playwright tests for an extension" | Extension launch/load notes here; broader Playwright authoring is out of scope for this pack |
| "Publish to npm" | Route to publish-npm-package |
| "Submit to Chrome Web Store" | Use this skill; read references/publishing/web-store.md |
| "Deploy via enterprise policy" | Out of scope unless a dedicated enterprise skill exists |
Workflow
1. Scope The Extension
Ask: "Which extension surfaces exist, which Chrome APIs are required, which permissions can be deferred to runtime?"
- List required surfaces: popup, options, side panel, content script, service worker, offscreen document, devtools.
- List required Chrome APIs and resulting permissions.
- Split install-time vs
optional_permissions/optional_host_permissions. - Choose the framework — see
references/frameworks/comparison.md. - Record the built-output folder Chrome will load.
2. Scaffold Or Adapt
Ask: "Greenfield, existing framework build, or hand-written manifest?"
- Greenfield default: WXT.
- Existing Vite app becoming an extension: CRXJS.
- Custom build pipeline / explicit control: vanilla + Vite.
- Existing repo: follow its framework and output conventions.
Chrome must load the built output folder, never src/ or unbundled TypeScript.
3. Implement Extension Contexts
Ask: "Which context owns the state, which owns the DOM, which messages cross the boundary?"
Read the matching reference before writing code:
- Service worker lifecycle, persistence, alarms, offscreen →
references/patterns/service-worker.md - Content scripts, world isolation, idempotency, cross-origin routing →
references/patterns/content-scripts.md - Popup, options, side panel, devtools, new-tab surfaces →
references/patterns/ui-surfaces.md - One-time messages, ports, external/native messaging, page bridges →
references/apis/messaging.md - Desktop communication, Raycast Companion, Native Messaging →
references/patterns/desktop-ipc.md chrome.storageareas, quotas, typed wrappers, migrations →references/apis/storage.md- Tabs, scripting, alarms, DNR, side panel, offscreen, runtime →
references/apis/core-apis.md - Manifest, permission, and MV2 migration decisions are covered by the pinned defaults and guardrails in this
SKILL.md.
Validate every cross-boundary payload: messages, storage reads, external API responses, MAIN-world bridge data.
4. Validate Built Output
Ask: "Can Chrome load this exact folder, and do all manifest paths exist there?"
Run from the skill directory against your build folder:
scripts/check-mv3-manifest.sh dist
scripts/preflight-extension.sh distAdjust the path per framework:
| Framework | Dev output | Production output |
| WXT | .output/chrome-mv3-dev/ | .output/chrome-mv3/ |
| Plasmo | build/chrome-mv3-dev/ | build/chrome-mv3-prod/ |
| CRXJS | dist/ | dist/ |
| Vanilla Vite | dist/ | dist/ |
Read scripts/check-mv3-manifest.md and scripts/preflight-extension.md before modifying either script.
5. Test Extension Behavior
Ask: "What failure only appears once Chrome loads the extension?"
- Unit-test pure logic and message/storage helpers; mock
chrome.*only at boundaries. - Manually load the built output via
chrome://extensions→ "Load unpacked". - Inspect the service worker from the extensions page; trigger an idle/wake cycle.
- Test content-script injection on allowed and disallowed URLs.
- Verify the runtime permission request UX (
chrome.permissions.request) before the host call.
Read references/testing/testing-guide.md for extension-specific tests and references/testing/debugging.md for service-worker, popup, content-script, permission, and storage debugging.
6. Package For Web Store Review
Ask: "Could a reviewer state the single purpose, each permission's need, the data use, and the remote-code posture in one sentence each?"
- Build production output.
- Run both bundled scripts against that production build.
- Strip junk before zipping: source maps (unless intentionally shipped), tests,
.DS_Store,__MACOSX, framework caches. - Create the zip from inside the built-output folder (so
manifest.jsonsits at the zip root). - Prepare: privacy practices, per-permission justification, remote-code disclosure, data-use certification, reviewer test instructions.
- Read
references/publishing/web-store.mdbefore submission.
Minimal Build Evidence
Match evidence to the task before stopping:
| Task | Evidence |
| New scaffold | Built-output path + check-mv3-manifest.sh result |
| Feature change | Relevant unit/integration test + manual-load note when Chrome behavior changed |
| Manifest/permission change | check-mv3-manifest.sh result + permission justification |
| Content-script change | Allowed-URL injection result + disallowed-URL injection result |
| Service-worker change | Restart-resilience note OR explicit "not exercised" caveat |
| Package/Web Store work | preflight-extension.sh result + zip path + reviewer notes |
Output Contract
Final reports include:
- loaded build directory
- zip path (when packaged)
- Chrome version used for manual load (when manually tested)
- summary of permissions and host-permissions justification
- Web Store policy posture: single purpose, data use, remote code, MV3 compliance
- scripts/tests run, including failures
- reviewer notes for remaining manual checks
Guardrails
- Never generate or preserve Manifest V2 for new work.
- Never load
src/in Chrome unless the framework explicitly emits loadable code there. - Never hold durable state in module-scope globals inside the service worker.
- Never rely on
setTimeout/setIntervalto keep the service worker alive. - Never use blocking
webRequestfor normal MV3 network modification. - Never request
<all_urls>without a feature-level justification and a documented narrower alternative. - Never expose arbitrary background
fetchthrough unauthenticated content-script messages. - Never ship remote executable code or undeclared remote-code behavior.
- Always validate built-output paths against
manifest.jsonbefore claiming done. - Always make each permission explainable in one sentence.
Bottom Line
MV3 punishes the same mistakes repeatedly: lost service-worker state, late-registered listeners, timer-based scheduling, isolated-world surprises, over-broad permissions, and shipping src/ instead of built output. Apply the pinned defaults, route to references for depth, run the bundled scripts against the built folder, and produce review-ready evidence.
In this repository31 other files it gives its agents.
- AGENTS.mdAGENTS.md
- audit-agentic-cliSkill
- audit-agentic-mcpSkill
- audit-completionSkill
- audit-skill-by-derailmentSkill
- audit-ui-uxSkill
- build-cloudflare-access-ssoSkill
- build-cloudflare-email-sendingSkill
- build-kernel-ts-sdkSkill
- build-licenseseat-swiftSkill
- build-mcp-use-serverSkill
- build-raycast-script-commandSkill
- build-skillSkill
- ci-cd-optimizeSkill
- convert-to-natural-writingSkill
- convert-url-to-nextjsSkill
- init-agent-configSkill
- init-makefilesSkill
- mobilerun-controlSkill
- optimize-nextjs-fluiditySkill
- publish-npm-packageSkill
- run-astro-auditSkill
- run-railwaySkill
- run-repo-cleanupSkill
- run-ts-cleanupSkill
- test-by-maestroSkill
- test-by-mcpc-cliSkill
- update-agent-configSkill
- use-chatgpt-by-applescriptSkill
- use-cloudflare-tunnelSkill
- use-linear-cliSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

