Skip to main content
A browser extension is hard to debug because the interesting state lives in places you can’t easily reach: an MV3 service worker that goes idle, an isolated content-script world, a popup that closes the moment it loses focus. Extension.js opens a small local control channel into your running dev session so you (or an AI agent, or a CI job) can reach those contexts directly: read what they logged, inspect what they rendered, call into them, and fire the events a user would. It’s local, it needs no account, and observation is always free. Anything that changes state is opt-in per session.

Turn it on

Start a dev session with the control flags you need:
Every operation below targets that session and addresses a context the same way, whether you’re reading or acting.

What you can do

Address a context

Reading and acting share one vocabulary: you name the surface, Extension.js resolves it against the session it’s already tracking.

Example: did my extension actually change the page?

This runs on the default template as-is. --context page evaluates in the active tab’s MAIN world, so you can check what your content script really did to the page:
Any console.log your expression triggers flows out through extension logs at the same time, correlated by sequence, so you see the return value and the side effects. To call into the background instead, target a Firefox or MV2 session, where the background is a page that evaluates normally:
On Chromium MV3 (what the default template builds for Chrome) the background is a service worker, and Chrome’s extension CSP blocks eval there. --context background returns an explanatory error instead of a value on those builds. Use --context page or --context content on Chromium MV3. The extension_eval MCP tool defaults to the page context on Chromium MV3 sessions for exactly this reason; on Firefox/MV2 its default stays background.

Cross-browser support

Extension.js debugs through an in-browser companion, not the Chrome DevTools Protocol, so the core loop reaches your own surfaces on both Chrome and Firefox. The old “Firefox uses RDP, not supported” wall is gone for these tools. (eval --context background on a Chromium MV3 build returns an explanatory error. The MV3 background is a service worker, and Chrome’s extension CSP rejects unsafe-eval there on every MV3 build, not only in production. Evaluate in page/content on Chromium MV3, or target the background on a Firefox/MV2 build. extension_list_extensions is an MCP tool rather than a CLI verb: it connects over the DevTools Protocol, so it’s Chromium-only.)

Safety

The gates are intentional, not bureaucratic:
  • Observation needs nothing. Reading logs and DOM is always available.
  • Bounded operations need --allow-control. storage, reload, and open change state, so you opt in per session.
  • eval needs --allow-eval and a per-session token. The token is written to a 0600 file outside dist/ so it never ships in a build, and a random local process can’t quietly drive your service worker.
  • Nothing reaches production. The control channel exists only during dev/preview; it’s gated on a port that isn’t present in a built bundle.

With an AI agent

The same operations are exposed as MCP tools through @extension.dev/mcp (extension_logs, extension_eval, extension_storage, extension_reload, extension_open, extension_list_extensions). The gates are identical: an assistant observes freely but only acts when you’ve enabled it for the session.

Next steps