Skip to content
Back to skills

Build Browser Extension

ASecurity

End-to-end workflow to build a Manifest V3 browser extension: decide which context owns each piece of logic (content script vs MV3 service worker vs injected page script vs popup/options), wire messaging between contexts, request the narrowest permissions (activeTab -> optional -> host -> all_urls), and pass Chrome/Edge/Firefox store review. Complements the manifest-permissions-audit and store-submission-readiness skills. The popup/options UI seams to frontend-engineering.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentsrustapifrontend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill build-browser-extension --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Build Browser Extension?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Build Browser Extension
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-build-browser-extension/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-build-browser-extension)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: build-browser-extension
description: "End-to-end workflow to build a Manifest V3 browser extension: decide which context owns each piece of logic (content script vs MV3 service worker vs injected page script vs popup/options), wire messaging between contexts, request the narrowest permissions (activeTab -> optional -> host -> all_urls), and pass Chrome/Edge/Firefox store review. Complements the manifest-permissions-audit and store-submission-readiness skills. The popup/options UI seams to frontend-engineering."
---

# Skill: Build a Browser Extension (Manifest V3)

A browser extension is a multi-context distributed system: its UI is a frontend
app, but its execution model (ephemeral service worker, isolated content scripts,
a strict permission/store regime) is what makes it distinctive. This skill is the
**end-to-end build workflow**; pair it with the deeper audit/checklist skills it
references at each step. Traverse the decision trees in
[`../../knowledge/where-logic-lives.md`](../../knowledge/where-logic-lives.md) and
[`../../knowledge/manifest-v3-architecture.md`](../../knowledge/manifest-v3-architecture.md)
**before** placing logic or requesting a permission.

## 1. Architecture — where does each piece of logic live?

Pick the **least-privileged context that works** (full tree:
[`../../knowledge/where-logic-lives.md`](../../knowledge/where-logic-lives.md)):

| Need | Context |
|---|---|
| Read/modify a page's **DOM** (not its JS) | **Content script** (isolated world) |
| Reach the page's **own JS / window globals** | **Injected page script** (main world) — untrusted, bridge via `postMessage` |
| Privileged `chrome.*` APIs (tabs, alarms, network), background work | **Service worker** (MV3 background) — ephemeral, no DOM |
| Toolbar UI | **Popup** (`action.default_popup`) — transient |
| Settings / full page | **Options / extension page** |

**Never assume two contexts share memory.** Cross-context state lives in
`chrome.storage` or is passed by message.

## 2. Service worker discipline

MV3's background is **event-driven and terminated when idle** — re-think any
"long-running/stateful" design:

- Register **all** event listeners at the **top level**, synchronously, on every
  load — not inside an async callback, or the wake-up event is missed.
- Hold **no** in-memory state you can't lose; persist to `chrome.storage`,
  re-hydrate on wake.
- No DOM, no `localStorage` — it's a worker. (See
  [`../../best-practices/treat-the-background-as-ephemeral.md`](../../best-practices/treat-the-background-as-ephemeral.md).)

## 3. Messaging

- Content script ↔ service worker ↔ popup/options → `chrome.runtime.sendMessage`
  / `onMessage`, or a long-lived `chrome.runtime.connect` `Port`; service worker →
  a tab → `chrome.tabs.sendMessage`.
- Content script (isolated) ↔ injected page script (main world) →
  `window.postMessage` is the **only** bridge — **validate `origin`, `source`, and
  message shape**; treat it as untrusted input.
- Return `true` from an `onMessage` listener that responds asynchronously, and
  remember the worker may have been asleep (don't rely on prior in-memory state).

See [`../../best-practices/message-pass-across-the-isolation-boundary.md`](../../best-practices/message-pass-across-the-isolation-boundary.md).

## 4. Least-privilege permissions

Walk the permissions tree narrowest-first — escalate only when a concrete feature
forces it, and justify each escalation in the listing:

1. **`activeTab`** — temporary current-tab access on a user gesture; no install
   warning. The default for user-initiated, current-tab actions.
2. **Specific API perms** — exactly the `chrome.*` APIs you use (`storage`,
   `alarms`, `scripting`).
3. **Narrow `host_permissions`** — specific match patterns
   (`https://api.example.com/*`), never `<all_urls>` reflexively.
4. **`optional_permissions` / `optional_host_permissions`** — request broad access
   at runtime with `chrome.permissions.request()` on a user action.
5. **`<all_urls>`** — only when running on arbitrary sites *is* the core function;
   expect stricter review.

Re-audit at every release; drop unused permissions. Run the deeper audit with
[`../manifest-permissions-audit/SKILL.md`](../manifest-permissions-audit/SKILL.md)
(and see [`../../best-practices/request-least-privilege-permissions.md`](../../best-practices/request-least-privilege-permissions.md)).

## 5. Store submission checklist

- [ ] `manifest_version: 3`, version bumped.
- [ ] **No remotely-hosted code** — all executable JS ships in the package (fetch
      data, not code). (See
      [`../../best-practices/no-remotely-hosted-code.md`](../../best-practices/no-remotely-hosted-code.md).)
- [ ] Permissions minimized and **re-audited**; each (and any broad host access)
      justified.
- [ ] `web_accessible_resources` scoped to specific files + origins, never `*`.
      (See [`../../best-practices/scope-web-accessible-resources.md`](../../best-practices/scope-web-accessible-resources.md).)
- [ ] Privacy policy + accurate data-use disclosures.
- [ ] Icons, screenshots, listing copy; single-purpose description that matches
      behavior.
- [ ] Tested on each target: Chrome, **Edge** (Chromium — usually drop-in),
      **Firefox** (`browser.*`/Promise differences, `webextension-polyfill`,
      `browser_specific_settings.gecko.id`, every version signed).
- [ ] `declarativeNetRequest` rules (if any) validated; content-script match
      patterns as narrow as the feature allows.
- [ ] After publish: staged/percentage rollout where supported; monitor reviews +
      error telemetry.

Run the full per-store readiness pass with
[`../store-submission-readiness/SKILL.md`](../store-submission-readiness/SKILL.md);
the cross-browser delta + the three pipelines live in
[`../../knowledge/cross-browser-and-stores.md`](../../knowledge/cross-browser-and-stores.md).

## Seam — the UI is a frontend app

The **popup / options UI** is an ordinary frontend and routes to
`frontend-engineering`, **not** this skill: components/forms/accessibility, client
state (note `chrome.storage` + `onChanged` is the persistence/sync layer — don't
bolt a global store onto an already-transient popup), and popup open-speed/bundle
discipline (a popup must open fast, so bundle discipline matters *more*, not
less). See [`../../CLAUDE.md`](../../CLAUDE.md) §1 and §3.

This skill owns only the MV3-distinctive layer (context placement, service-worker
lifetime, messaging, permissions, store review). Full reference + the two Mermaid
decision trees:
[`../../knowledge/where-logic-lives.md`](../../knowledge/where-logic-lives.md)
(where logic lives) and
[`../../knowledge/manifest-v3-architecture.md`](../../knowledge/manifest-v3-architecture.md)
(permissions-minimization). MV3 specifics are volatile — verify at use against the
Chrome/MDN/Firefox docs.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…