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

# Dev command for watch mode and live reload

> Develop browser extensions with watch mode, hot module replacement, automatic browser launch, and context-aware reload via the Extension.js dev command.

Use `dev` for day-to-day browser extension development with watch mode, browser launch, and context-aware update behavior.

`dev` runs the development pipeline and watches your project files. It applies update strategies based on what changed: hot module replacement (HMR), hard reload, or a full restart when the change requires it.

## When to use `dev`

* Building features and validating changes in real time.
* Debugging extension behavior in one or more browser targets.
* Running source-inspection workflows with `--source`.

Use `build` for production artifacts, `start` for production build + launch, and `preview` to run existing build output only.

If your extension lives inside a monorepo/submodule, review how `extension.config.*` loads env files (including workspace-root fallback): [Environment variables](/docs/features/environment-variables#how-it-works).

## Dev command capabilities

| Capability             | What it gives you                                     |
| ---------------------- | ----------------------------------------------------- |
| Watch mode iteration   | Tight edit → rebuild → validate loop while coding     |
| Browser-target control | Explicit cross-browser validation per command         |
| Profile-aware runs     | Reliable fresh or persisted profiles by workflow need |
| Source inspection mode | Structured output and diagnostics with source flags   |

## Usage

<CodeGroup>
  ```bash npm theme={null}
  extension dev [path-or-url] [options]
  ```

  ```bash pnpm theme={null}
  extension dev [path-or-url] [options]
  ```

  ```bash yarn theme={null}
  extension dev [path-or-url] [options]
  ```

  ```bash bun theme={null}
  extension dev [path-or-url] [options]
  ```

  ```bash deno theme={null}
  extension dev [path-or-url] [options]
  ```
</CodeGroup>

If you omit the path, Extension.js uses the current working folder. You can also pass a **GitHub tree URL** (for example, `https://github.com/user/repo/tree/main/path`). Extension.js downloads the repository and runs development mode on the local copy.

## Most-used flags

These cover the 80% case. Skip to the [full reference](#arguments-and-flags) for the rest.

| Flag                   | What it does                                                             | Default    |
| ---------------------- | ------------------------------------------------------------------------ | ---------- |
| `--browser <target>`   | Target Chrome, Edge, Firefox, or comma-separated list.                   | `chromium` |
| `--polyfill`           | Bridge `browser.*` API to Chromium targets for Firefox-flavored sources. | `false`    |
| `--port <port>`        | Dev server port. Use `0` for OS-assigned.                                | `8080`     |
| `--starting-url <url>` | Open this URL when the browser launches.                                 | unset      |
| `--no-reload`          | Skip auto-reload. Use when you want a clean dev bundle.                  | reload on  |

## Arguments and flags

| Flag                           | Alias              | What it does                                                                                                                     | Default                                         |
| ------------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `[path or url]`                | -                  | Extension path or remote URL.                                                                                                    | `process.cwd()`                                 |
| `--browser <browser>`          | `-b`               | Browser/engine target (`chromium`, `chrome`, `edge`, `firefox`, engine aliases, or comma-separated values).                      | `chromium`                                      |
| `--profile <path\|boolean>`    | -                  | Browser profile path or boolean profile mode.                                                                                    | fresh profile                                   |
| `--chromium-binary <path>`     | -                  | [Custom Chromium-family binary path](/docs/browsers/running-other-browsers).                                                     | system default                                  |
| `--gecko-binary <path>`        | `--firefox-binary` | [Custom Gecko-family binary path](/docs/browsers/running-other-browsers).                                                        | system default                                  |
| `--polyfill [boolean]`         | -                  | Enable `browser.*` API compatibility polyfill for Chromium targets.                                                              | `false`                                         |
| `--starting-url <url>`         | -                  | Starting URL in launched browser.                                                                                                | unset                                           |
| `--port <port>`                | -                  | Dev server port. Use `0` for OS-assigned port.                                                                                   | `8080`                                          |
| `--host <host>`                | -                  | Host to bind the dev server to. Use `0.0.0.0` for Docker/dev containers.                                                         | `127.0.0.1`                                     |
| `--public-host <host>`         | -                  | Connectable host the browser dials for HMR and the reload bridge, when it differs from the bind `--host` (remote/dev container). | bind host (`127.0.0.1` when bound to `0.0.0.0`) |
| `--no-open`                    | -                  | Do not automatically launch browser.                                                                                             | browser launches by default                     |
| `--no-browser`                 | -                  | Do not launch browser.                                                                                                           | browser launch enabled                          |
| `--no-reload`                  | -                  | Skip the content-script reload runtime and on-rebuild reload dispatch. Reload tabs manually to see changes.                      | reload runtime enabled                          |
| `--wait [boolean]`             | -                  | Wait for `dist/extension-js/<browser>/ready.json` and exit.                                                                      | disabled                                        |
| `--wait-timeout <ms>`          | -                  | Timeout for `--wait` mode.                                                                                                       | `60000`                                         |
| `--wait-format <pretty\|json>` | -                  | Output format for wait results (`json` is machine-readable).                                                                     | `pretty`                                        |
| `--extensions <list>`          | -                  | Comma-separated companion extensions or store URLs.                                                                              | unset                                           |
| `--install [boolean]`          | -                  | Install project dependencies when missing.                                                                                       | command behavior default                        |
| `--author`                     | `--author-mode`    | Enable maintainer diagnostics.                                                                                                   | disabled                                        |

## Automation metadata (recommended for scripts/agents)

When `dev` runs, Extension.js emits machine-readable metadata under:

* `dist/extension-js/<browser>/ready.json`
* `dist/extension-js/<browser>/events.ndjson` (newline-delimited JSON)

For automation (Playwright, continuous integration (CI), AI agents), prefer these files over terminal log parsing.

Treat `ready.json` as the readiness contract:

* `status: "starting"` while booting
* `status: "ready"` when runtime is ready
* `status: "error"` for startup/compile failures
* `runId` uniquely identifies a runtime session
* `startedAt` marks the runtime session start timestamp
* `port` is the dev server port; `host` is the connectable host clients dial (see [bind host vs. connectable host](#bind-host-vs-connectable-host))
* `controlPort` / `instanceId` locate the control bridge used by `extension logs` and the act verbs

### `--no-browser` and readiness synchronization

`--no-browser` disables browser launch but keeps the full dev loop. The dev server still watches your files, and on each rebuild it broadcasts a reload over the control bridge to the extension's service worker so your changes apply without a launched browser driving them:

* A **content-script** change is re-injected into the already-open matching tabs in place (the service worker runs `chrome.scripting.executeScript` with the fresh build), so the page updates on save without a manual refresh. Tabs opened afterward get the new build too — the service worker re-registers the content scripts dynamically (`chrome.scripting.registerContentScripts`).
* A **service-worker / manifest** change restarts the extension.

So `--no-browser` behaves like a normal `dev` session for headless, continuous integration (CI), and remote/dev-container workflows: load the built `dist/<browser>` into any browser you control and it keeps updating on save. (Use `--no-reload` for a static dev bundle that never reloads — see below.)

`--no-browser` does not block external runners until the compile finishes.

For Playwright/CI/AI workflows:

1. Run `extension dev --no-browser` as a long-lived process.
2. Run `extension dev --wait --browser=<browser>` as the readiness gate.
3. Launch external browser automation only after `status: "ready"`.

`--wait` targets a second process (or CI step) and exits non-zero on `error`/timeout.
When `--wait` sees a stale `ready.json` from a dead process (`pid` no longer alive), it keeps waiting for a live producer.
If you pass both `--wait` and `--no-browser` in the same command invocation, `--wait` takes precedence. The command runs in wait-only mode.

### `--no-reload` for a clean dev bundle

`--no-reload` skips the content-script reinjection wrapper and the on-rebuild reload dispatch. The dev `dist` stays close to a production bundle and an open tab is not disturbed when files change. Reload the extension or page yourself to pick up changes.

`--no-reload` is only supported on `extension dev`. Passing it to `start`, `preview`, or `build` exits with an error. Internally it sets `EXTENSION_NO_RELOAD=true` so the develop process can read it from outside the CLI argv.

## Source inspection workflow

Use source options only with `dev`. Source inspection opens a target URL in the browser and prints the full, live HTML **after** your content scripts run. Use this to verify what your extension actually changes.

* `--source [url]` to inspect a page (defaults to `--starting-url` or `https://example.com`)
* `--watch-source` / `--no-watch-source` to re-print HTML on each rebuild

`start` and `preview` do not support source-inspection flags.

### Source flags

| Flag                                            | What it does                               | Default behavior                   |
| ----------------------------------------------- | ------------------------------------------ | ---------------------------------- |
| `--source [url]`                                | Enable source inspection flow.             | disabled                           |
| `--watch-source [boolean]`                      | Re-print source output on watched updates. | `true` when `--source` is enabled  |
| `--source-format <pretty\|json\|ndjson>`        | Output format for source inspection.       | falls back to log format or `json` |
| `--source-summary [boolean]`                    | Emit compact summary output.               | disabled                           |
| `--source-meta [boolean]`                       | Include page metadata.                     | enabled when source is on          |
| `--source-probe <selectors>`                    | Comma-separated CSS selectors to probe.    | unset                              |
| `--source-tree <off\|root-only>`                | Include compact extension tree details.    | unset                              |
| `--source-console [boolean]`                    | Include console summary.                   | disabled                           |
| `--source-dom [boolean]`                        | Include DOM snapshots/diffs.               | enabled when watch-source is on    |
| `--source-max-bytes <bytes>`                    | Cap output payload size.                   | unset                              |
| `--source-redact <off\|safe\|strict>`           | Redaction strategy for source output.      | format-dependent                   |
| `--source-include-shadow <off\|open-only\|all>` | Control Shadow DOM inclusion.              | `open-only` when source is on      |
| `--source-diff [boolean]`                       | Include diff metadata on watch updates.    | enabled when watch-source is on    |

## Logging flags

| Flag                                                 | What it does                                                                                 | Default            |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------ |
| `--logs <off\|error\|warn\|info\|debug\|trace\|all>` | Minimum log level.                                                                           | `off`              |
| `--log-context <list\|all>`                          | Context filter (`background`, `content`, `page`, `sidebar`, `popup`, `options`, `devtools`). | `all`              |
| `--log-format <pretty\|json\|ndjson>`                | Logger output format.                                                                        | `pretty`           |
| `--no-log-timestamps`                                | Disable timestamps in pretty mode.                                                           | timestamps enabled |
| `--no-log-color`                                     | Disable color in pretty mode.                                                                | color enabled      |
| `--log-url <pattern>`                                | Filter log events by URL substring/regex.                                                    | unset              |
| `--log-tab <id>`                                     | Filter log events by tab ID.                                                                 | unset              |

## Shared global options

Also supports [Global flags](/docs/workflows/global-flags).

## Monorepo and workspace roots

You can point `dev` (and `build`) at the **root of a monorepo** instead of the extension package itself. Extension.js detects the workspace root and auto-resolves the extension package inside it:

```bash theme={null}
extension dev .            # run from the monorepo root
```

When exactly one extension package is found, Extension.js resolves it and prints:

```text theme={null}
Workspace root detected — resolved extension package: packages/my-extension
```

When several candidates exist, it lists them so you can point at the one you mean:

```bash theme={null}
extension dev packages/my-extension
```

## Examples

### Running a local extension

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension
  ```

  ```bash bun theme={null}
  extension dev ./my-extension
  ```

  ```bash deno theme={null}
  extension dev ./my-extension
  ```
</CodeGroup>

### Running a remote extension from GitHub

Pass a GitHub tree URL as the argument to develop a remote extension locally:

<CodeGroup>
  ```bash npm theme={null}
  extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
  ```

  ```bash pnpm theme={null}
  extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
  ```

  ```bash yarn theme={null}
  extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
  ```

  ```bash bun theme={null}
  extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
  ```

  ```bash deno theme={null}
  extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
  ```
</CodeGroup>

### Inspecting page HTML with source mode

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension --source https://example.com
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --source https://example.com
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --source https://example.com
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --source https://example.com
  ```

  ```bash deno theme={null}
  extension dev ./my-extension --source https://example.com
  ```
</CodeGroup>

### Running in Firefox

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension --browser firefox
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --browser firefox
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --browser firefox
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --browser firefox
  ```

  ```bash deno theme={null}
  extension dev ./my-extension --browser firefox
  ```
</CodeGroup>

### Running in multiple browsers in sequence

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension --browser=chrome,firefox
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --browser=chrome,firefox
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --browser=chrome,firefox
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --browser=chrome,firefox
  ```

  ```bash deno theme={null}
  extension dev ./my-extension --browser=chrome,firefox
  ```
</CodeGroup>

### Running inside Docker or a dev container

When you run inside Docker, dev containers, or GitHub Codespaces, bind the dev server to `0.0.0.0` so the host machine can reach it:

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension --host 0.0.0.0
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --host 0.0.0.0
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --host 0.0.0.0
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --host 0.0.0.0
  ```

  ```bash deno theme={null}
  extension dev ./my-extension --host 0.0.0.0
  ```
</CodeGroup>

Combine with `--port 0` to let the OS choose an available port automatically:

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension --host 0.0.0.0 --port 0
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --host 0.0.0.0 --port 0
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --host 0.0.0.0 --port 0
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --host 0.0.0.0 --port 0
  ```

  ```bash deno theme={null}
  extension dev ./my-extension --host 0.0.0.0 --port 0
  ```
</CodeGroup>

#### Bind host vs. connectable host

`--host` is the address the dev server **binds** to. The browser (the HMR client and the reload bridge) needs an address it can actually **connect** to, which is not always the same value:

* `--host 0.0.0.0` binds every interface, but `0.0.0.0` is not a connectable address. Extension.js automatically advertises `127.0.0.1` to the browser instead — the right target for the common port-forwarded Docker/dev-container/Codespaces setup, where the browser runs on the host and the port is forwarded to the container.
* For a true **remote** setup — the browser runs on a different machine than the dev server — pass `--public-host` with the address the browser can reach (an LAN IP or hostname). It is propagated to the HMR client URL, `ready.json`, and the reload bridge baked into the extension.

<CodeGroup>
  ```bash npm theme={null}
  # Browser on another machine reaches the dev server at devbox.local
  extension dev ./my-extension --host 0.0.0.0 --public-host devbox.local
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --host 0.0.0.0 --public-host devbox.local
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --host 0.0.0.0 --public-host devbox.local
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --host 0.0.0.0 --public-host devbox.local
  ```
</CodeGroup>

When `--host` is a concrete address already (for example `--host 192.168.1.50`), that value is connectable as-is and is used directly — `--public-host` is only needed when the bind host and the browser-facing host differ.

### Running in Brave as a custom binary

<CodeGroup>
  ```bash npm theme={null}
  extension dev ./my-extension --chromium-binary /path/to/brave
  ```

  ```bash pnpm theme={null}
  extension dev ./my-extension --chromium-binary /path/to/brave
  ```

  ```bash yarn theme={null}
  extension dev ./my-extension --chromium-binary /path/to/brave
  ```

  ```bash bun theme={null}
  extension dev ./my-extension --chromium-binary /path/to/brave
  ```

  ```bash deno theme={null}
  extension dev ./my-extension --chromium-binary /path/to/brave
  ```
</CodeGroup>

## Best practices

* **Browser compatibility:** Test your extension in different browsers to verify it works on every target.
* **Polyfilling:** If Firefox or a Gecko-based browser is also a target, use `--polyfill`. This flag enables `browser.*` API compatibility in Chromium-based browsers.
* **Source inspection workflows:** Use `dev` (not `start`/`preview`) when you need source inspection options.
* **Automation reliability:** Treat `dev` as the watch-mode companion (`--no-browser` + `dev --wait`). Treat `start` as the production companion (`--no-browser` + `start --wait`). Use `--wait-format=json` for scripts and CI automation.

## Next steps

* Build production artifacts with [`build`](/docs/commands/build).
* Validate production launch flow with [`start`](/docs/commands/start).
* Review browser targeting with [Browser-specific manifest fields](/docs/features/browser-specific-fields).
* Configure shared defaults in [`extension.config.js`](/docs/features/extension-configuration).
* Review configuration env loading behavior in [Environment variables](/docs/features/environment-variables#how-it-works).
