# Feedback
If you encounter incorrect, outdated, or confusing documentation on any page, submit feedback:
POST https://extension.js.org/feedback
```json
{
"path": "/current-page-path",
"feedback": "Description of the issue"
}
```
Only submit feedback when you have something specific and actionable to report.
# Announcing Extension.js 2.0.0-alpha
Source: https://extension.js.org/blog/announcing-2-0-0-alpha
Extension.js 2.0.0-alpha introduces 30+ starter templates, browser-specific manifest fields, and SWC-powered compilation for faster cross-browser builds.
Extension.js is all about making cross-browser extension development as easy as possible.
Since being [featured on HackerNews](https://hnrankings.info/40210627/), we've made a lot of internal improvements to make the package smaller, faster, and more developer-friendly.
To me, "very easy" means very low cognitive effort. It means something is familiar. Developers are familiar with copy/paste, and as a developer myself, it feels very easy if I can copy/paste code and have an instant response of what the code means without configuration. Even if the code is not understandable at first, a fast visual response is a clear indicator of how things are **expected** to work. That's the expectation of Extension.js v2: something very easy to work with.
**Version 2.0.0-alpha**
* 30+ templates for popular JavaScript frameworks, CSS pre-processors, and extension APIs. [View on GitHub](https://github.com/extension-js/examples/tree/main/examples).
* Support for adding browser-specific fields in manifest.json that apply only to the target browser, like `{chrome:service_worker: "sw.js"}`
* `extension.config.js` - For advanced users needing access to the compilation process. [See configuration docs](/docs/features/extension-configuration).
* Faster compilation thanks to [SWC](https://swc.rs/).
* A revamped documentation site (this one!).
* A new templates site (coming soon).
The alpha release is focused on bug fixes and testing all existing features while we finish the documentation and templates websites. Keep an eye on this blog or follow us on X for future updates.
For the next release, we are going to focus on making all the existing features stable enough to document and test.
Thanks for reading. Talk soon.
Cezar Augusto
Creator and Lead Developer, Extension.js
# Announcing Extension.js 2.0.0-beta
Source: https://extension.js.org/blog/announcing-2-0-0-beta
Extension.js 2.0.0-beta stabilizes alpha features and resolves browser compatibility issues across Firefox, Edge, and Chrome for reliable cross-browser builds.
Extension.js is now officially in beta.
This beta stabilizes the features introduced in the alpha release and fixes all known issues with browser compatibility.
**Version 2.0.0-beta**
* Fixed Sass Modules not working in the content script.
* Add support for Svelte templates.
* Add preview command.
* Add universal support for content\_scripts (via hard-reload and hot-reload).
* Fix all known browser issues for Firefox, Edge, and Chrome.
* Updated documentation (this one!)
The beta release is focused on making all existing features stable enough to document and test. New features are locked until the next stable release. The documentation is now up to date. This is the last beta release before the release candidate.
For the next release, we are going to focus on shaping Extension.js into a faster and smaller bundle that can work both locally and remotely (via `pnpm dlx`, `yarn dlx`, and of course `npx`).
Thanks for reading. Talk soon.
Cezar Augusto
Creator and Lead Developer, Extension.js
# Announcing Extension.js 3.0.0
Source: https://extension.js.org/blog/announcing-3-0-0
Extension.js 3.0.0 ships faster Rspack-powered builds, Firefox Manifest V3 support, and first-class TypeScript for cross-browser extension development.
Extension.js 3.0.0 is now available.
This release is focused on one thing: helping teams ship production browser extensions with less setup, clearer workflows, and better defaults across Chrome, Edge, and Firefox.
## What ships in v3
**Version 3.0.0**
* Stronger cross-browser workflows across Chrome, Edge, and Firefox with one command surface.
* Expanded template experience for modern stacks including TypeScript, React, Vue, Svelte, and Preact.
* Improved development update behavior with clearer HMR, hard-reload, and restart-required boundaries.
* Better production confidence workflows across troubleshooting, security checks, performance checks, CI templates, and Playwright E2E.
* More predictable project configuration through `extension.config.js` and browser-aware build output.
* A cleaner documentation and homepage experience designed for faster onboarding and lower cognitive load.
## The day-one flow is simpler
Use one path from project creation to production output:
1. `create` to scaffold.
2. `dev` to iterate quickly.
3. `build` to generate production artifacts.
4. `start` or `preview` to validate production behavior.
```bash theme={null}
npx extension@latest create my-extension --template=new-typescript
cd my-extension
pnpm dev
pnpm build
```
## Better for teams, not just demos
v3 is designed for real teams maintaining real extension codebases:
* Typed templates and framework-first baselines reduce setup drift.
* Browser-targeted builds and flags make compatibility checks explicit.
* Workflow docs now map directly to release confidence tasks.
* Security and performance guidance is integrated into standard delivery flow.
## Who this release is for
* Teams migrating from custom extension build scripts.
* Web developers moving from app development to extension development.
* Teams standardizing extension DX across multiple repos and contributors.
## Upgrade notes
If you are already using Extension.js:
* update the package to latest,
* prefer canonical template slugs (`new-react`, `new-typescript`, `new-vue`, etc.),
* keep your command flow aligned with `create` -> `dev` -> `build`.
## Thanks
Thanks to everyone building with Extension.js, reporting issues, and improving docs and templates with feedback.
v3 is a major step forward, and we are just getting started.
Cezar Augusto
Creator and Lead Developer, Extension.js
# Extension.js blog and release announcements
Source: https://extension.js.org/blog/index
Stay up to date with Extension.js release notes, feature announcements, and ecosystem updates for cross-browser extension development.
Follow the latest news and updates from the team at Extension.js! Here, you will find information about new releases, features, and more. For the latest updates, we are also available on [Twitter](https://twitter.com/extension_js) and [Discord](https://discord.gg/extensionjs).
## [Announcing v3.0.0](/blog/announcing-3-0-0)
> February 18, 2026
## [Announcing v2.0.0-beta](/blog/announcing-2-0-0-beta)
> October 18, 2024
## [Announcing v2.0.0-alpha](/blog/announcing-2-0-0-alpha)
> September 9, 2024
# AI access via MCP and llms.txt
Source: https://extension.js.org/docs/ai-access
Connect Extension.js documentation to your AI tools via the hosted MCP server, or pull structured summaries from the llms.txt endpoint.
You can access Extension.js documentation through two standard AI-accessible surfaces. This lets your editor and assistants answer questions grounded in the real docs instead of stale training data.
## Hosted MCP (Model Context Protocol) server
The docs site exposes a [Model Context Protocol](https://modelcontextprotocol.io) server at:
```
https://extensionjs.mintlify.app/mcp
```
Point any MCP-compatible client at this URL and it gains tools to search the docs, fetch specific pages, and answer questions from current content.
### One-click install
Each page has a contextual menu (top-right of the page) with install buttons for common clients — **Claude**, **Cursor**, **VS Code**, and **ChatGPT**. Use those for the shortest path.
### Manual install
```json Claude Desktop (claude_desktop_config.json) theme={null}
{
"mcpServers": {
"extensionjs": {
"url": "https://extensionjs.mintlify.app/mcp"
}
}
}
```
```json Cursor (.cursor/mcp.json) theme={null}
{
"mcpServers": {
"extensionjs": {
"url": "https://extensionjs.mintlify.app/mcp"
}
}
}
```
```jsonc VS Code (settings.json) theme={null}
"mcp.servers": {
"extensionjs": {
"url": "https://extensionjs.mintlify.app/mcp"
}
}
```
```bash Claude Code theme={null}
claude mcp add --transport http extensionjs https://extensionjs.mintlify.app/mcp
```
After installing, ask your assistant something like *"How do I configure browser-specific manifest fields in Extension.js?"* — it consults the MCP tools and cites docs pages.
## llms.txt
The documentation provides a static, machine-friendly index at:
* [`/llms.txt`](https://extensionjs.mintlify.app/llms.txt) — short index of all pages
* [`/llms-full.txt`](https://extensionjs.mintlify.app/llms-full.txt) — full-content bundle for ingestion
Use these when you want to feed the docs directly into a retrieval pipeline or summarize them in a custom agent. Extension.js regenerates them on every docs deploy.
## Per-page AI actions
The page-level contextual menu also offers:
* **Copy page** — copies the current page as Markdown for pasting into chats
* **View as Markdown** — opens the raw Markdown source in a new tab
* **Ask ChatGPT / Claude / Perplexity** — opens the respective assistant pre-loaded with the page as context
## Best practices
* Prefer the MCP server when you want the assistant to reason across multiple pages and keep answers current.
* Prefer `llms-full.txt` for offline tooling, evaluations, or custom RAG (retrieval-augmented generation) pipelines.
* Use the per-page actions for quick spot questions about the page you are already reading.
# Browser launch flags for debugging and automation
Source: https://extension.js.org/docs/browsers/browser-flags
Control Chrome, Firefox, and Edge launch behavior with browser flags for debugging, automation, and runtime experiments in Extension.js.
Control browser launch behavior for debugging, automation, and runtime
experiments.
Tune browser launch behavior without changing extension source code. Extension.js merges browser flags from your `extension.config.*` and applies them in `dev`, `preview`, and `start` flows.
## Template example
### `new-browser-flags`
See browser flags in action with a new-tab extension that configures launch behavior.
```bash npm theme={null}
npx extension@latest create my-extension --template=new-browser-flags
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --template=new-browser-flags
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --template=new-browser-flags
```
Repository: [extension-js/examples/new-browser-flags](https://github.com/extension-js/examples/tree/main/examples/new-browser-flags)
## How it works
Define flags in configuration:
* `browser..browserFlags`
* `commands.dev|start|preview.browserFlags`
* optional `excludeBrowserFlags` to remove defaults or user flags (runner-dependent behavior)
Merge precedence is browser defaults -> command defaults -> CLI-selected command context.
## Flag capabilities
| Config key | What it does |
| -------------------------------------- | -------------------------------------------------------- |
| `browser..browserFlags` | Sets default launch flags for a specific browser target. |
| `commands.dev.browserFlags` | Adds or overrides flags for `dev` runs. |
| `commands.start.browserFlags` | Adds or overrides flags for `start` runs. |
| `commands.preview.browserFlags` | Adds or overrides flags for `preview` runs. |
| `browser..excludeBrowserFlags` | Removes matching default or user flags for a target. |
| `commands..excludeBrowserFlags` | Removes flags in a command-specific context. |
### Example configuration
```js theme={null}
export default {
browser: {
chrome: {
browserFlags: ['--disable-web-security', '--auto-open-devtools-for-tabs'],
excludeBrowserFlags: ['--mute-audio']
},
firefox: {
browserFlags: ['--devtools', '--new-instance'],
excludeBrowserFlags: ['--devtools']
}
}
}
```
## Chromium vs Firefox behavior
* **Chromium family (`chrome`, `edge`, `chromium`, `chromium-based`)**
* Starts from an internal default flag set, then appends your `browserFlags`.
* `excludeBrowserFlags` removes matching default flags (exact-match filtering).
* Extension.js manages `--load-extension=...` and filters it out from user-provided flags.
* **Firefox/Gecko family (`firefox`, `gecko-based` / `firefox-based`)**
* Uses user-provided `browserFlags` (no large default flag bundle like Chromium).
* `excludeBrowserFlags` removes user flags by prefix matching.
## Supported targets and references
| Browser | Usage | More Information |
| -------------- | ---------------------------------------- | -------------------------------------------------------------------------------------- |
| Chrome | `extension dev --browser=chrome` | [Chrome Flags](https://peter.sh/experiments/chromium-command-line-switches/) |
| Edge | `extension dev --browser=edge` | [Edge Flags](https://docs.microsoft.com/en-us/deployedge/microsoft-edge-policies) |
| Firefox | `extension dev --browser=firefox` | [Firefox Flags](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options) |
| Chromium-based | `extension dev --browser=chromium-based` | [Chromium Flags](https://peter.sh/experiments/chromium-command-line-switches/) |
| Gecko-based | `extension dev --browser=gecko-based` | Firefox-based browsers share the same flags as Firefox. |
Use binary flags when needed for engine-based targets:
* `--chromium-binary=...`
* `--gecko-binary=...`
## Best practices
* **Add only necessary flags**: Minimize long flag lists to reduce flaky or non-portable setups.
* **Prefer `excludeBrowserFlags` over replacing defaults**: Remove only what conflicts with your workflow.
* **Do not pass `--load-extension` manually**: Extension.js manages extension loading flags internally.
* **Validate per browser family**: A flag working in Chromium may be invalid or ignored in Firefox.
## Next steps
* Learn more about [browser preferences](/docs/browsers/browser-preferences).
* Learn more about [browser profile](/docs/browsers/browser-profile).
# Firefox and Gecko runtime preferences setup
Source: https://extension.js.org/docs/browsers/browser-preferences
Set Firefox and Gecko runtime preferences for development without editing extension source. Configure homepage, devtools, and notification defaults.
Tune browser runtime behavior for development without changing your extension
source.
Get repeatable browser behavior during development (for example, homepage defaults, devtools settings, or notification behavior) without editing extension code.
Extension.js reads `preferences` from `extension.config.*` and applies them in browser launch flows where supported.
## How it works
Configure preferences in `extension.config.js` (or `.mjs` / `.cjs`) under:
* `browser..preferences`
* `commands.dev|start|preview.preferences`
Command-level values can override browser defaults.
## Preference capabilities
| Config key | What it does |
| ------------------------------ | ------------------------------------------------------- |
| `browser..preferences` | Sets default preferences for a specific browser target. |
| `commands.dev.preferences` | Sets or overrides preferences for `dev` runs. |
| `commands.start.preferences` | Sets or overrides preferences for `start` runs. |
| `commands.preview.preferences` | Sets or overrides preferences for `preview` runs. |
## Firefox and Gecko-based behavior
### Example configuration
```js theme={null}
export default {
browser: {
firefox: {
preferences: {
'browser.startup.homepage': 'https://developer.mozilla.org',
'devtools.theme': 'dark',
'dom.webnotifications.enabled': false
}
}
}
}
```
In Firefox/Gecko flows, Extension.js writes a `user.js` file into the active profile (managed or explicit profile) and merges:
* internal baseline preferences required for dev/runtime behavior
* your custom `preferences` values (your values win on key conflicts)
If you enable system profile mode (`EXTENSION_USE_SYSTEM_PROFILE=true`), Extension.js does not write a managed profile file.
## Chromium-family behavior
Chromium-family launches (`chrome`, `edge`, `chromium`, `chromium-based`) primarily use **browser flags**.
`preferences` can still exist in merged config objects, but flags and profile args control Chromium launch behavior, not a Firefox-style `user.js` preference file.
For Chromium customization, prefer:
* `browserFlags`
* `excludeBrowserFlags`
* `profile` / `persistProfile`
## Dark mode defaults
Extension.js injects dark-mode defaults unless you already define those keys:
* Chromium family: dark-mode launch flags
* Firefox/Gecko family: dark-mode preference keys (for UI + content color scheme)
Your explicit `preferences`/flags override these defaults.
## Interface example
```js theme={null}
export default {
commands: {
dev: {
browser: 'firefox',
preferences: {
'devtools.theme': 'dark'
}
}
}
}
```
### Example with custom profile
```js theme={null}
export default {
browser: {
firefox: {
profile: 'path/to/custom-profile',
preferences: {
'browser.startup.homepage': 'https://example.com'
}
}
}
}
```
## More detailed preference references
For a comprehensive list of available Firefox preferences, explore the [Firefox source code](https://searchfox.org/mozilla-central/source/). Mozilla defines many defaults in `all.js` or `firefox.js`.
## Best practices
* **Prefer browser-scoped preferences**: Keep Firefox/Gecko preference keys under browser-targeted config blocks.
* **Use command overrides for temporary experiments**: Put short-lived preference tweaks in `commands.dev`.
* **Keep profiles isolated**: Use separate profiles for reproducible debugging.
* **Use flags for Chromium tuning**: Treat flags as the primary configuration surface on Chromium-family targets.
## Next steps
* Learn more about [browser flags](/docs/browsers/browser-flags).
* Learn more about [browser profile](/docs/browsers/browser-profile).
# Browser profile management for isolated dev runs
Source: https://extension.js.org/docs/browsers/browser-profile
Manage browser state isolation during extension development with managed, persistent, or custom profile paths for Chrome and Firefox.
Control browser state isolation during development with managed, persistent, or custom profiles.
Keep browser sessions isolated or persistent based on your workflow. Extension.js launches browsers with profile-aware defaults so you can choose clean runs, reusable state, or an explicit local profile path.
## How it works
Extension.js selects the profile mode in this order:
1. Explicit `profile` path (if provided)
2. Managed profile mode (default)
* ephemeral by default
* persistent when `persistProfile: true`
3. System profile mode when `EXTENSION_USE_SYSTEM_PROFILE=true`
## Profile capabilities
| Config / option | What it does |
| ----------------------------------- | --------------------------------------------------------------- |
| `browser..profile` | Uses an explicit profile folder for that browser target. |
| `commands.dev.profile` | Uses an explicit profile only for `dev`. |
| `commands.start.profile` | Uses an explicit profile only for `start`. |
| `commands.preview.profile` | Uses an explicit profile only for `preview`. |
| `browser..persistProfile` | Reuses managed profile state between runs for a target. |
| `commands..persistProfile` | Reuses managed profile state in a command-specific context. |
| `--profile=/abs/path` | CLI override for explicit profile path. |
| `EXTENSION_USE_SYSTEM_PROFILE=true` | Uses the OS/browser system profile instead of managed profiles. |
## Profile modes
| Mode | How to enable | Typical use |
| ----------------------- | ----------------------------------------------- | --------------------------------------------- |
| Managed ephemeral | default | clean runs with isolated state |
| Managed persistent | `persistProfile: true` | iterative debugging with stable browser state |
| Explicit custom profile | `profile: "/abs/path"` or `--profile=/abs/path` | reuse an existing profile |
| System profile | `EXTENSION_USE_SYSTEM_PROFILE=true` | launch with OS/browser default profile |
Extension.js creates managed profiles under:
* `dist/extension-js/profiles/-profile/<...>`
Persistent mode uses:
* `dist/extension-js/profiles/-profile/dev`
## Configure in `extension.config.*`
```js theme={null}
export default {
browser: {
chrome: {
// Use your own profile folder:
profile: 'path/to/custom/profile'
},
firefox: {
// Keep a stable managed profile for repeated sessions:
persistProfile: true
}
}
}
```
You can also scope profile defaults by command:
```js theme={null}
export default {
commands: {
dev: {
persistProfile: true
}
}
}
```
## CLI usage
Use an explicit profile path directly:
```bash theme={null}
extension dev --browser=chrome --profile=/path/to/custom/profile
```
Works similarly with `start` and `preview`.
## Lifecycle notes
* Extension.js creates ephemeral managed profiles for each run.
* Extension.js reuses the persistent managed profile (`dev`) across runs.
* Extension.js removes old managed profile folders automatically (controlled by `EXTENSION_TMP_PROFILE_MAX_AGE_HOURS`).
## Best practices
* **Use managed ephemeral profiles for baseline testing**: Reduces hidden state and flaky reproductions.
* **Use `persistProfile` for long-lived debug sessions**: Keep auth/session/devtools state between runs.
* **Keep custom profiles per browser family**: Avoid cross-browser contamination.
* **Use system profile mode intentionally**: Useful for reproduction, but less isolated than managed profiles.
## Next steps
* Learn more about [browser preferences](/docs/browsers/browser-preferences).
* Learn more about [browser flags](/docs/browsers/browser-flags).
# Supported browsers for Extension.js development
Source: https://extension.js.org/docs/browsers/browsers-available
See which browsers Extension.js supports. Run and test extensions across Chrome, Edge, Firefox, and custom binaries from a single CLI workflow.
Run and test your extension across major browsers from one CLI workflow.
Validate one extension across Chrome, Edge, Firefox, and custom browser binaries from a single CLI.
## 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` |
| `chromium-based` | Running custom Chromium binaries (Brave, Arc, etc.) | `extension dev --browser=chromium-based --chromium-binary=/path/to/browser` |
| `gecko-based` | Running custom Firefox-family binaries | `extension dev --browser=gecko-based --gecko-binary=/path/to/browser` |
## 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`.
## 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` |
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.
## Safari and other WebKit targets
**Fully supported CLI targets** are the Chromium family (`chromium`, `chrome`, `edge`, and `chromium-based` with a custom binary) and Firefox (`firefox`, `gecko-based` / `firefox-based`). Extension.js documents and tests those `--browser` values end-to-end.
**Safari** is not a supported `--browser` value in the CLI today. You can still author extensions with shared web code and add Safari-specific manifest or build steps outside this workflow. Do not assume that npm package keywords imply a managed Safari runner.
## 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`.
* `gecko-based` / `firefox-based` require `--gecko-binary`.
* Engine-based targets route to the same Chromium/Firefox runners with engine-aware behavior.
## 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` only when validating custom binaries.
* **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).
# Browser targeting guide
Source: https://extension.js.org/docs/browsers/index
Choose and configure browser targets for Extension.js development. Covers Chrome, Firefox, and Edge targeting, profiles, flags, and custom binaries.
Run one extension codebase across Chrome, Edge, Firefox, and custom binaries
with explicit browser targeting workflows.
## What to read first
| Need | Read this |
| ------------------------------ | --------------------------------------------------------------- |
| Choose browser targets quickly | [Browsers available](/docs/browsers/browsers-available) |
| Customize launch behavior | [Browser flags](/docs/browsers/browser-flags) |
| Configure Firefox preferences | [Browser preferences](/docs/browsers/browser-preferences) |
| Control profile isolation | [Browser profile](/docs/browsers/browser-profile) |
| Run Brave or custom binaries | [Running other browsers](/docs/browsers/running-other-browsers) |
## Practical target strategy
1. Use named targets (`chrome`, `edge`, `firefox`) for daily checks.
2. Use comma-separated targets for release validation.
3. Use engine targets only when you need custom binaries.
4. Keep browser differences in browser-prefixed manifest fields.
## Next steps
* Learn manifest filtering in [Cross-browser compatibility](/docs/features/cross-browser-compatibility).
* Configure browser-specific keys in [Browser-specific manifest fields](/docs/features/browser-specific-fields).
## Video walkthrough
# Running other browsers from binary path
Source: https://extension.js.org/docs/browsers/running-other-browsers
Test extensions in Brave, Vivaldi, Waterfox, or other Chromium and Gecko browsers by providing an explicit binary path to Extension.js.
Run Chromium- and Gecko-family browsers beyond the default named targets by
providing explicit binary paths.
Test custom browser binaries (for example, Brave, Vivaldi, or Waterfox) from the same Extension.js workflow using binary flags and `extension.config.*` in `dev`, `start`, and `preview`.
## How it works
Use one of these flags:
* `--chromium-binary `
* `--gecko-binary ` (alias: `--firefox-binary `)
These binary flags override named browser selection at runner level.
## Binary capabilities
| Option / key | What it does |
| --------------------------------- | ------------------------------------------------- |
| `--chromium-binary ` | Launches a custom Chromium-family browser binary. |
| `--gecko-binary ` | Launches a custom Gecko-family browser binary. |
| `--firefox-binary ` | Alias of `--gecko-binary`. |
| `browser..chromiumBinary` | Sets default custom Chromium binary in config. |
| `browser..geckoBinary` | Sets default custom Gecko binary in config. |
| `commands..chromiumBinary` | Sets command-specific custom Chromium binary. |
| `commands..geckoBinary` | Sets command-specific custom Gecko binary. |
### CLI examples
```bash theme={null}
extension dev --browser=chromium-based --chromium-binary="/path/to/brave"
```
```bash theme={null}
extension dev --browser=firefox --gecko-binary="/path/to/firefox-developer-edition"
```
You can also use them with `start` and `preview`.
## Configure in `extension.config.*`
```js theme={null}
export default {
browser: {
'chromium-based': {
chromiumBinary: '/path/to/custom-chromium-browser'
},
'gecko-based': {
geckoBinary: '/path/to/custom-gecko-browser'
}
}
}
```
You can also place binary paths in command blocks:
```js theme={null}
export default {
commands: {
dev: {
chromiumBinary: '/path/to/custom-chromium-browser'
},
preview: {
geckoBinary: '/path/to/custom-gecko-browser'
}
}
}
```
## Target mapping behavior
Binary hints map to engine targets:
* `chromiumBinary` -> `chromium-based`
* `geckoBinary` / `firefoxBinary` -> `gecko-based`
If you provide both, Extension.js applies Chromium binary resolution first.
## Available browsers
Common browsers you can run with binary flags:
| **Browser Name** | **Type** | **CLI Flag** | **Official Website** |
| ----------------------------- | ---------------------- | ------------------- | --------------------------------------------------------- |
| **Brave** | Chromium-based browser | `--chromium-binary` | [brave.com](https://brave.com) |
| **Opera** | Chromium-based browser | `--chromium-binary` | [opera.com](https://www.opera.com) |
| **Vivaldi** | Chromium-based browser | `--chromium-binary` | [vivaldi.com](https://vivaldi.com) |
| **Waterfox** | Gecko-based browser | `--gecko-binary` | [waterfox.net](https://www.waterfox.net) |
| **Firefox Developer Edition** | Gecko-based browser | `--gecko-binary` | [firefox.com](https://www.mozilla.org/firefox/developer/) |
## Important constraints
* `chromium-based` requires a valid `chromiumBinary` path.
* `gecko-based` / `firefox-based` require a valid `geckoBinary` path.
* Invalid paths fail fast with a clear CLI/runtime error.
* `build` does not accept binary flags; binary-based launching applies to `dev`, `start`, and `preview`.
## Best practices
* **Pair binaries with explicit browser target**: Use `--browser=chromium-based` or `--browser=gecko-based` for predictable intent.
* **Use absolute paths**: Avoid shell-dependent path resolution issues.
* **Version-pin in CI runners**: Keep browser binary paths deterministic for automated checks.
* **Combine with profile/flags carefully**: Reuse the same profile and flag strategy used for named browser targets.
## Next steps
* Learn more about [browser preferences](/docs/browsers/browser-preferences).
* Learn more about [browser profile](/docs/browsers/browser-profile).
# Build command for production extension artifacts
Source: https://extension.js.org/docs/commands/build
Create production-ready extension artifacts for Chrome, Edge, or Firefox with the Extension.js build command. Supports multi-browser output and zip packaging.
Create production extension artifacts for one or more browser targets.
`build` compiles your extension in production mode and writes output to `dist/`.
For monorepo/submodule projects, see [Environment variables](/docs/features/environment-variables#how-it-works) for config-time env resolution (project root first, then workspace-root fallback).
## When to use `build`
* Preparing extension packages for Chrome Web Store, Edge Add-ons, or Firefox Add-ons.
* Running continuous integration (CI) jobs that produce deterministic release artifacts.
* Validating production bundle output and browser-target differences before submission.
## Build command capabilities
| Capability | What it gives you |
| ---------------------- | ------------------------------------------------------------- |
| Production compilation | Generate optimized extension artifacts per target |
| Multi-target output | Build multiple browser targets in one command |
| Packaging support | Create distribution zip artifacts with optional source bundle |
| CI-friendly behavior | Keep build outputs and naming predictable in automation |
## Usage
```bash npm theme={null}
extension build [project-path] [options]
```
```bash pnpm theme={null}
extension build [project-path] [options]
```
```bash yarn theme={null}
extension build [project-path] [options]
```
## Build output
After running `build`, Extension.js generates optimized files for the selected browser targets and writes output to `dist/` with one subfolder per target. Each folder contains bundled JavaScript, CSS, HTML, and required runtime assets.
**Example output structure:**
```plaintext theme={null}
dist/
├── chrome/
│ ├── manifest.json
│ ├── background/service_worker.js
│ ├── content_scripts/content-0.js
├── edge/
│ ├── manifest.json
│ ├── background/service_worker.js
│ ├── content_scripts/content-0.js
```
## Browser target matrix
| Target style | Examples | Notes |
| -------------- | ------------------------------------------------ | ---------------------------------- |
| Named targets | `chromium`, `chrome`, `edge`, `firefox` | Build one specific browser target |
| Engine targets | `chromium-based`, `gecko-based`, `firefox-based` | Useful for engine-family workflows |
| Multi-target | `chrome,firefox` | Comma-separated targets |
## Arguments and flags
| Flag | Alias | What it does | Default |
| ------------------------ | --------------- | ---------------------------------------------------------------------------------------------------- | ------------------------ |
| `[path]` | - | Builds a local extension project. | `process.cwd()` |
| `--browser ` | `-b` | Browser target (`chromium`, `chrome`, `edge`, `firefox`, engine aliases, or comma-separated values). | `chromium` |
| `--polyfill [boolean]` | - | Enables `browser.*` API compatibility polyfill for Chromium targets. | `false` |
| `--zip [boolean]` | - | Creates a packaged zip artifact. | `false` |
| `--zip-source [boolean]` | - | Includes source files in zip output. | `false` |
| `--zip-filename ` | - | Sets custom zip filename. | extension name + version |
| `--silent [boolean]` | - | Suppresses build logs. | `false` |
| `--extensions ` | - | 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 |
## Shared global options
Also supports [global flags](/docs/workflows/global-flags).
## Zip behavior
| Option | Effect | Typical use |
| ---------------- | --------------------------------------- | ------------------------------------ |
| `--zip` | Creates a packaged artifact zip | Store submission/manual distribution |
| `--zip-filename` | Sets custom zip name | CI naming conventions |
| `--zip-source` | Adds source archive alongside artifacts | Compliance/review pipelines |
## Examples
### Building with zip output and custom filename
```bash npm theme={null}
extension build ./my-extension --browser=edge,chrome --zip --zip-filename=my-extension.zip
```
```bash pnpm theme={null}
extension build ./my-extension --browser=edge,chrome --zip --zip-filename=my-extension.zip
```
```bash yarn theme={null}
extension build ./my-extension --browser=edge,chrome --zip --zip-filename=my-extension.zip
```
In this example, the build targets Edge and Chrome, zips the output, and saves it as `my-extension.zip`.
### Building with polyfill support
```bash npm theme={null}
extension build ./my-extension --browser=chrome,firefox --polyfill
```
```bash pnpm theme={null}
extension build ./my-extension --browser=chrome,firefox --polyfill
```
```bash yarn theme={null}
extension build ./my-extension --browser=chrome,firefox --polyfill
```
In this example, the build targets Chrome and Firefox and includes polyfill support where relevant.
### Building source and artifact zip
```bash npm theme={null}
extension build ./my-extension --zip --zip-source
```
```bash pnpm theme={null}
extension build ./my-extension --zip --zip-source
```
```bash yarn theme={null}
extension build ./my-extension --zip --zip-source
```
## Best practices
* **Check build logs:** Review logs for warnings and missing assets after each build.
* **Optimize your manifest:** Keep `manifest.json` compatible with every target browser.
* **Name artifacts intentionally:** Use `--zip-filename` for stable CI artifact naming.
* **Validate target output:** Check each `dist/` folder before publishing.
## Next steps
* Run existing build output with [`preview`](/docs/commands/preview).
* Build and launch in one command with [`start`](/docs/commands/start).
* Configure shared defaults in [`extension.config.js`](/docs/features/extension-configuration).
* Review config env loading behavior in [Environment variables](/docs/features/environment-variables#how-it-works).
* Review supported targets in [browsers available](/docs/browsers/browsers-available).
# Create command to scaffold extension projects
Source: https://extension.js.org/docs/commands/create
Scaffold a new browser extension project from an official template with one CLI command. Choose React, Vue, Svelte, TypeScript, or vanilla JS.
`create` scaffolds files, config, and starter scripts for the selected template and optionally installs dependencies.
## When to use `create`
* Start a new extension from scratch.
* Spin up multiple proof-of-concept ideas quickly.
* Standardize onboarding for teammates with consistent template defaults.
## Create command capabilities
| Capability | What it gives you |
| -------------------- | ---------------------------------------------------------- |
| Template scaffolding | Start from official templates with ready project structure |
| Dependency bootstrap | Install required packages automatically after scaffold |
| Path flexibility | Create by project name or explicit folder path |
| Fast onboarding | Move from empty folder to runnable extension quickly |
## Usage
```bash npm theme={null}
npx extension@latest create [options]
```
```bash pnpm theme={null}
pnpx extension@latest create [options]
```
```bash yarn theme={null}
yarn dlx extension@latest create [options]
```
## Arguments and flags
| Flag | Alias | What it does | Default |
| --------------------- | ----- | ---------------------------------------- | ------------ |
| `[path or name]` | - | Project folder/name to create. | required |
| `--template ` | `-t` | Selects template slug. | `javascript` |
| `--install [boolean]` | - | Installs dependencies after scaffolding. | `true` |
When you want the default JavaScript starter, omit `--template` entirely. Add `--template=` only for another stack from the [official examples](https://github.com/extension-js/examples/tree/main/examples). The slug `init` is an alias for the same default starter. Only use a literal slug named `default` if that folder actually exists in the examples repository.
## Shared global options
Also supports [global flags](/docs/workflows/global-flags).
## Example commands
```bash npm theme={null}
npx extension@latest create my-extension --template=new-react
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --template=new-react
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --template=new-react
```
```bash npm theme={null}
npx extension@latest create my-extension --install=false
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --install=false
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --install=false
```
## Available templates
For the full, continuously updated list of templates, browse the [examples
repository](https://github.com/extension-js/examples/tree/main/examples).
Minimal starter. Use when you want a clean baseline.
Typed starter with `tsconfig.json` preconfigured.
React UI wired for content scripts and popup views.
Vue UI with single-file component (SFC) support baked in.
## Best practices
* Start from a template that matches your UI/runtime needs to reduce setup drift.
* Keep the first run small, then add extra tooling after verifying baseline command flow.
# Dev command for watch mode and live reload
Source: https://extension.js.org/docs/commands/dev
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 restart-required diagnostics.
## 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
```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]
```
If you omit the path, Extension.js uses `process.cwd()`. 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 dev mode on the local copy.
## Arguments and flags
| Flag | Alias | What it does | Default |
| ------------------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------------ |
| `[path or url]` | - | Extension path or remote URL. | `process.cwd()` |
| `--browser ` | `-b` | Browser/engine target (`chromium`, `chrome`, `edge`, `firefox`, engine aliases, or comma-separated values). | `chromium` |
| `--profile ` | - | Browser profile path or boolean profile mode. | fresh profile |
| `--chromium-binary ` | - | [Custom Chromium-family binary path](/docs/browsers/running-other-browsers). | system default |
| `--gecko-binary ` | `--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 ` | - | Starting URL in launched browser. | unset |
| `--port ` | - | Dev server port. Use `0` for OS-assigned port. | `8080` |
| `--host ` | - | Host to bind the dev server to. Use `0.0.0.0` for Docker/devcontainers. | `127.0.0.1` |
| `--no-open` | - | Do not automatically open browser. | browser opens by default |
| `--no-browser` | - | Do not launch browser. | browser launch enabled |
| `--wait [boolean]` | - | Wait for `dist/extension-js//ready.json` and exit. | disabled |
| `--wait-timeout ` | - | Timeout for `--wait` mode. | `60000` |
| `--wait-format ` | - | Output format for wait results (`json` is machine-readable). | `pretty` |
| `--extensions ` | - | 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//ready.json`
* `dist/extension-js//events.ndjson`
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
### `--no-browser` and readiness synchronization
`--no-browser` only disables browser launch. It 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=` as the readiness gate
3. launch external browser automation only after `status: "ready"`
`--wait` is designed for 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 and the command runs in wait-only mode.
## Source inspection workflow
Use source options only with `dev`. Source inspection opens a target URL in the browser and prints the full, live HTML of the page **after** your content scripts run. This helps you verify what your extension actually changes on a page.
* `--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 ` | 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 ` | Comma-separated CSS selectors to probe. | unset |
| `--source-tree ` | 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 ` | Cap output payload size. | unset |
| `--source-redact ` | Redaction strategy for source output. | format-dependent |
| `--source-include-shadow ` | 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 ` | Minimum log level. | `off` |
| `--log-context ` | Context filter (`background`, `content`, `page`, `sidebar`, `popup`, `options`, `devtools`). | `all` |
| `--log-format ` | 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 ` | Filter log events by URL substring/regex. | unset |
| `--log-tab ` | Filter log events by tab ID. | unset |
## Shared global options
Also supports [global flags](/docs/workflows/global-flags).
## Examples
### Running a local extension
```bash npm theme={null}
extension dev ./my-extension
```
```bash pnpm theme={null}
extension dev ./my-extension
```
```bash yarn theme={null}
extension dev ./my-extension
```
### Running a remote extension from GitHub
Pass a GitHub tree URL as the argument to develop a remote extension locally:
```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
```
### Inspecting page HTML with source mode
```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
```
### Running in Firefox
```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
```
### Running in multiple browsers in sequence
```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
```
### Running inside Docker or a devcontainer
When you run inside Docker, devcontainers, or GitHub Codespaces, bind the dev server to `0.0.0.0` so the host machine can reach it:
```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
```
Combine with `--port 0` to let the OS choose an available port automatically:
```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
```
### Running in Brave as a custom binary
```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
```
## Best practices
* **Browser compatibility:** Test your extension in different browsers to ensure compatibility with your target browsers.
* **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 config env loading behavior in [Environment variables](/docs/features/environment-variables#how-it-works).
# Extension.js command guide
Source: https://extension.js.org/docs/commands/index
Reference for every Extension.js CLI command including dev, build, start, preview, create, install, and uninstall with usage examples.
Choose the right command for the current phase of extension development, from first scaffold to production validation.
## Command chooser
| Goal | Command | Example |
| ---------------------------------- | ----------- | --------------------------------------------------------------- |
| Scaffold a new project | `create` | `npx extension@latest create my-extension --template=new-react` |
| Develop with watch mode | `dev` | `extension dev --browser=firefox` |
| Build production artifacts | `build` | `extension build --browser=chrome,firefox --zip` |
| Build and launch production output | `start` | `extension start --browser=edge` |
| Launch existing build output only | `preview` | `extension preview --browser=chrome` |
| Install a managed browser runtime | `install` | `extension install chrome` |
| Remove a managed browser runtime | `uninstall` | `extension uninstall chrome` |
## Typical developer flow
1. `create` to bootstrap.
2. `dev` for iterative coding and reload cycles.
3. `build` for release artifacts.
4. `preview` or `start` for production-like validation.
5. `install` or `uninstall` when you need to manage Extension.js browser runtimes explicitly.
## Command references
* [Create](/docs/commands/create)
* [Dev](/docs/commands/dev)
* [Build](/docs/commands/build)
* [Start](/docs/commands/start)
* [Preview](/docs/commands/preview)
* [Install](/docs/commands/install)
* [Uninstall](/docs/commands/uninstall)
## Next steps
* Apply cross-command controls in [Global flags](/docs/workflows/global-flags).
* Keep defaults centralized in [Extension config](/docs/features/extension-configuration).
## Video walkthrough
# Install command for managed browser runtimes
Source: https://extension.js.org/docs/commands/install
Add a managed browser runtime to the Extension.js cache for deterministic builds. Supports Chrome for Testing, Chromium, Firefox, and Edge.
Use `install` to add a managed browser runtime into the Extension.js cache.
This is most useful when you want a consistent browser binary for `dev`, `build`, `start`, or `preview`. It supports Chrome for Testing, Chromium, Firefox, and Edge.
## When to use `install`
* You need a deterministic browser binary for CI, automation, or team-consistent local runs.
* You want Chrome for Testing instead of relying on whatever system Chrome is installed.
* You are setting up cross-browser testing with managed Firefox or Edge runtimes.
## Canonical usage
For a single browser, use the positional form:
```bash npm theme={null}
extension install
```
```bash pnpm theme={null}
extension install
```
```bash yarn theme={null}
extension install
```
Use `--browser` only when you need multiple targets, browser families, or `all`.
## Install command capabilities
| Capability | What it gives you |
| --------------------- | -------------------------------------------------------------------------- |
| Managed browser cache | Stable install location under the Extension.js browser cache |
| Deterministic runtime | Consistent binaries for repeatable local runs and automation |
| Cross-browser setup | One command flow for Chrome, Chromium, Edge, and Firefox |
| Path discovery | `--where` reveals the resolved cache root or browser-specific install path |
## Usage
```bash npm theme={null}
extension install [browser-name] [options]
```
```bash pnpm theme={null}
extension install [browser-name] [options]
```
```bash yarn theme={null}
extension install [browser-name] [options]
```
## Arguments and flags
| Flag / argument | What it does | Default |
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------- |
| `[browser-name]` | Install a single browser target such as `chrome`, `chromium`, `edge`, or `firefox` | `chromium` |
| `--browser ` | Override the positional browser name and support multi-target installs | unset |
| `--where` | Print the resolved managed cache root, or browser-specific install path | disabled |
## Examples
### Install Chrome for Testing
```bash theme={null}
extension install chrome
```
### Install multiple targets in one command
```bash theme={null}
extension install --browser chrome,firefox
```
### Show the managed install path for Chrome
```bash theme={null}
extension install chrome --where
```
## Cache locations
By default, Extension.js stores managed browsers in a stable per-user cache:
* macOS: `~/Library/Caches/extension.js/browsers`
* Linux: `~/.cache/extension.js/browsers` or `$XDG_CACHE_HOME/extension.js/browsers`
* Windows: `%LOCALAPPDATA%\extension.js\browsers`
You can override the cache root with `EXT_BROWSERS_CACHE_DIR`.
## Best practices
* **Use `install` in CI** to pin a deterministic browser binary instead of relying on whatever the runner provides.
* **Prefer `chrome`** over `chromium` for Chrome for Testing — it matches stable Chrome behavior more closely.
* **Use `--where`** to verify cache paths before scripting automation around managed browsers.
* `install` only manages browsers inside the Extension.js cache. It does not modify system browser installs.
## Behavior notes
* `chrome` installs Chrome for Testing rather than relying on the system Google Chrome app.
* `edge` may require a privileged interactive session on Linux.
## Next steps
* Remove managed browsers with [`uninstall`](/docs/commands/uninstall).
* Use managed browsers with [`dev`](/docs/commands/dev) and [`start`](/docs/commands/start).
* Learn about [running other browsers](/docs/browsers/running-other-browsers) with custom binary paths.
# Preview command to launch built extensions
Source: https://extension.js.org/docs/commands/preview
Launch an already-built extension for production-like manual testing without recompiling. Load unpacked output and run the browser launcher flow.
Launch an already-built extension output for production-like manual testing.
`preview` does not compile your project. It loads an existing unpacked extension root and runs the browser launcher flow.
## When to use `preview`
* Running existing build output without rebuilding.
* Comparing packaged behavior across browser targets quickly.
* Debugging runtime issues tied to production artifacts rather than dev/watch mode.
## Preview command capabilities
| Capability | What it gives you |
| ----------------------- | ------------------------------------------------------ |
| Build-output validation | Test real production artifacts without rebuilding |
| Browser-target checks | Run compiled output against selected browser targets |
| Runner control | Launch or skip browser runner based on workflow needs |
| Fast manual QA | Verify packaging-ready behavior quickly before release |
> `preview` is run-only. It prefers `dist/` when that output exists, but it can also run from another unpacked extension folder if that folder already contains a `manifest.json`.
## Usage
```bash npm theme={null}
extension preview [project-path] [options]
```
```bash pnpm theme={null}
extension preview [project-path] [options]
```
```bash yarn theme={null}
extension preview [project-path] [options]
```
If you omit the path, Extension.js uses `process.cwd()`.
## How `preview` chooses what to run
`preview` checks these locations in order:
1. `dist/` for the selected browser target
2. the provided project path or current working folder
3. `--output-path` when you explicitly point to an unpacked extension root
What matters is not whether a build happened in the same command. What matters is whether the chosen folder already contains an unpacked extension with a `manifest.json`.
## Arguments and flags
| Flag | Alias | What it does | Default |
| --------------------------- | ------------------ | ---------------------------------------------------------------------------- | ---------------------- |
| `[path]` | - | Preview built extension from a project path. | `process.cwd()` |
| `--browser ` | `-b` | Browser/engine target (`chromium`, `chrome`, `edge`, `firefox`, etc.). | `chromium` |
| `--profile ` | - | Browser profile path or boolean profile mode. | fresh profile |
| `--chromium-binary ` | - | [Custom Chromium-family binary path](/docs/browsers/running-other-browsers). | system default |
| `--gecko-binary ` | `--firefox-binary` | [Custom Gecko-family binary path](/docs/browsers/running-other-browsers). | system default |
| `--starting-url ` | - | Starting URL in launched browser. | unset |
| `--no-browser` | - | Skip browser launch. | browser launch enabled |
| `--port ` | - | Runner/devtools port when runner is enabled. Use `0` for OS-assigned port. | `8080` |
| `--host ` | - | Host to bind the dev server to. Use `0.0.0.0` for Docker/devcontainers. | `127.0.0.1` |
| `--extensions ` | - | Comma-separated companion extensions or store URLs. | unset |
| `--author` | `--author-mode` | Enable maintainer diagnostics. | disabled |
## Automation metadata
`preview` writes readiness metadata to:
* `dist/extension-js//ready.json`
For `--no-browser` flows, this provides deterministic command state:
* `starting` while command initializes
* `ready` when run-only validation is complete
* `error` when required output is missing or startup fails
* `runId` and `startedAt` for session correlation in scripts/agents
`preview` does not provide a `--wait` gate flag. For `preview` automation, consume `ready.json` directly.
## Logging flags
| Flag | What it does | Default |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------ |
| `--logs ` | Minimum log level. | `off` |
| `--log-context ` | Context filter (`background`, `content`, `page`, `sidebar`, `popup`, `options`, `devtools`). | `all` |
| `--log-format ` | 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 ` | Filter log events by URL substring/regex. | unset |
| `--log-tab ` | Filter log events by tab ID. | unset |
## Source inspection flags (not supported in `preview`)
`preview` accepts these flags in parsing, but exits with an error if you use them. Use `dev --source ...` instead.
| Flag | Support in `preview` |
| ----------------------------------------------- | ------------------------------------------- |
| `--source [url]` | not supported (command exits with guidance) |
| `--watch-source [boolean]` | not supported (command exits with guidance) |
| `--source-format ` | not supported (command exits with guidance) |
| `--source-summary [boolean]` | not supported (command exits with guidance) |
| `--source-meta [boolean]` | not supported (command exits with guidance) |
| `--source-probe ` | not supported (command exits with guidance) |
| `--source-tree ` | not supported (command exits with guidance) |
| `--source-console [boolean]` | not supported (command exits with guidance) |
| `--source-dom [boolean]` | not supported (command exits with guidance) |
| `--source-max-bytes ` | not supported (command exits with guidance) |
| `--source-redact ` | not supported (command exits with guidance) |
| `--source-include-shadow ` | not supported (command exits with guidance) |
| `--source-diff [boolean]` | not supported (command exits with guidance) |
## Shared global options
Also supports [global flags](/docs/workflows/global-flags).
## Examples
### Previewing a local extension
```bash npm theme={null}
extension preview ./my-extension
```
```bash pnpm theme={null}
extension preview ./my-extension
```
```bash yarn theme={null}
extension preview ./my-extension
```
### Previewing in Edge and Chrome
```bash npm theme={null}
extension preview ./my-extension --browser=edge,chrome
```
```bash pnpm theme={null}
extension preview ./my-extension --browser=edge,chrome
```
```bash yarn theme={null}
extension preview ./my-extension --browser=edge,chrome
```
### Preview without launching browser
```bash npm theme={null}
extension preview ./my-extension --no-browser
```
```bash pnpm theme={null}
extension preview ./my-extension --no-browser
```
```bash yarn theme={null}
extension preview ./my-extension --no-browser
```
## Behavior notes
* `preview` is run-only and never compiles the project.
* `preview` prefers existing build output (`dist/`) but can fall back to another unpacked extension root.
* `preview` does not run watch mode or hot module replacement (HMR).
* Source inspection flags are not supported in `preview`; use `dev` for that workflow.
* For scripts/agents, rely on `ready.json` and avoid parsing terminal output.
## Best practices
* Run `build` before `preview` when testing a fresh production artifact.
* Pass `--output-path` when your unpacked extension lives outside the default project output.
* Use `--browser` to verify behavior across targets before packaging.
## Next steps
* Build and launch in one step with [`start`](/docs/commands/start).
* Generate production artifacts with [`build`](/docs/commands/build).
* Configure shared defaults in [`extension.config.js`](/docs/features/extension-configuration).
* Review config env loading behavior in [Environment variables](/docs/features/environment-variables#how-it-works).
# Start command for build-and-launch workflow
Source: https://extension.js.org/docs/commands/start
Run a production build and immediately launch the extension in the browser with one command. Combines build and preview into a single step.
Use `start` when you want a production build and immediate browser launch in one command.
The `start` command runs a production build first, then launches the built extension using the same flow as the `preview` command.
## When to use `start`
* Manually validating production behavior right after compilation.
* Reproducing runtime differences between watch mode and production output.
* Running a production-like check locally without a separate `build` then `preview` step.
## Start command capabilities
| Capability | What it gives you |
| -------------------------- | --------------------------------------------------------- |
| Build + launch workflow | Run production compile and browser launch in one step |
| Target selection | Start directly in selected browser or engine target |
| Runner control | Skip browser launch when you only need build verification |
| Production-like validation | Check real compiled output instead of watch-mode state |
## How it differs from other commands
* `dev`: development server + hot module replacement (HMR)/watch loop
* `build`: production build only
* `preview`: launch an existing built extension without building
* `start`: `build` + `preview` in sequence
## Usage
```bash npm theme={null}
extension start [path-or-url] [options]
```
```bash pnpm theme={null}
extension start [path-or-url] [options]
```
```bash yarn theme={null}
extension start [path-or-url] [options]
```
If you omit the path, the command uses the current working folder.
## Arguments and flags
| Flag | Alias | What it does | Default |
| ------------------------------ | ------------------ | -------------------------------------------------------------------------- | ------------------------ |
| `[path or url]` | - | Extension path or remote URL. | `process.cwd()` |
| `--browser ` | - | Browser/engine target. | `chromium` |
| `--profile ` | - | Browser profile path or boolean profile mode. | fresh profile |
| `--chromium-binary ` | - | Custom Chromium-family binary path. | system default |
| `--gecko-binary ` | `--firefox-binary` | Custom Gecko-family binary path. | system default |
| `--polyfill [boolean]` | - | Enable `browser.*` API compatibility polyfill for Chromium targets. | `true` |
| `--starting-url ` | - | Starting URL in launched browser. | unset |
| `--no-browser` | - | Build but skip browser launch. | browser launch enabled |
| `--wait [boolean]` | - | Wait for `dist/extension-js//ready.json` and exit. | disabled |
| `--wait-timeout ` | - | Timeout for `--wait` mode. | `60000` |
| `--wait-format ` | - | Output format for wait results (`json` is machine-readable). | `pretty` |
| `--port ` | - | Runner/devtools port when runner is enabled. Use `0` for OS-assigned port. | `8080` |
| `--host ` | - | Host to bind the dev server to. Use `0.0.0.0` for Docker/devcontainers. | `127.0.0.1` |
| `--extensions ` | - | 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
`start` writes readiness metadata to:
* `dist/extension-js//ready.json`
This is useful for automation when using `--no-browser`:
* wait for `status: "ready"` before launching external runners
* handle `status: "error"` as a deterministic failure signal
* use `runId` and `startedAt` to correlate a specific runtime session
### `--no-browser` and readiness synchronization
`--no-browser` only disables browser launch. It does not block external runners until the production build finishes.
For production-oriented Playwright, continuous integration (CI), and AI workflows:
1. run `extension start --no-browser` as the producer process
2. run `extension start --wait --browser=` as the readiness gate
3. launch external browser automation only after `status: "ready"`
`--wait` exits non-zero on `error`/timeout and ignores stale contracts from dead processes (`pid` no longer alive).
If you pass both `--wait` and `--no-browser` in the same command invocation, `--wait` takes precedence and the command runs in wait-only mode.
## Logging flags
| Flag | What it does | Default |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------ |
| `--logs ` | Minimum log level. | `off` |
| `--log-context ` | Context filter (`background`, `content`, `page`, `sidebar`, `popup`, `options`, `devtools`). | `all` |
| `--log-format ` | 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 ` | Filter log events by URL substring/regex. | unset |
| `--log-tab ` | Filter log events by tab ID. | unset |
## Source inspection flags (not supported in `start`)
`start` accepts these flags in parsing, but exits with an error if you use them. Use `dev --source ...` instead.
| Flag | Support in `start` |
| ----------------------------------------------- | ------------------------------------------- |
| `--source [url]` | not supported (command exits with guidance) |
| `--watch-source [boolean]` | not supported (command exits with guidance) |
| `--source-format ` | not supported (command exits with guidance) |
| `--source-summary [boolean]` | not supported (command exits with guidance) |
| `--source-meta [boolean]` | not supported (command exits with guidance) |
| `--source-probe ` | not supported (command exits with guidance) |
| `--source-tree ` | not supported (command exits with guidance) |
| `--source-console [boolean]` | not supported (command exits with guidance) |
| `--source-dom [boolean]` | not supported (command exits with guidance) |
| `--source-max-bytes ` | not supported (command exits with guidance) |
| `--source-redact ` | not supported (command exits with guidance) |
| `--source-include-shadow ` | not supported (command exits with guidance) |
| `--source-diff [boolean]` | not supported (command exits with guidance) |
## Shared global options
Also supports [global flags](/docs/workflows/global-flags).
## Examples
### Start with default browser
```bash npm theme={null}
extension start
```
```bash pnpm theme={null}
extension start
```
```bash yarn theme={null}
extension start
```
### Start in Firefox
```bash npm theme={null}
extension start --browser firefox
```
```bash pnpm theme={null}
extension start --browser firefox
```
```bash yarn theme={null}
extension start --browser firefox
```
### Build and skip browser launch
```bash npm theme={null}
extension start --no-browser
```
```bash pnpm theme={null}
extension start --no-browser
```
```bash yarn theme={null}
extension start --no-browser
```
## Behavior notes
* `start` does not run a dev server and does not provide hot module replacement (HMR) or watch mode.
* `start` is production-mode oriented; use `dev` for iterative local development.
* `start` does not support source inspection options. Use `dev --source ...` for that workflow.
* For machine consumers, parse `dist/extension-js//ready.json` instead of terminal text.
## Next steps
* Iterate quickly with [`dev`](/docs/commands/dev).
* Launch existing build output with [`preview`](/docs/commands/preview).
# Uninstall command to remove managed browsers
Source: https://extension.js.org/docs/commands/uninstall
Remove managed browser runtimes from the Extension.js cache. Only affects browsers installed by Extension.js, not your system browsers.
Use `uninstall` to remove managed browser runtimes from the Extension.js cache.
This only removes browsers that Extension.js installed in its managed cache root. It does not touch system Chrome, system Edge, or any browser installed outside that cache.
## When to use `uninstall`
* You want to reclaim disk space from managed browsers you no longer need.
* You are resetting a managed browser install to force a fresh download on next `install`.
* You are cleaning up CI caches or switching browser targets.
## Canonical usage
For a single browser, use the positional form:
```bash npm theme={null}
extension uninstall
```
```bash pnpm theme={null}
extension uninstall
```
```bash yarn theme={null}
extension uninstall
```
Use `--all` to remove every managed browser target.
## Usage
```bash npm theme={null}
extension uninstall [browser-name] [options]
```
```bash pnpm theme={null}
extension uninstall [browser-name] [options]
```
```bash yarn theme={null}
extension uninstall [browser-name] [options]
```
## Arguments and flags
| Flag / argument | What it does | Default |
| ---------------- | ---------------------------------------------------------------------------------- | -------- |
| `[browser-name]` | Remove a single managed browser such as `chrome`, `chromium`, `edge`, or `firefox` | unset |
| `--all` | Remove all managed browser runtimes from the Extension.js cache | disabled |
| `--where` | Print the resolved cache root, or browser-specific managed paths | disabled |
## Examples
### Remove managed Chrome for Testing
```bash theme={null}
extension uninstall chrome
```
### Remove all managed browsers
```bash theme={null}
extension uninstall --all
```
### Show the managed uninstall path for Firefox
```bash theme={null}
extension uninstall firefox --where
```
## Best practices
* **Use `--all` in CI teardown** to clean up managed browsers after test runs.
* **Use `--where` first** to confirm what will be removed before scripting bulk uninstalls.
* `uninstall` is safe for system browsers — it only removes Extension.js-managed cache folders.
## Behavior notes
* If you set `EXT_BROWSERS_CACHE_DIR`, uninstall uses that custom cache root.
## Next steps
* Reinstall managed browsers with [`install`](/docs/commands/install).
* Review browser targeting with [`dev`](/docs/commands/dev) and [`start`](/docs/commands/start).
* Learn about [running other browsers](/docs/browsers/running-other-browsers) with custom binary paths.
# Browser-specific manifest fields
Source: https://extension.js.org/docs/features/browser-specific-fields
Define Chrome, Firefox, and Edge manifest values inline with browser prefixes. Extension.js emits only the fields matching each target at build time.
Avoid maintaining separate manifest files for every browser.
Extension.js lets you define browser-specific values inline with prefixes, then emits only the fields that match the active target at compile time.
## Why this matters
Browsers still differ in key manifest areas, like background configuration and vendor metadata. Prefixed fields let you keep one source `manifest.json` while producing browser-correct output for Chromium and Firefox families.
## How it works
Extension.js scans manifest keys and resolves prefixed entries for the selected browser:
* Chromium family targets (`chrome`, `edge`, `chromium-based`) resolve: `chromium:`, `chrome:`, `edge:`
* Gecko family targets (`firefox`, `gecko-based`) resolve: `firefox:`, `gecko:`
When a prefixed key matches the active target, Extension.js rewrites it to the unprefixed key in the emitted manifest.
### For Chromium-based browsers (Chrome, Edge, ...)
```json theme={null}
{
"chromium:background": {
"service_worker": "sw.js"
}
}
```
### For Firefox
```json theme={null}
{
"firefox:background": {
"scripts": ["sw.js"]
}
}
```
You can also target vendor-specific variants inside the same browser family:
```json theme={null}
{
"chrome:action": {
"default_title": "Chrome variant"
},
"edge:action": {
"default_title": "Edge variant"
}
}
```
This makes `service_worker` available only for Chromium-family outputs while keeping `background.scripts` for Firefox outputs.
Supported prefix map:
| Prefix | Included for target browser |
| ----------- | ----------------------------------------- |
| `chromium:` | `chrome`, `edge`, `chromium-based` |
| `chrome:` | `chrome`, `chromium-based` |
| `edge:` | `edge`, `chromium-based` |
| `firefox:` | `firefox`, `gecko-based`, `firefox-based` |
| `gecko:` | `firefox`, `gecko-based`, `firefox-based` |
This works for any manifest field at any level, including `permissions`, `content_scripts`, and `background`.
## Best practices
* **Keep shared defaults unprefixed**: Put common fields in regular manifest keys, then prefix only browser-specific differences.
* **Prefix only when behavior diverges**: Use browser prefixes when runtime requirements differ.
* **Build per target in CI (continuous integration)**: Generate and verify each browser output (`dist/`) to catch compatibility regressions early.
* **Validate with MDN**: Use [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) to confirm support before adding browser-only settings.
## Next steps
* Learn more about the [browsers available](/docs/browsers/browsers-available).
* Learn more about [cross-browser compatibility](/docs/features/cross-browser-compatibility).
# Cross-browser compatibility for Chrome and Firefox
Source: https://extension.js.org/docs/features/cross-browser-compatibility
Ship one extension to Chrome, Edge, and Firefox from a single project. Extension.js handles browser-specific build output for Chromium and Gecko targets.
Extension.js handles browser-specific setup during development and build, so you
can produce runtime-correct outputs for Chromium and Gecko targets from one
project.
Use one `manifest.json`, target specific browsers or custom binaries, and enable polyfills when needed for `browser.*` API compatibility in Chromium-family targets.
## Template example
### `action`
Try cross-browser compatibility with an action popup that runs in Chrome, Firefox, and Edge.
```bash npm theme={null}
npx extension@latest create my-extension --template=action
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --template=action
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --template=action
```
Repository: [extension-js/examples/action](https://github.com/extension-js/examples/tree/main/examples/action)
## How it works
### 1) Choose a browser target
Choose where you want to run your extension.
Use `--browser` for common targets, or pass a custom browser binary.
```bash npm theme={null}
extension dev --browser firefox
```
```bash pnpm theme={null}
extension dev --browser firefox
```
```bash yarn theme={null}
extension dev --browser firefox
```
```bash npm theme={null}
extension dev --chromium-binary /Applications/Brave\\ Browser.app/Contents/MacOS/Brave\\ Browser
```
```bash pnpm theme={null}
extension dev --chromium-binary /Applications/Brave\\ Browser.app/Contents/MacOS/Brave\\ Browser
```
```bash yarn theme={null}
extension dev --chromium-binary /Applications/Brave\\ Browser.app/Contents/MacOS/Brave\\ Browser
```
Use a binary path that matches your OS:
* **macOS**: `/Applications/Brave Browser.app/Contents/MacOS/Brave Browser`
* **Linux**: `/usr/bin/brave-browser` (or another Chromium-based browser binary)
* **Windows**: `"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"`
When you pass a binary, Extension.js maps it to a browser engine target:
* `chromium-based` for `--chromium-binary`
* `gecko-based` for `--gecko-binary`
### 2) Compile with browser-specific manifest filtering
During compilation, Extension.js includes only the manifest fields for your selected browser.
For example:
```json theme={null}
{
"chromium:background": {
"service_worker": "sw.js"
},
"firefox:background": {
"scripts": ["sw.js"]
}
}
```
For Chromium-family targets, Extension.js uses prefixes like `chromium:`, `chrome:`, and `edge:`.\
For Firefox-family targets, Extension.js uses `firefox:` and `gecko:`.
> Note: Browser-prefixed manifest fields are covered in detail in [Browser-specific manifest fields](/docs/features/browser-specific-fields).
### 3) Output per browser target
Extension.js writes each target to its own build folder:
* `dist/chrome`
* `dist/edge`
* `dist/firefox`
* `dist/chromium-based` (custom Chromium engines)
* `dist/gecko-based` (custom Gecko engines)
This keeps your builds organized and easier to ship in CI.
### 4) Optional `browser.*` polyfill for Chromium targets
If your code uses `browser.*`, enable `--polyfill` for Chromium-family targets:
```bash npm theme={null}
extension build --browser chrome --polyfill
```
```bash pnpm theme={null}
extension build --browser chrome --polyfill
```
```bash yarn theme={null}
extension build --browser chrome --polyfill
```
When enabled, Extension.js uses `webextension-polyfill` for non-Firefox targets.\
Extension.js skips this step for Firefox because Firefox already supports `browser.*` natively.
## Best practices
* **Keep one codebase**: Put browser differences in prefixed manifest fields when possible.
* **Build one target at a time**: Generate dedicated artifacts (`dist/`) for each browser in CI (continuous integration).
* **Use `--polyfill` when needed**: Enable it only when your code depends on `browser.*` in Chromium-family targets.
* **Check API support early**: Use [MDN WebExtensions docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) before relying on browser-specific APIs.
## Next steps
* Learn more about the [browsers available](/docs/browsers/browsers-available).
* Learn more about [browser-specific manifest fields](/docs/features/browser-specific-fields).
# Environment variables for cross-browser configs
Source: https://extension.js.org/docs/features/environment-variables
Manage API hosts, keys, and browser-specific values across environments. Extension.js loads env files for compiled bundles and for the config layer.
Use one extension codebase across browsers and environments without hardcoding values.
Manage API hosts, keys, and browser-specific values cleanly across environments. Extension.js uses **two different** environment loading paths. One handles **compiled extension bundles** (browser/mode aware). The other runs while loading **`extension.config.*`** in Node. Both paths matter, depending on where you read your variables.
## Template examples
### `new-env`
See environment variables in action with a new-tab extension that reads `EXTENSION_PUBLIC_*` values.
```bash npm theme={null}
npx extension@latest create my-extension --template=new-env
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --template=new-env
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --template=new-env
```
Repository: [extension-js/examples/new-env](https://github.com/extension-js/examples/tree/main/examples/new-env)
### `content-env`
Use environment variables inside content scripts injected into web pages.
```bash npm theme={null}
npx extension@latest create my-extension --template=content-env
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --template=content-env
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --template=content-env
```
Repository: [extension-js/examples/content-env](https://github.com/extension-js/examples/tree/main/examples/content-env)
## How it works
### Extension bundles (compile time)
When building your extension, the compiler picks **one** env file from your extension package folder by first match in this order:
1. `.env.[browser].[mode]` (for example `.env.chrome.development`)
2. `.env.[browser]`
3. `.env.[mode]`
4. `.env.local`
5. `.env`
6. `.env.example`
Extension.js **always merges** `.env.defaults` first when present, then the selected file’s variables, then **system** `process.env` (highest precedence for overlapping keys).
> Note: Selection is a **single** env file from the list above (plus `.env.defaults`), not a full cascade through every file.
If no matching file exists next to the project, Extension.js repeats the same search from the **nearest workspace root**. The workspace root is the folder containing `pnpm-workspace.yaml`, found by walking up from the project. This lets monorepo extensions share root-level env files for **bundle** injection.
**Monorepo constraint:** Workspace fallback only runs when a `pnpm-workspace.yaml` marker exists on an ancestor. Pure npm or Yarn workspaces that rely only on `package.json` `"workspaces"` do **not** get this automatic root lookup. Keep env files beside the extension package, or add a `pnpm-workspace.yaml` at the repo root.
### `extension.config.*` (Node, before your config runs)
`extension.config.js` / `.mjs` / `.cjs` runs in Node. Before Extension.js evaluates the file, it preloads a **small** set of files into `process.env` so the config can read them:
1. `.env.defaults` (merged when present)
2. Then the **first** file that exists among: `.env.development`, `.env.local`, `.env`
Extension.js does **not** use browser-scoped files such as `.env.chrome` or `.env.chrome.development` for this preload step. Instead, use plain `process.env` set by your shell or CI (continuous integration) pipeline. You can also rely on bundle-time env (described above) for injecting `EXTENSION_PUBLIC_*` values into extension code.
**Monorepo:** If none of those files exist in the extension package folder, the same preload runs from the nearest folder containing `pnpm-workspace.yaml` (same constraint as above).
This split exists because config loading is browser-agnostic at file-read time, while the bundler knows the active browser and mode.
## Built-in environment variables
Extension.js injects built-in variables at compile time, so browser and mode are always available in your extension code.
| Variable Name | Description |
| -------------------------- | ----------------------------------------------------------------------------------------- |
| `EXTENSION_PUBLIC_BROWSER` | The current browser target for your extension (for example, `chrome`, `firefox`, `edge`). |
| `EXTENSION_PUBLIC_MODE` | The mode in which your extension is running, such as `development` or `production`. |
| `EXTENSION_BROWSER` | Browser target (non-legacy alias). |
| `EXTENSION_MODE` | Build mode (non-legacy alias). |
| `BROWSER` | Short browser alias. |
| `MODE` | Short mode alias. |
| `NODE_ENV` | Node environment aligned to compiler mode (`development` / `production`). |
All built-ins above are available through both `process.env.*` and `import.meta.env.*`.
## Environment variable inventory
### Public/runtime variables (user-defined)
| Variable pattern | Purpose | Available in JS runtime | Notes |
| -------------------- | -------------------------------------------- | --------------------------------------- | ------------------------ |
| `EXTENSION_PUBLIC_*` | Expose user-defined values to extension code | Yes (`process.env` + `import.meta.env`) | Safe-to-ship values only |
### Static placeholder variables
| Variable pattern | Purpose | Available in JS runtime | Notes |
| -------------------------------------------------- | --------------------------------------------------- | ----------------------- | --------------------------------------- |
| `$EXTENSION_*` tokens in emitted `.html` / `.json` | Build-time placeholder replacement in static assets | Not as JS vars | Avoid using secrets in static templates |
### Built-in/alias variables
| Variable | Type | Notes |
| -------------------------- | -------------- | ------------------ |
| `EXTENSION_PUBLIC_BROWSER` | built-in | Browser target |
| `EXTENSION_PUBLIC_MODE` | built-in | Build mode |
| `EXTENSION_BROWSER` | built-in alias | Browser target |
| `EXTENSION_MODE` | built-in alias | Build mode |
| `BROWSER` | built-in alias | Short browser name |
| `MODE` | built-in alias | Short mode name |
| `NODE_ENV` | built-in | Compiler mode |
### CLI and dev-server operational variables
| Variable | Purpose | Typical usage |
| -------------------------- | -------------------------------------------- | ------------------------------------- |
| `EXTENSION_AUTO_EXIT_MS` | Auto-exit dev process after N ms | CI hard-stop control |
| `EXTENSION_FORCE_KILL_MS` | Force-kill timeout fallback | CI cleanup resilience |
| `EXTENSION_VERBOSE` | Verbose diagnostics in selected flows | Debugging CLI behavior |
| `EXTENSION_AUTHOR_MODE` | Maintainer/author diagnostics mode | Internal diagnostics and tooling |
| `EXTENSION_CLI_NO_BROWSER` | Disable browser launch from CLI (`1` to set) | Equivalent to `--no-browser` flag |
| `EXTENSION_DEV_NO_BROWSER` | Disable browser launch in dev server | Monorepo watch without browser spawns |
| `EXTENSION_DEV_DRY_RUN` | Skip dev server startup (return early) | Smoke-testing CLI wiring |
| `EXT_BROWSERS_CACHE_DIR` | Override managed browser cache folder | Custom CI cache paths |
### Telemetry control variables
| Variable | Purpose | Default |
| --------------------------------- | ----------------------------------------------- | ------- |
| `EXTENSION_TELEMETRY_DISABLED` | Disable telemetry entirely (`1` to set) | unset |
| `EXTENSION_TELEMETRY` | Back-compat disable (`0` to disable) | unset |
| `EXTENSION_TELEMETRY_SAMPLE_RATE` | Sampling rate for `command_executed` (0.0–1.0) | `0.2` |
| `EXTENSION_TELEMETRY_MAX_EVENTS` | Maximum events emitted per CLI process | `3` |
| `EXTENSION_TELEMETRY_DEBOUNCE_MS` | Dedup window for identical event tuples (ms) | `60000` |
| `EXTENSION_TELEMETRY_DEBUG` | Print telemetry payloads to stderr (`1` to set) | unset |
See [Telemetry and privacy](/docs/features/telemetry-and-privacy) for the full opt-out contract.
### Browser transport tuning variables
These variables override internal Chrome DevTools Protocol (CDP) and Remote Debugging Protocol (RDP) timeouts. Useful for slow CI (continuous integration) environments, Docker containers, or debugging flaky browser connections.
| Variable | Purpose | Default |
| ------------------------------------- | --------------------------------------- | ------- |
| `EXTENSION_CDP_COMMAND_TIMEOUT_MS` | CDP sendCommand timeout (ms) | `12000` |
| `EXTENSION_CDP_HTTP_TIMEOUT_MS` | CDP HTTP `/json` discovery timeout (ms) | `1200` |
| `EXTENSION_CDP_HEARTBEAT_INTERVAL_MS` | CDP WebSocket heartbeat interval (ms) | `30000` |
| `EXTENSION_RDP_EVAL_TIMEOUT_MS` | Firefox RDP evaluation timeout (ms) | `8000` |
| `EXTENSION_RDP_MAX_RETRIES` | Firefox RDP connect retry count | `150` |
| `EXTENSION_RDP_RETRY_INTERVAL_MS` | Firefox RDP connect retry interval (ms) | `1000` |
## Browser-specific environment files
The rules below apply to **compile-time / bundle** env selection (see [Extension bundles](#extension-bundles-compile-time) above). They do **not** apply to the narrow `extension.config.*` preload in Node.
Need different values per browser? Extension.js supports browser-scoped env files such as `.env.chrome` and `.env.firefox`. You can also combine browser and mode for a single build variant:
* `.env.chrome.development`: Extension.js applies this only when running the extension in Chrome during development mode.
* `.env.firefox.production`: Extension.js applies this only when building the extension for Firefox in production mode.
Priority order is:
* `.env.[browser].[mode]`
* `.env.[browser]`
* `.env.[mode]`
* `.env.local`
* `.env`
* `.env.example`
### Example files
```ini theme={null}
# .env.chrome.development
EXTENSION_PUBLIC_API_URL=https://api-dev.chrome.com
```
```ini theme={null}
# .env.firefox.production
EXTENSION_PUBLIC_API_URL=https://api.firefox.com
```
## Custom environment variables
You can define custom variables in env files at project root.\
Extension.js only injects variables prefixed with `EXTENSION_PUBLIC_` into JavaScript bundles (`process.env` / `import.meta.env`).
```ini theme={null}
# .env
EXTENSION_PUBLIC_API_KEY=your_api_key_here
EXTENSION_PUBLIC_SITE_URL=https://example.com
PRIVATE_KEY=abc123 # Not injected into JS bundles
```
**Important:** Extension.js does not inject variables without `EXTENSION_PUBLIC_` into JS bundles.\
However, placeholders in emitted `.json`/`.html` files can resolve `$EXTENSION_*` tokens, so avoid referencing secrets in static asset templates.
## Using environment variables
You can use environment variables in `manifest.json`, locales, HTML, and JavaScript/TypeScript files.
### 1. In `manifest.json`
`manifest.json` does not natively support environment variables, but Extension.js replaces supported placeholders during build. For example:
```json theme={null}
{
"name": "My Extension",
"version": "1.0",
"description": "This extension is connected to $EXTENSION_PUBLIC_API_KEY",
"background": {
"service_worker": "service_worker.js"
}
}
```
During compilation, Extension.js replaces `$EXTENSION_PUBLIC_API_KEY` with the resolved env value.
### 2. In locale files
You can also use placeholders in locale files when values should change by environment. For example:
```json theme={null}
{
"appName": {
"message": "My Extension - $EXTENSION_PUBLIC_SITE_URL"
},
"appDescription": {
"message": "Connected to API at $EXTENSION_PUBLIC_API_KEY"
}
}
```
When Extension.js emits assets, it replaces placeholders such as `$EXTENSION_PUBLIC_SITE_URL` with resolved values.
### 3. In HTML files
You can also use placeholders in static HTML files (for example, under `pages/`):
```html theme={null}
My Extension