> ## Documentation Index
> Fetch the complete documentation index at: https://extension.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Debug a running browser extension from the terminal

> Drive a live extension dev session from the terminal: read logs, inspect the DOM, eval in the worker, read chrome.storage, and reload on Chrome or Firefox.

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:

```bash theme={null}
# Read-only is always on. Add control to act on the extension:
pnpm extension dev --allow-control

# Add eval to also run arbitrary expressions in a context:
pnpm extension dev --allow-control --allow-eval
```

Every operation below targets that session and addresses a context the same way, whether you're reading or acting.

## What you can do

| Command                                    | What it does                                                                                                         | Gate              |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `extension logs`                           | Stream logs from every context (SW, content, popup, options, sidebar, devtools) in one ordered timeline              | —                 |
| `extension inspect`                        | Read the live, post-injection DOM of a surface                                                                       | —                 |
| `extension storage get \| set`             | Read or write your extension's `chrome.storage`                                                                      | `--allow-control` |
| `extension reload`                         | Reload the extension or a tab                                                                                        | `--allow-control` |
| `extension open <popup\|options\|sidebar>` | Open an extension surface                                                                                            | `--allow-control` |
| `extension open action`                    | [Trigger the toolbar action](/docs/debugging/trigger-actions-and-commands) — opens its popup, or replays `onClicked` | `--allow-control` |
| `extension open command --name <cmd>`      | [Replay a keyboard-shortcut command](/docs/debugging/trigger-actions-and-commands)                                   | `--allow-control` |
| `extension eval "<expr>" --context <c>`    | Evaluate an expression in a context and return the value                                                             | `--allow-eval`    |

## Address a context

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

| Address                                            | Means                                        |
| -------------------------------------------------- | -------------------------------------------- |
| `--context background`                             | the service worker (or MV2 background page)  |
| `--context popup` / `options` / `sidebar`          | that extension surface, if it's open         |
| `--context content --url "https://shop.example/*"` | the isolated content world on matching pages |
| `--context content --tab 7`                        | the content world in a specific tab          |

## Example: did my background handler actually run?

```bash theme={null}
pnpm extension eval "globalThis.__lastSyncAt" --context background --allow-eval
```

```json theme={null}
{ "ok": true, "value": 1718000000000 }
```

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.

## 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.

| Capability                                                                        | Chromium | Firefox      |
| --------------------------------------------------------------------------------- | -------- | ------------ |
| `logs`, `inspect`, `eval`, `storage`, `reload`, `open` (incl. `action`/`command`) | ✓        | ✓ (verified) |
| `inspect --deep-dom` (closed shadow roots)                                        | ✓        | — (uses CDP) |
| `extension_list_extensions` (MCP tool)                                            | ✓        | — (uses CDP) |

(`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 — 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`](/docs/integrations/chrome-devtools-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](/docs/debugging/trigger-actions-and-commands) — test handlers without clicking, headless and in CI.
* [Run both MCP servers](/docs/integrations/chrome-devtools-mcp) — Extension.js control alongside Chrome DevTools MCP.
* [CI templates](/docs/workflows/ci-templates) — wire these into a pull-request gate.
