PROJECTTEMPLATE

Chrome Extension

A browser extension is three small programs pretending to be one. The popup is a tiny web page that appears when you click the icon. The content script is code injected into someone else's website, where it can read and change the page. The background service worker is the coordinator that owns state and talks to APIs. They run in separate worlds and can only communicate by passing messages, and that message passing is where agent-built extensions go to die: the agent writes each piece correctly and wires them together wrong, or writes Manifest V2 patterns from its training data into your V3 project.

The other thing to get right before any code exists is permissions. Every permission you request is a scary line in the install prompt and a bigger attack surface if your extension is ever compromised. "Read and change all your data on all websites" is what <all_urls> looks like to the person installing. If your extension works on one site, say that one site. This spec forces the permissions list to be written down and justified up front, because an agent left to guess will request everything, and it will work in testing, and you will only feel the cost at review time or install time.

One hard rule baked in below: no secrets in the extension. Anything shipped in an extension can be unzipped and read by anyone who installs it. If your extension needs an API that requires a key, the key lives on a server you control and the extension talks to that server.

Prerequisites

  • Chrome installed, with `chrome://extensions` Developer Mode toggled on so you can load the unpacked extension.
  • The exact site or sites the extension should act on, decided before you start.
markdown
# Project: Chrome extension, [extension name]

Build a Chrome extension (Manifest V3, and reject any MV2 pattern, including
background pages and `chrome.browserAction`) that [one sentence: what it
does for the user, e.g., "adds a copy-as-markdown button to GitHub issue
pages"].

## Behavior

- When the user is on [target site / URL pattern], the content script
  [what it detects or changes on the page].
- Clicking the toolbar icon opens a popup that shows [current state /
  controls: e.g., an on-off toggle and a count of items processed].
- Settings persist across browser restarts.

## Architecture (three parts, message-passing between them)

- `manifest.json`: MV3. `action` for the popup, `content_scripts`
  matching ONLY [url pattern, e.g., "https://github.com/*"], and a
  background `service_worker`.
- Content script: reads/modifies the page DOM. Holds no state. Sends
  results to the service worker via `chrome.runtime.sendMessage`.
- Service worker: owns state and any network calls. Remember it is
  killed when idle: no in-memory state that matters; persist to
  `chrome.storage` immediately.
- Popup: renders state from `chrome.storage`, sends user actions as
  messages. Assume it can open before any page has loaded.

Define every message shape up front in one shared file:
`{ type: "[MESSAGE_NAME]", payload: {...} }`. No stringly-typed
ad-hoc messages invented mid-build.

## Permissions (exhaustive list, request nothing else)

- `storage`: settings and state.
- Host permission for [exact match pattern] only. NOT `<all_urls>`.
- [Any other permission, each with a one-line justification. If you
  cannot justify it in one line, it is not in the list.]

## Data and secrets

- No API keys, tokens, or secrets anywhere in the extension bundle.
  Anything the extension ships, any user can read.
- If [external API] is needed: the extension calls [my server
  endpoint], and the server holds the key. Stub this server call
  behind one function so it is swappable.
- The extension sends no page content anywhere off the machine except
  [explicitly listed data, or "nothing"].

## Constraints

- Vanilla JS or TypeScript with a minimal build step. No framework in
  the content script; it must not slow down or visually break the
  host page.
- Content script must tolerate the target site's client-side
  navigation: use a MutationObserver or re-check on URL change rather
  than assuming one page load.
- All user-visible errors surface in the popup, not as silent console
  noise.

## Done means

- Loads unpacked via `chrome://extensions` with zero manifest errors.
- Works after: browser restart, service worker idle-kill (wait 60s,
  then use it), and navigating within the target site without a full
  page reload.
- Does nothing at all on non-target sites: no injected code, no
  console output.
- A README section documents each permission and why it is needed.

Adaptation notes:

  • Extension with no UI (pure page modification): drop the popup, keep the service worker and storage, and put the on-off toggle in the icon's context menu or omit it. The message-shape file stays.
  • Firefox too: WebExtensions are close enough that the spec holds; add a requirement for the browser.* namespace via a polyfill and test in both.
  • If the extension needs to act on every site (a genuine all-sites tool like a highlighter), accept <all_urls> but say so explicitly in the spec and the README, and expect the install warning and a slower store review.
  • The mistake: testing only the happy path where the page was fully loaded before the content script ran. Modern sites render late and navigate without reloading; the "done means" list above exists because agents skip exactly those three checks.
  • Publishing to the Chrome Web Store is its own project: developer account, privacy disclosures, review delays. Scope this build to "works unpacked," then decide about the store.