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

# Browsers supported by Extension.js

> See which browsers Extension.js supports. Run and test extensions across Chrome, Edge, Firefox, and custom binaries from a single CLI workflow.

See which browsers Extension.js supports and validate your extension across
Chrome, Edge, Firefox, and custom binaries from a single CLI workflow within the
same project.

## Choose the right target

| Target | Use when | Example |
| - | - | - |
| `chromium` | Fast default local development | `extension dev --browser=chromium` |
| `chrome` | Validating Chrome-specific behavior | `extension dev --browser=chrome` |
| `edge` | Validating Edge distribution behavior | `extension dev --browser=edge` |
| `firefox` | Validating Gecko compatibility and APIs | `extension dev --browser=firefox` |
| `chrome,firefox` | Release checks across both major engines | `extension build --browser=chrome,firefox` |
| Named forks | Running an installed fork by name (auto-located) | `extension dev --browser=brave` (also `opera`, `vivaldi`, `yandex`, `waterfox`, `librewolf`, `zen`, `floorp`) |
| `chromium-based` | Custom Chromium binaries in `dev`; family-generic `build` artifacts | `extension dev --browser=chromium-based --chromium-binary=/path/to/browser` |
| `gecko-based` | Custom Firefox-family binaries in `dev`; family-generic `build` artifacts | `extension dev --browser=gecko-based --gecko-binary=/path/to/browser` |
| `safari` | Developing or building a Safari app on macOS (Xcode required) | `extension dev --browser=safari` |

## How it works

Use `--browser` to choose a target in `dev`, `start`, `preview`, and `build`.

If you do not specify a browser, the CLI defaults to `chromium`.

`--browser` accepts exactly these values, alone or comma-separated:

```text theme={null}
chrome | chromium | edge | firefox | brave | opera | vivaldi | yandex |
waterfox | librewolf | zen | floorp | chromium-based | gecko-based |
firefox-based |
safari | webkit-based
```

`--browser=all` is also accepted and expands to `chrome, edge, firefox`.

<Warning>
  Prefer `chromium` (or Chrome for Testing via `npx extension install chrome`)
  over branded Chrome for development. Recent branded Chrome builds (150+) drop
  the `--load-extension` switch unless a policy disables that behavior. A
  dropped switch looks exactly like a healthy launch. When Extension.js cannot
  confirm the load, it warns and points you at `chrome://extensions`.
</Warning>

`safari` (and its `webkit-based` alias) is the exception: it is a **macOS-only** target supported by `build` and `dev`, not by `preview` or `start`. A Safari dev session streams logs and reloads on save. See [Building Safari extensions](/docs/browsers/safari).

<Frame>
  <iframe className="w-full aspect-video rounded-xl" src="https://www.youtube-nocookie.com/embed/YBOIpiB_SHQ?rel=0" title="Extension.js: Chromium, Edge and Firefox from one dev command" loading="lazy" allow="encrypted-media; picture-in-picture; fullscreen" allowFullScreen />
</Frame>

## Requested target vs. launch binary

The browser you request determines the artifact. The binary is just the runtime.

When you run `extension dev --browser=chromium`, Extension.js always:

* Writes output to `dist/chromium` (the folder is named after the requested target, never after the binary that launches).
* Resolves [browser-specific manifest fields](/docs/features/browser-specific-fields) for the requested target.

If the requested browser is not installed, Extension.js does not change the target. For `chrome` and `chromium` it looks for another managed Chromium-family binary (one previously downloaded by `npx extension install`) and uses it as the runtime instead:

* Requested `chromium` missing: falls back to managed Chrome, then managed Edge.
* Requested `chrome` missing: falls back to managed Chromium, then managed Edge.

When this happens, the CLI prints a warn naming the missing browser and the fix, for example `npx extension install chromium`. The warn deliberately omits the substitute binary path. The session's identity card already carries a Binary row that names the exact binary in use, so the fact is printed once. The output folder and the emitted manifest are exactly what they would be without the fallback.

### What `--browser=edge` does without a managed Edge

`edge` never swaps to another browser. Extension.js resolves the Edge binary in this order:

1. The path in the `EDGE_BINARY` environment variable, when set.
2. A managed Edge from `npx extension install edge`, or the Edge that is installed on your system.

When neither resolves, the command prints install guidance (`npx extension install edge`) and exits with code `1`. There is no silent Chromium substitute for a missing Edge.

If a Chromium window still surprises you, check the identity card. Its Binary row names the exact binary that launched, and its provenance label tells you why. A requested `chrome` or `chromium` can borrow a managed family binary (see above), but a requested `edge` cannot. The output contract holds either way: `dist/edge` exists only when `edge` was the requested target.

### Binary provenance labels

The identity card labels where the session's binary came from:

| Label | Meaning |
| - | - |
| `managed` | A binary from the managed cache (`npx extension install <browser>`). |
| `pinned` | A binary you pinned with `--chromium-binary` or `--gecko-binary`. |
| `system` | A browser found on your system. |
| `snapshot` | The managed Chromium tip-of-tree snapshot. |

### Chromium snapshots vs stable

The managed `chromium` install is a tip-of-tree snapshot, not a stable release. When a stable system Chromium exists, Extension.js swaps to it automatically and prints a warn with the opt-out. Set `EXTENSION_PREFER_CHROMIUM_SNAPSHOT=true` to keep the cached snapshot instead.

Within the Chromium family this substitution is safe: `dist/chrome` and `dist/chromium` are byte-identical because manifest prefixes resolve per engine family, not per vendor. See [Browser-specific manifest fields](/docs/features/browser-specific-fields) for the prefix rules.

To pre-install managed binaries for reproducible runs, use `npx extension install <browser>` or `npx extension install all` (which covers `chromium` too).

## Supported browsers

Named browser targets:

| Browser | Usage |
| - | - |
| **Chrome** | `npx extension dev --browser=chrome` |
| **Edge** | `npx extension dev --browser=edge` |
| **Firefox** | `npx extension dev --browser=firefox` |
| **Chromium** | `npx extension dev --browser=chromium` |

Named forks (auto-located from your system, no binary path required):

| Browser | Engine | Usage |
| - | - | - |
| **Brave** | Chromium | `npx extension dev --browser=brave` |
| **Opera** | Chromium | `npx extension dev --browser=opera` |
| **Vivaldi** | Chromium | `npx extension dev --browser=vivaldi` |
| **Yandex** | Chromium | `npx extension dev --browser=yandex` |
| **Waterfox** | Gecko | `npx extension dev --browser=waterfox` |
| **LibreWolf** | Gecko | `npx extension dev --browser=librewolf` |
| **Zen** | Gecko | `npx extension dev --browser=zen` |
| **Floorp** | Gecko | `npx extension dev --browser=floorp` |

If a named fork is not installed, Extension.js exits with install guidance. See [Running other browsers](/docs/browsers/running-other-browsers).

Engine-based targets (custom binary required):

| Engine target | Usage |
| - | - |
| **Chromium-based** | `npx extension dev --browser=chromium-based --chromium-binary=/path/to/browser` |
| **Gecko-based** (`firefox-based`) | `npx extension dev --browser=gecko-based --gecko-binary=/path/to/browser` |

Extension.js treats `firefox-based` as a Gecko engine target internally.

Mobile browsers such as Samsung Internet, Chrome for Android, and Firefox for Android are not launch targets. Build the Firefox output and follow that browser's own sideloading flow.

### What engine targets are for

`chromium-based` and `gecko-based` target an engine family instead of one vendor.

In `dev`, `start`, and `preview`, they run a binary that has no named target. Think a nightly fork, an in-house build, or a fork that the built-in locators do not know.

In `build`, they exist for distribution, and no binary is involved at all:

* `extension build --browser=chromium-based` writes a family-generic artifact to `dist/chromium-based`.
* That artifact resolves `chromium:` manifest prefixes, reads `.env.chromium-based`, and sets `EXTENSION_BROWSER=chromium-based`.
* Ship that one package to users on Chrome, Brave, Edge, or any other Chromium fork.

Choose a named target (`chrome`, `edge`) when a store build needs vendor-specific manifest fields. Choose an engine target when one artifact should serve the whole family. See [What engine targets mean for `build`](/docs/commands/build#what-engine-targets-mean-for-build).

## Safari and other WebKit targets

In addition to the Chromium family and Firefox (Gecko engine), Extension.js can build your extension into a **Safari** app on macOS.

| Target | Usage |
| - | - |
| **Safari** | `npx extension build --browser=safari` |
| **WebKit-based** (engine alias) | `npx extension build --browser=webkit-based` |

Safari is a **dev and build target**: `build` and `dev` are supported, but `preview` and `start` are not. `dev` converts your extension, signs an app, opens it, streams logs, and reloads on save, after you enable the extension once in Safari Settings. It requires macOS with the full Xcode app. See [Building Safari extensions](/docs/browsers/safari) for the full workflow, requirements, and how to enable the extension in Safari.

## Multi-browser selection

You can run multiple named browsers in one command:

```bash theme={null}
npx extension dev --browser=chrome,firefox
```

Use comma-separated values to run multiple named targets in sequence (for example, `--browser=chrome,edge,firefox`).

## Constraints and behavior

* `chromium-based` requires `--chromium-binary` in commands that launch a browser (`dev`, `start`, `preview`); `build` needs no binary.
* `gecko-based` / `firefox-based` require `--gecko-binary` under the same conditions.
* Engine-based targets route to the same Chromium/Firefox runners with engine-aware behavior.
* As `build` targets, engine targets get their own `dist/<target>` folder, `.env.<target>` resolution, `EXTENSION_BROWSER` value, and manifest prefix. See [What engine targets mean for `build`](/docs/commands/build#what-engine-targets-mean-for-build).

## Firefox builds warn about Chromium-only APIs

From 4.1.18, a production build for a Gecko target scans the emitted bundle for two calls that addons-linter reports as `UNSUPPORTED_API`: `chrome.sidePanel.*` on any manifest version, and `chrome.action.*` on Manifest V2. Each hit prints a warning that names the file. The build still succeeds.

```plaintext theme={null}
background.ts uses chrome.sidePanel, which is Chromium only.
Firefox opens a sidebar through the sidebar_action manifest key on every manifest version, and addons-linter flags the call as UNSUPPORTED_API.
Move the call behind a build-time branch on import.meta.env.EXTENSION_PUBLIC_BROWSER so the Firefox bundle drops it.
```

```plaintext theme={null}
background.ts uses chrome.action, which Manifest V2 does not have on Firefox.
Firefox Manifest V2 exposes the toolbar button as browserAction, and addons-linter flags the call as UNSUPPORTED_API.
Use browserAction behind a build-time branch on import.meta.env.EXTENSION_PUBLIC_BROWSER, or declare Manifest V3 for Firefox with firefox:manifest_version.
```

The fix is a branch that the bundler resolves at build time, so the Firefox bundle drops the call entirely:

```js theme={null}
if (import.meta.env.EXTENSION_PUBLIC_BROWSER !== "firefox") {
  chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
}
```

A runtime guard such as `chrome.sidePanel?.setPanelBehavior()` still warns. addons-linter matches the static read of the namespace, not what runs, so only a build-time branch clears the finding. See [Environment variables](/docs/features/environment-variables) for the `EXTENSION_PUBLIC_BROWSER` value on each target.

## Best practices

* **Use named browsers for daily iteration**: `chrome`, `edge`, and `firefox` are the fastest path for regular testing.
* **Use engine-based mode intentionally**: Prefer `chromium-based` / `gecko-based` when validating custom binaries or shipping a family-generic build.
* **Keep profiles isolated per browser**: Reduce cross-browser state leakage while debugging.
* **Pair with browser-specific fields**: Use browser-prefixed manifest keys for true behavior differences.

## Next steps

* [Customize browser flags](/docs/browsers/browser-flags).
* [Customize browser preferences](/docs/browsers/browser-preferences).
* [Run other browsers from custom binaries](/docs/browsers/running-other-browsers).
* [Build Safari extensions on macOS](/docs/browsers/safari).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.