Turn it on
Start a dev session with the control flags you need:What you can do
extension logs and extension inspect have their own reference pages: logs and inspect.
Shared flags
Every acting command accepts the same trio:--browserchooses the session to target (defaultchromium).--timeout <ms>bounds the round trip (default 5000).--output <pretty|json>chooses the stdout dialect.
--allow-eval for eval, --allow-control for everything else. You never have to guess which gate you missed.
What lands on disk
Beyond the live channel, the session writes append-only records underdist/extension-js/<browser>/: logs.ndjson holds every captured log event, and actions.ndjson (written when the session has --allow-control) audits the control actions that ran. Both are readable after the session ends.
The log contract also reserves structured dx.signal entries, machine-readable diagnostics about the runtime’s own health, with extension logs --signals-only as their filter. No emitter ships yet, so the filter currently returns nothing. The shape is documented so consumers can branch on it the day the first signal lands.
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:
--output json when a script is reading, and you get the full envelope instead:
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:
--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.)
Where logs live in the browser
extension logs merges every context into one terminal timeline (see logs). When you want the browser’s own console for a context instead, each one lives behind a different door:
Three details save time:
- On Chrome, the service worker link also revives an idle MV3 worker, so use it when the background seems dead.
- Content script logs never reach the extension’s own inspector. In the page DevTools console, the context dropdown filters to your extension’s isolated world.
- On Firefox, the
about:debuggingtoolbox covers the background and extension pages. Content script output stays in the page’s DevTools.
extension logs, tagged by context, without clicking through any of those doors.
Safety
The gates are intentional, not bureaucratic:- Observation needs nothing. Reading logs and DOM is always available.
- Bounded operations need
--allow-control.storage,reload, andopenchange state, so you opt in per session. evalneeds--allow-evaland a per-session token. The token is written to a0600file outsidedist/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
- Trigger actions and keyboard commands: test handlers without clicking, headless and in CI.
- Manifest refusals: why Chromium refuses an extension before the session can attach.
- Run both MCP servers: Extension.js control alongside Chrome DevTools MCP.
- CI templates: wire these into a pull-request gate.

