Browser Extension Development
When to use
- New extension scaffold from scratch (MV3)
- Adding a content script, background worker, popup, options page, or side panel to an existing extension
- Migrating a Manifest V2 extension to Manifest V3
- Preparing a submission for Chrome Web Store, Firefox AMO, or Edge Add-ons
- Debugging cross-browser permission or messaging issues
Workflow
Classify the extension type — decide which surfaces are needed:
Surface File Purpose Background background/service-worker.jsLong-running logic, alarms, storage sync Content script content/index.jsInjected into host pages; DOM access Popup popup/popup.html + popup.jsToolbar icon click UI (ephemeral) Options page options/options.htmlPersistent settings UI Side panel sidepanel/panel.htmlChrome 114+ persistent side panel DevTools panel devtools/devtools.htmlPage inspector integration Write
manifest.json(MV3 only) — required fields:{ "manifest_version": 3, "name": "…", "version": "1.0.0", "description": "…", "permissions": [], "host_permissions": [], "background": { "service_worker": "background/service-worker.js" }, "action": { "default_popup": "popup/popup.html" }, "content_scripts": [], "web_accessible_resources": [] }Request the minimum permission set; justify every entry in a comment block above the manifest.
Implement messaging architecture — choose ONE pattern and stick to it:
- Short-lived:
chrome.runtime.sendMessage/onMessage— fire-and-forget between popup and background. - Long-lived:
chrome.runtime.connect/Port— streaming data from content script to background. - Never call DOM APIs from the service worker; delegate to content scripts via
chrome.tabs.sendMessage.
- Short-lived:
Handle storage correctly:
- User preferences →
chrome.storage.sync(≤100 KB, synced across devices). - Large / sensitive data →
chrome.storage.local(≤10 MB). - Session state (cleared on browser close) →
chrome.storage.session(MV3 only). - Always handle
chrome.runtime.lastErrorafter every storage call.
- User preferences →
Content-Security-Policy — MV3 default CSP blocks
evaland inline scripts. Serve all scripts from extension files; never inject<script>tags with inline code.Cross-browser compatibility:
- Use the
webextension-polyfillpackage (mozilla/webextension-polyfill) to normalizechrome.*vsbrowser.*APIs. - Test in Chrome Canary, Firefox Nightly, and Edge Dev before release.
- Firefox requires
browser_specific_settings.gecko.idin manifest.
- Use the
Build pipeline:
- Bundle with Vite +
vite-plugin-web-extensionor webpack +copy-webpack-plugin. - Use separate entry points per surface; do not bundle them together.
- Output a
dist/folder; zip it withweb-ext buildfor store submission.
- Bundle with Vite +
Security audit before submission — run through
.claude/checklists/security.mdplus:- No
"<all_urls>"unless unavoidable; prefer specific origins. content_security_policyexplicitly set; nounsafe-eval, nounsafe-inline.- All external API calls go through the background service worker, not content scripts.
- Never store auth tokens in
localStorageon the host page; usechrome.storage.local.
- No
Store submission checklist:
- 128×128 icon (PNG, no alpha border), 440×280 promotional tile.
- Privacy policy URL if any
host_permissionsor personal data is collected. - Single-purpose declaration matches
"description"exactly. - Chrome Web Store review typically takes 1–3 business days; plan accordingly.
Version & update cycle — bump
"version"in manifest for every release; the browser auto-updates within 24 h. Usechrome.runtime.onInstalledto migrate storage schema on update.
Standards
Do:
- Keep the service worker stateless — it can be killed at any time; persist everything to
chrome.storage. - Use
chrome.alarmsinstead ofsetIntervalin the background; alarms survive worker restarts. - Declare
"content_scripts""run_at": "document_idle"unless you explicitly needdocument_start. - Isolate business logic in plain
.jsmodules importable by both popup and service worker. - Write end-to-end tests with
puppeteer+puppeteer-extra-plugin-stealthorplaywrightwith extension loading.
Do not:
- Use
chrome.tabs.executeScriptfor code injection — use declarative content scripts instead. - Include
node_modulesin the extension zip. - Persist JWTs or API keys in
chrome.storage.sync(not encrypted at rest on all platforms). - Request
"tabs"permission just to get the current URL — use"activeTab"instead. - Access cross-origin resources directly from content scripts — proxy through the background worker.
Common mistakes to avoid
- Service worker going idle: MV3 service workers terminate after ~30 s of inactivity. Use
chrome.alarmsor an activePortconnection to keep alive only when genuinely needed — not to fight the lifetime model. chrome.runtime.lastErrorswallowing: Every asyncchrome.*callback must checkchrome.runtime.lastError; unchecked errors cause silent failures.- Popup script assuming DOM is ready: Wrap popup logic in
DOMContentLoaded;popup.htmlis destroyed on close — never cache popup DOM references in the background. - Missing
web_accessible_resources: Injected iframes or images from the extension package fail silently if not listed here. - Forgetting Firefox's strict CSP for AMO: Firefox AMO review rejects any use of
evaleven if Chrome accepts it. - Version not bumped: Auto-update only triggers if the version string increases; patch changes without a version bump never reach users.
- Overly broad
host_permissions: Requesting"<all_urls>"triggers a manual Chrome Web Store review and lowers user trust score.
Output format
A complete extension project looks like:
manifest.json
background/
service-worker.js
content/
index.js
styles.css
popup/
popup.html
popup.js
options/
options.html
options.js
icons/
16.png 48.png 128.png
_locales/en/messages.json
tests/
e2e/
extension.test.js
package.json
vite.config.js (or webpack.config.js)
Reference architecture template: .claude/templates/architecture.md
Related checklists
.claude/checklists/security.md— permission minimization, CSP, secret storage.claude/checklists/launch.md— store submission readiness
Related agents
.claude/agents/engineering/— feature implementation workflow.claude/agents/quality/— QA and testing strategy.claude/agents/stack/web/— web frontend patterns (popup/options UI)