# AGENTS
Source: https://extension.js.org/AGENTS
# AGENTS.md
Entry point for AI coding agents working on the Extension.js documentation site.
## What this repo is
The source for [extension.js.org](https://extension.js.org), the documentation site for [Extension.js](https://github.com/extension-js/extension.js), a cross-browser extension framework. Built with [Mintlify](https://mintlify.com).
## Layout
```
extension.js.org/
├── docs.json # Site config (navigation, theme, integrations)
├── docs/ # All documentation pages (MDX)
│ ├── getting-started/
│ ├── concepts/
│ ├── features/
│ ├── workflows/
│ ├── commands/
│ ├── browsers/
│ ├── languages-and-frameworks/
│ ├── integrations/
│ ├── implementation-guide/
│ └── ai-access.mdx # MCP and llms.txt entry points
├── blog/ # Blog posts
├── snippets/ # Reusable MDX components
├── images/ # Static assets referenced as /images/...
├── index.mdx # Site landing page
├── style.css # Custom styling on top of Mintlify theme
├── robots.txt
└── .agents/skills/ # Skill files for agents (mintlify, mintlify-api, mintlify-docs)
```
## Commands
```sh theme={null}
pnpm install # Install dependencies (Node >= 22.18.0, pnpm 10)
pnpm dev # Local preview at localhost:3000
pnpm build # Build the site (mint build)
pnpm broken-links # Check internal links
pnpm validate # Validate the docs build
pnpm check # lint + format + spellcheck
pnpm test # Run vitest tests
```
Always run `pnpm check` before opening a PR.
## Conventions
* **Page format:** MDX with YAML frontmatter. `title` is required, `description` is strongly recommended for SEO.
* **Filenames:** kebab-case (`getting-started.mdx`, `manifest-v3.mdx`).
* **Internal links:** root-relative without extension (`/concepts/manifest`, never `../concepts/manifest.mdx`).
* **Images:** store in `/images`, reference as `/images/example.png`.
* **Navigation:** every new page must be registered in `docs.json` or it will be hidden.
* **Voice:** second person, active voice, sentence-case headings. No marketing language ("powerful", "seamless"), no filler ("in order to", "it's important to note").
* **Punctuation:** no em dashes. Use commas, colons, or parentheses.
* **Verbs:** prefer "choose" over "pick" or "select" for selection/option language (see commit history for prior standardizations).
* **Code blocks:** always tag with a language.
## AI access surfaces
The site exposes two AI-accessible endpoints, documented in `docs/ai-access.mdx`:
* **MCP server:** `https://extensionjs.mintlify.app/mcp` (full search and page-fetch tools)
* **llms.txt:** `/llms.txt` (index) and `/llms-full.txt` (full bundle), auto-generated by Mintlify on every deploy
When pointing other agents at this docs site for grounded answers, prefer the MCP server.
## Skills available
`.agents/skills/` contains reusable skill files for agents working on the site:
* `mintlify/SKILL.md`: writing docs, components, navigation, deployment
* `mintlify-api/SKILL.md`: programmatic deployment and metadata via REST API
* `mintlify-docs/SKILL.md`: Mintlify-specific documentation patterns
Load these when working on related tasks rather than guessing Mintlify behavior.
## Workflow for changes
1. Read `docs.json` to understand navigation structure.
2. Search existing pages before creating new ones, prefer updates and links over duplication.
3. Read 2-3 nearby pages to match tone and structure.
4. Write content, update `docs.json` if a new page is added.
5. Run `pnpm check`, `pnpm broken-links`, `pnpm validate`.
6. Commit with a conventional prefix (`docs:`, `fix:`, `feat:`). Do not include AI co-author trailers.
## What not to do
* Do not introduce a hand-rolled `index.mdx` redesign without flagging the brand gap first, the current landing page is intentional.
* Do not rename `docs.json` to `mint.json`, `mint.json` is deprecated.
* Do not add files to the root `docs/` folder without a navigation entry.
* Do not use relative paths or file extensions in internal links.
# 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 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. As a developer myself, it feels very easy if I can copy/paste code and get an instant visual response of what it means, without configuration. Even if you don't understand the code 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 preprocessors, 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 use cases where you need access to the compilation process. [See configuration docs](/docs/features/extension-configuration).
* Faster compilation thanks to [SWC (Speedy Web Compiler)](https://swc.rs/).
* A revamped documentation site (this one!).
* A new templates site (coming soon).
The alpha release focuses on bug fixes and testing all existing features while we finish the documentation and template sites. Keep an eye on this blog or follow us on X for future updates.
For the next release, we will focus on making all 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 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**
* Fix 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.
* Update documentation (this one!)
The beta release focuses on making all existing features stable enough to document and test. We have locked new features 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 plan to make Extension.js a faster and smaller bundle. It will work both locally and remotely (via `pnpm dlx`, `yarn dlx`, and `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 focuses 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 hot module replacement (HMR), hard-reload, and restart-required boundaries.
* Better production confidence workflows across troubleshooting, security checks, performance checks, continuous integration (CI) templates, and Playwright end-to-end (E2E) testing.
* 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.
* The standard delivery flow now includes security and performance guidance.
## Who this release is for
* You are migrating from custom extension build scripts.
* You are moving from app development to extension development.
* You are standardizing extension developer experience 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 Extension.js updates. Find information about new releases, features, and more. You can also follow along 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.
Access Extension.js documentation through two standard AI-accessible surfaces. This lets your editor and assistants answer questions from current documentation rather than outdated 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?"* The assistant 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 Chrome, Firefox, and Edge
Source: https://extension.js.org/docs/browsers/browser-flags
Use firefox://flags, chrome://flags, and browser launch flags in Extension.js. Control Chrome, Firefox, and Edge runtime behavior during extension development.
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.
## Does Firefox have `firefox://flags`?
Firefox does not use `firefox://flags` the way Chromium browsers use `chrome://flags` or `edge://flags`. Firefox exposes runtime toggles through `about:config` (preferences) and accepts launch flags from the command line.
If you searched for `firefox://flags`, `firefox //flags`, `browser://flags`, `mozilla://flags`, or `about flags firefox`, you are probably trying to change browser behavior during extension development. In Extension.js, do that in two places:
* Use [`browserFlags`](/docs/browsers/browser-flags) for launch-time flags Extension.js passes to the browser binary.
* Use [Firefox preferences](/docs/browsers/browser-preferences) for repeatable Gecko runtime behavior that would otherwise live in `about:config`.
Both work for Chrome, Edge, and Firefox extension development from the same Extension.js project.
## Template examples
### `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
```
```bash bun theme={null}
bunx extension@latest create my-extension --template=new-browser-flags
```
```bash bun theme={null}
bunx 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
Configure flags in `extension.config.*`:
* `browser..browserFlags`
* `commands.dev|start|preview.browserFlags`
* Optional `excludeBrowserFlags` to remove defaults or user flags (behavior depends on whether you target Chromium or Firefox).
Override order: 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.
### Default Chromium flags
Extension.js applies these flags automatically when launching Chromium-family browsers. Use `excludeBrowserFlags` to remove any you do not need.
| Flag | Purpose |
| ---------------------------------------------------------- | ----------------------------------------------------------------------- |
| `--no-first-run` | Disable first run experience |
| `--disable-client-side-phishing-detection` | Disable phishing detection |
| `--disable-sync` | Disable sync to avoid account prompts |
| `--disable-component-extensions-with-background-pages` | Disable built-in extensions not affected by `--disable-extensions` |
| `--disable-default-apps` | Disable installation of default apps |
| `--disable-features=InterestFeedContentSuggestions` | Disable Discover feed on new tab page (NTP) |
| `--disable-features=Translate` | Disable Chrome translation |
| `--hide-scrollbars` | Hide scrollbars from screenshots |
| `--mute-audio` | Mute any audio |
| `--no-default-browser-check` | Disable default browser check prompt |
| `--ash-no-nudges` | Avoid user education nudges |
| `--disable-search-engine-choice-screen` | Disable search engine choice screen |
| `--disable-features=MediaRoute` | Disable Chrome Media Router background networking |
| `--use-mock-keychain` | Use mock keychain on Mac to prevent blocking dialogs |
| `--disable-background-networking` | Disable background network services |
| `--disable-breakpad` | Disable crashdump collection |
| `--disable-component-update` | Disable component updates |
| `--disable-domain-reliability` | Disable domain reliability monitoring |
| `--no-pings` | Disable hyperlink auditing pings |
| `--enable-features=SidePanelUpdates` | Ensure side panel is visible |
| `--disable-features=DisableLoadExtensionCommandLineSwitch` | Allow `--load-extension` at the command line |
| `--enable-unsafe-extension-debugging` | Allow Chrome DevTools Protocol (CDP) extension management (Chrome 126+) |
| `--silent-debugger-extension-api` | Suppress the "X is debugging this browser" infobar |
In Docker, continuous integration (CI), or containerized environments,
Extension.js also applies `--no-sandbox` and `--disable-setuid-sandbox`
automatically.
## 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).
* Choose a target in [Browsers available](/docs/browsers/browsers-available).
* Wire flags through [`extension.config.*`](/docs/features/extension-configuration).
# 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.
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 during Firefox and Gecko browser launches.
## 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
| Preference 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 development and 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 configuration objects. However, flags and profile options control Chromium launch behavior. Chromium does not use 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 configuration 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. Choose clean runs, reusable state, or an explicit local profile path.
## How it works
Extension.js chooses the profile mode in this order:
1. Explicit `profile` path (if provided)
2. Managed profile mode (default)
* temporary (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 automatically removes old managed profile folders. Set `EXTENSION_TMP_PROFILE_MAX_AGE_HOURS` to control the maximum age.
## 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.
Use this page when you need to test a Chrome extension, Firefox extension, or Edge extension from the same Extension.js project.
## Choose the right target
| Target | Use when | Example |
| ---------------- | --------------------------------------------------- | --------------------------------------------------------------------------- |
| `chromium` | Fast default local development | `extension dev --browser=chromium` |
| `chrome` | Validating Chrome-specific behavior | `extension dev --browser=chrome` |
| `edge` | Validating Edge distribution behavior | `extension dev --browser=edge` |
| `firefox` | Validating Gecko compatibility and APIs | `extension dev --browser=firefox` |
| `chrome,firefox` | Release checks across both major engines | `extension build --browser=chrome,firefox` |
| `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` |
| `safari` | Building a Safari app on macOS (Xcode required) | `extension build --browser=safari` |
## How it works
Use `--browser` to choose a target in `dev`, `start`, `preview`, and `build`.
If you do not specify a browser, the CLI defaults to `chromium`.
`safari` (and its `webkit-based` alias) is the exception: it's an **alpha**, macOS-only build target supported by `build` and `dev` only — not `preview` or `start`. See [Building Safari extensions](/docs/browsers/safari).
## 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
In addition to the Chromium family and Firefox (Gecko engine), Extension.js can build your extension into a **Safari** app on macOS.
| Target | Usage |
| ------------------------------- | -------------------------------------------- |
| **Safari** | `npx extension build --browser=safari` |
| **WebKit-based** (engine alias) | `npx extension build --browser=webkit-based` |
Safari is a **build target**: `build` and `dev` are supported, but `preview` and `start` are not (Safari extensions can't be auto-loaded into a live browser). It requires macOS with the full Xcode app. See [Building Safari extensions](/docs/browsers/safari) for the full workflow, requirements, and how to enable the extension in Safari.
## Multi-browser selection
You can run multiple named browsers in one command:
```bash theme={null}
npx extension dev --browser=chrome,firefox
```
Use comma-separated values to run multiple named targets in sequence (for example, `--browser=chrome,edge,firefox`).
## Constraints and behavior
* `chromium-based` requires `--chromium-binary`.
* `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).
* [Build Safari extensions on macOS](/docs/browsers/safari).
# Browser targeting guide for Chrome, Firefox, and Edge
Source: https://extension.js.org/docs/browsers/index
Configure browser targets for Chrome, Firefox, and Edge extension development. Covers targeting, profiles, flags, and custom binaries in Extension.js.
Run one extension codebase across Chrome, Edge, Firefox, and custom binaries
with explicit browser targeting workflows.
## Chrome extension development
For Chrome extension development, target `chrome` (or `chromium` for the default Chromium binary): `extension dev --browser=chrome`. Extension.js loads the extension into a fresh, isolated profile and applies a sane set of [Chrome launch flags](/docs/browsers/browser-flags) so reload behavior is predictable.
## Firefox extension development
For Firefox extension development, target `firefox`: `extension dev --browser=firefox`. Manifest V3 background scripts compile to a non-persistent `scripts` array (Firefox does not use `service_worker`), and Firefox preferences replace the Chromium concept of `chrome://flags`. See [Browser preferences](/docs/browsers/browser-preferences).
## Edge extension development
For Edge extension development, target `edge`: `extension dev --browser=edge`. Edge shares the Chromium engine, so most flags and APIs match Chrome, but Extension.js still emits a separate `dist/edge` artifact for distribution.
## Cross-browser extension development
For cross-browser extension development, run multiple targets in one command (`extension dev --browser=chrome,firefox`) and keep browser differences in [browser-prefixed manifest fields](/docs/features/browser-specific-fields). One project, one `manifest.json`, distinct outputs per browser. See [Cross-browser compatibility](/docs/features/cross-browser-compatibility) for the full pipeline.
## 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. Use 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 which browser binary Extension.js launches, regardless of the named browser target you selected.
## 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](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. You can use binary-based launching only with `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 continuous integration (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).
# Building Safari extensions with Extension.js
Source: https://extension.js.org/docs/browsers/safari
Build your web extension into a Safari app on macOS with Extension.js. Covers requirements, the dev and build workflow, and known limitations.
Package your existing web extension into a native Safari app on macOS — no separate Xcode project to maintain by hand.
Safari support is **alpha**. The build → convert → `xcodebuild` → open pipeline works, but expect rough edges and breaking changes. Chrome, Edge, and Firefox are the stable targets.
Use `--browser=safari` to turn the same extension you ship to Chrome and Firefox into a Safari App Extension. Extension.js bundles your code, runs Apple's `safari-web-extension-converter`, compiles the generated app with `xcodebuild`, and walks you through enabling it.
## Requirements
Safari is **macOS-only** and needs the **full Xcode app** — not just the Command Line Tools. The converter (`safari-web-extension-converter`) and `xcodebuild` ship inside `Xcode.app`.
```bash theme={null}
# Install Xcode from the Mac App Store, then point the toolchain at it:
sudo xcode-select --switch /Applications/Xcode.app
xcodebuild -runFirstLaunch
```
If Xcode is missing, `extension build`/`dev --browser=safari` fail fast — before bundling — with guidance instead of a late, confusing error.
## What it produces
`extension build --browser=safari` creates, next to your project:
| Path | What it is |
| --------------------- | ------------------------------------------------------------- |
| `dist/safari` | The bundled web extension (manifest, scripts, assets). |
| `dist/safari-xcode` | The generated Xcode project (app + Safari extension targets). |
| `…/Release/.app` | The compiled, ad-hoc–signed app that hosts your extension. |
The pipeline runs end to end: **bundle → convert → `xcodebuild` → open the app → guided enable**.
```bash theme={null}
npx extension build --browser=safari
```
The app name and bundle identifier are derived from your manifest `name` (for example, `React Sidebar Example` → bundle id `dev.extensionjs.React-Sidebar-Example`). The project targets macOS by default.
## Enabling the extension in Safari
Local builds are **ad-hoc signed** (no Apple Developer account required), so Safari needs you to opt in once. After the build opens the app, Extension.js prints these steps and confirms when macOS has registered the extension:
1. Safari ▸ Settings ▸ Advanced ▸ check **"Show features for web developers"**.
2. Safari ▸ Develop ▸ **Allow Unsigned Extensions** (this resets every time Safari restarts).
3. Safari ▸ Settings ▸ Extensions ▸ turn on your extension.
"Allow Unsigned Extensions" resets each time you launch Safari. Re-enable it after restarting Safari during development. A signed build (Apple Developer ID) avoids this step and is part of the distribution workflow.
## Developing with `dev`
`extension dev --browser=safari` runs a watch loop:
```bash theme={null}
npx extension dev --browser=safari
```
* **First compile** — full package: convert, build, open the app, and print the enable steps.
* **On every save** — incremental `xcodebuild` resync (typically a couple of seconds) that updates the app's resources from the freshly rebuilt `dist/safari`.
Safari has no live-reload channel like Chromium or Firefox, so after a rebuild **refresh the page (or toggle the extension)** in Safari to pick up changes. The Xcode project is generated once and reused, so any customizations you make in Xcode (entitlements, capabilities) are preserved across rebuilds. Delete `dist/safari-xcode` to regenerate it from scratch.
## Engine target
`safari` has an engine alias, **`webkit-based`**, that parallels `chromium-based` and `gecko-based`:
```bash theme={null}
npx extension build --browser=webkit-based
```
## Command support
| Command | Safari support |
| --------- | ----------------------------------------------------------------------------- |
| `build` | ✅ Builds and packages the Safari app. |
| `dev` | ✅ Watch + incremental rebuild (refresh in Safari to apply). |
| `preview` | ❌ Not supported — Safari extensions can't be auto-loaded into a live browser. |
| `start` | ❌ Not supported — same reason as `preview`. |
`preview` and `start` exist to launch your extension in a running browser. Safari requires the manual, security-gated enable step above, so those commands point you to `build` instead.
## Limitations
* **macOS only.** Building a Safari app requires macOS with the full Xcode app.
* **No live reload.** Rebuilds are fast, but you refresh in Safari to apply them.
* **Manual one-time enable.** Allowing unsigned extensions and toggling the extension on are Safari security controls and cannot be automated.
* **Local builds are ad-hoc signed.** Distribution signing, notarization, and App Store submission are a separate step beyond this workflow.
* **macOS target only.** iOS app generation is not produced by this workflow today.
## Best practices
* **Build other targets normally**: Safari is additive — keep iterating in `chromium`/`firefox` and run `--browser=safari` when you want to validate Safari.
* **Use browser-specific fields** for true behavioral differences via browser-prefixed manifest keys.
* **Keep the generated project** unless you need a clean slate — regenerating discards Xcode-side customizations.
## Next steps
* See all [supported browsers](/docs/browsers/browsers-available).
* Use [browser-specific manifest fields](/docs/features/browser-specific-fields).
* Review [multi-platform builds](/docs/features/multi-platform-builds).
# 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 and zip output.
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 configuration-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 repeatable production 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]
```
```bash bun theme={null}
extension build [project-path] [options]
```
```bash bun theme={null}
extension build [project-path] [options]
```
## Build output
After running `build`, Extension.js generates optimized files for the selected browser targets. Output goes to `dist/` with one subfolder per target. Each folder contains bundled JavaScript, CSS, HTML, and required runtime assets.
For TypeScript projects, `build` also regenerates the `extension-env.d.ts` ambient type declarations (the same file [`dev`](/docs/commands/dev) writes), so a CI `tsc --noEmit` stays clean whether or not you ran `dev` first. JavaScript-only projects skip this step.
**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 ` | - | Browser/engine 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` |
| `--mode ` | - | Bundler mode override (`development`, `production`, or `none`). Also sets `NODE_ENV`. | `production` |
| `--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).
## Mode override
`--mode` overrides the bundler mode and `NODE_ENV` for the build. Accepts `development`, `production`, or `none`. Use it to mirror Vite/webpack workflows where you need a non-production bundle for staging or debugging.
```bash npm theme={null}
extension build ./my-extension --mode development
```
```bash pnpm theme={null}
extension build ./my-extension --mode development
```
```bash yarn theme={null}
extension build ./my-extension --mode development
```
```bash bun theme={null}
extension build ./my-extension --mode development
```
```bash bun theme={null}
extension build ./my-extension --mode development
```
Invalid values exit with an error; the default remains `production`.
## 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
```
```bash bun theme={null}
extension build ./my-extension --browser=edge,chrome --zip --zip-filename=my-extension.zip
```
```bash bun 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
```
```bash bun theme={null}
extension build ./my-extension --browser=chrome,firefox --polyfill
```
```bash bun 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
```
```bash bun theme={null}
extension build ./my-extension --zip --zip-source
```
```bash bun 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 configuration 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, configuration, 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 your teammates with consistent template defaults.
## Create command capabilities
| Capability | What it gives you |
| -------------------- | ----------------------------------------------------------- |
| Template scaffolding | Start with official templates and a ready project structure |
| Dependency install | Optionally install required packages 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]
```
```bash bun theme={null}
bunx extension@latest create [options]
```
```bash bun theme={null}
bunx extension@latest create [options]
```
## Arguments and flags
| Flag | Alias | What it does | Default |
| --------------------- | ----- | ---------------------------------------- | ------------ |
| `[path or name]` | - | Project folder/name to create. | required |
| `--template ` | `-t` | Sets the template slug. | `javascript` |
| `--install [boolean]` | - | Installs dependencies after scaffolding. | `false` |
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 bun theme={null}
bunx extension@latest create my-extension --template=new-react
```
```bash bun theme={null}
bunx extension@latest create my-extension --template=new-react
```
```bash npm theme={null}
npx extension@latest create my-extension --install
```
```bash pnpm theme={null}
pnpx extension@latest create my-extension --install
```
```bash yarn theme={null}
yarn dlx extension@latest create my-extension --install
```
```bash bun theme={null}
bunx extension@latest create my-extension --install
```
```bash bun theme={null}
bunx extension@latest create my-extension --install
```
## 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 a full restart when the change requires it.
## When to use `dev`
* Building features and validating changes in real time.
* Debugging extension behavior in one or more browser targets.
* Running source-inspection workflows with `--source`.
Use `build` for production artifacts, `start` for production build + launch, and `preview` to run existing build output only.
If your extension lives inside a monorepo/submodule, review how `extension.config.*` loads env files (including workspace-root fallback): [Environment variables](/docs/features/environment-variables#how-it-works).
## Dev command capabilities
| Capability | What it gives you |
| ---------------------- | ----------------------------------------------------- |
| Watch mode iteration | Tight edit → rebuild → validate loop while coding |
| Browser-target control | Explicit cross-browser validation per command |
| Profile-aware runs | Reliable fresh or persisted profiles by workflow need |
| Source inspection mode | Structured output and diagnostics with source flags |
## Usage
```bash npm theme={null}
extension dev [path-or-url] [options]
```
```bash pnpm theme={null}
extension dev [path-or-url] [options]
```
```bash yarn theme={null}
extension dev [path-or-url] [options]
```
```bash bun theme={null}
extension dev [path-or-url] [options]
```
```bash bun theme={null}
extension dev [path-or-url] [options]
```
If you omit the path, Extension.js uses the current working folder. You can also pass a **GitHub tree URL** (for example, `https://github.com/user/repo/tree/main/path`). Extension.js downloads the repository and runs development mode on the local copy.
## Most-used flags
These cover the 80% case. Skip to the [full reference](#arguments-and-flags) for the rest.
| Flag | What it does | Default |
| ---------------------- | ------------------------------------------------------------------------ | ---------- |
| `--browser ` | Target Chrome, Edge, Firefox, or comma-separated list. | `chromium` |
| `--polyfill` | Bridge `browser.*` API to Chromium targets for Firefox-flavored sources. | `false` |
| `--port ` | Dev server port. Use `0` for OS-assigned. | `8080` |
| `--starting-url ` | Open this URL when the browser launches. | unset |
| `--no-reload` | Skip auto-reload. Use when you want a clean dev bundle. | reload on |
## Arguments and flags
| Flag | Alias | What it does | Default |
| ------------------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------- | --------------------------- |
| `[path or url]` | - | Extension path or remote URL. | `process.cwd()` |
| `--browser ` | `-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/dev containers. | `127.0.0.1` |
| `--no-open` | - | Do not automatically launch browser. | browser launches by default |
| `--no-browser` | - | Do not launch browser. | browser launch enabled |
| `--no-reload` | - | Skip the content-script reload runtime and on-rebuild reload dispatch. Reload tabs manually to see changes. | reload runtime enabled |
| `--wait [boolean]` | - | Wait for `dist/extension-js//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` (newline-delimited JSON)
For automation (Playwright, continuous integration (CI), AI agents), prefer these files over terminal log parsing.
Treat `ready.json` as the readiness contract:
* `status: "starting"` while booting
* `status: "ready"` when runtime is ready
* `status: "error"` for startup/compile failures
* `runId` uniquely identifies a runtime session
* `startedAt` marks the runtime session start timestamp
### `--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` targets a second process (or CI step) and exits non-zero on `error`/timeout.
When `--wait` sees a stale `ready.json` from a dead process (`pid` no longer alive), it keeps waiting for a live producer.
If you pass both `--wait` and `--no-browser` in the same command invocation, `--wait` takes precedence. The command runs in wait-only mode.
### `--no-reload` for a clean dev bundle
`--no-reload` skips the content-script reinjection wrapper and the on-rebuild reload dispatch. The dev `dist` stays close to a production bundle and an open tab is not disturbed when files change. Reload the extension or page yourself to pick up changes.
`--no-reload` is only supported on `extension dev`. Passing it to `start`, `preview`, or `build` exits with an error. Internally it sets `EXTENSION_NO_RELOAD=true` so the develop process can read it from outside the CLI argv.
## Source inspection workflow
Use source options only with `dev`. Source inspection opens a target URL in the browser and prints the full, live HTML **after** your content scripts run. Use this to verify what your extension actually changes.
* `--source [url]` to inspect a page (defaults to `--starting-url` or `https://example.com`)
* `--watch-source` / `--no-watch-source` to re-print HTML on each rebuild
`start` and `preview` do not support source-inspection flags.
### Source flags
| Flag | What it does | Default behavior |
| ----------------------------------------------- | ------------------------------------------ | ---------------------------------- |
| `--source [url]` | Enable source inspection flow. | disabled |
| `--watch-source [boolean]` | Re-print source output on watched updates. | `true` when `--source` is enabled |
| `--source-format ` | 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).
## Monorepo and workspace roots
You can point `dev` (and `build`) at the **root of a monorepo** instead of the extension package itself. Extension.js detects the workspace root and auto-resolves the extension package inside it:
```bash theme={null}
extension dev . # run from the monorepo root
```
When exactly one extension package is found, Extension.js resolves it and prints:
```text theme={null}
Workspace root detected — resolved extension package: packages/my-extension
```
When several candidates exist, it lists them so you can point at the one you mean:
```bash theme={null}
extension dev packages/my-extension
```
## Examples
### Running a local extension
```bash npm theme={null}
extension dev ./my-extension
```
```bash pnpm theme={null}
extension dev ./my-extension
```
```bash yarn theme={null}
extension dev ./my-extension
```
```bash bun theme={null}
extension dev ./my-extension
```
```bash bun 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
```
```bash bun theme={null}
extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
```
```bash bun theme={null}
extension dev https://github.com/nicedoc/browserless/tree/main/packages/screencast
```
### 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
```
```bash bun theme={null}
extension dev ./my-extension --source https://example.com
```
```bash bun 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
```
```bash bun theme={null}
extension dev ./my-extension --browser firefox
```
```bash bun 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
```
```bash bun theme={null}
extension dev ./my-extension --browser=chrome,firefox
```
```bash bun theme={null}
extension dev ./my-extension --browser=chrome,firefox
```
### Running inside Docker or a dev container
When you run inside Docker, dev containers, or GitHub Codespaces, bind the dev server to `0.0.0.0` so the host machine can reach it:
```bash npm theme={null}
extension dev ./my-extension --host 0.0.0.0
```
```bash pnpm theme={null}
extension dev ./my-extension --host 0.0.0.0
```
```bash yarn theme={null}
extension dev ./my-extension --host 0.0.0.0
```
```bash bun theme={null}
extension dev ./my-extension --host 0.0.0.0
```
```bash bun 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
```
```bash bun theme={null}
extension dev ./my-extension --host 0.0.0.0 --port 0
```
```bash bun theme={null}
extension dev ./my-extension --host 0.0.0.0 --port 0
```
### 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
```
```bash bun theme={null}
extension dev ./my-extension --chromium-binary /path/to/brave
```
```bash bun theme={null}
extension dev ./my-extension --chromium-binary /path/to/brave
```
## Best practices
* **Browser compatibility:** Test your extension in different browsers to verify it works on every target.
* **Polyfilling:** If Firefox or a Gecko-based browser is also a target, use `--polyfill`. This flag enables `browser.*` API compatibility in Chromium-based browsers.
* **Source inspection workflows:** Use `dev` (not `start`/`preview`) when you need source inspection options.
* **Automation reliability:** Treat `dev` as the watch-mode companion (`--no-browser` + `dev --wait`). Treat `start` as the production companion (`--no-browser` + `start --wait`). Use `--wait-format=json` for scripts and CI automation.
## Next steps
* Build production artifacts with [`build`](/docs/commands/build).
* Validate production launch flow with [`start`](/docs/commands/start).
* Review browser targeting with [Browser-specific manifest fields](/docs/features/browser-specific-fields).
* Configure shared defaults in [`extension.config.js`](/docs/features/extension-configuration).
* Review configuration env loading behavior in [Environment variables](/docs/features/environment-variables#how-it-works).
# 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 scaffold.
2. `dev` for iterative coding and reload cycles.
3. `build` for production 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)
The `telemetry` command (`enable` / `disable` / `status`) manages anonymous telemetry consent — see [Telemetry and privacy](/docs/features/telemetry-and-privacy).
## Next steps
* Apply cross-command controls in [Global flags](/docs/workflows/global-flags).
* Keep defaults centralized in [Extension configuration](/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 consistent, repeatable browser binary for continuous integration (CI), automation, or team-consistent local runs.
* You want Chrome for Testing instead of relying on whatever Chrome version your system has 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
```
```bash bun theme={null}
extension install
```
```bash bun 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 |
| Repeatable 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]
```
```bash bun theme={null}
extension install [browser-name] [options]
```
```bash bun 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 consistent 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. You can also point it at another unpacked extension folder that 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]
```
```bash bun theme={null}
extension preview [project-path] [options]
```
```bash bun theme={null}
extension preview [project-path] [options]
```
If you omit the path, Extension.js uses the current working folder.
## 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.
The folder needs to contain an unpacked extension with a `manifest.json`. It does not matter whether a build ran in the same command.
## Arguments and flags
| Flag | Alias | What it does | Default |
| --------------------------- | ------------------ | ---------------------------------------------------------------------------- | ---------------------- |
| `[path]` | - | Preview built extension from a project path. | `process.cwd()` |
| `--browser ` | - | 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/dev containers. | `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`)
The CLI detects these flags and exits with an error before parsing. 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
```
```bash bun theme={null}
extension preview ./my-extension
```
```bash bun 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
```
```bash bun theme={null}
extension preview ./my-extension --browser=edge,chrome
```
```bash bun theme={null}
extension preview ./my-extension --browser=edge,chrome
```
### Preview without launching the 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
```
```bash bun theme={null}
extension preview ./my-extension --no-browser
```
```bash bun 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).
* `preview` does not support source inspection flags; 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 the project path argument 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 configuration 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`: dev server + hot module replacement (HMR)/watch mode
* `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]
```
```bash bun theme={null}
extension start [path-or-url] [options]
```
```bash bun 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/dev containers. | `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 invocation, `--wait` takes precedence. 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`)
The CLI detects these flags and exits with an error before parsing. 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
```
```bash bun theme={null}
extension start
```
```bash bun 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
```
```bash bun theme={null}
extension start --browser firefox
```
```bash bun 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
```
```bash bun theme={null}
extension start --no-browser
```
```bash bun 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 continuous integration (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
```
```bash bun theme={null}
extension uninstall
```
```bash bun 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]
```
```bash bun theme={null}
extension uninstall [browser-name] [options]
```
```bash bun 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 |
| `--browser ` | Explicit flag form of the browser argument | 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.
# Extension.js vs WXT
Source: https://extension.js.org/docs/compare/extension-js-vs-wxt
Side-by-side comparison of Extension.js and WXT for building cross-browser extensions. CLI ergonomics, manifest model, framework support, and reload behavior.
Extension.js and [WXT](https://wxt.dev) are both modern frameworks for building cross-browser extensions. They overlap in mission and differ in philosophy. This page is a factual comparison so you can choose based on your project, not marketing copy.
## At a glance
| Dimension | Extension.js | WXT |
| -------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Bundler | [Rspack](/docs/features/rspack-configuration) (Rust-based) | Vite (Rolldown migration in progress) |
| Manifest | One `manifest.json`, browser-prefixed keys filtered at compile time | `wxt.config.ts` generates `manifest.json` per build |
| Entrypoints | Files referenced from `manifest.json` | File-system convention under `entrypoints/` |
| Browser targets | Chrome, Edge, Firefox, Chromium, Gecko, custom binaries | Chrome, Edge, Firefox, Safari (community), custom binaries |
| Manifest V3 | Default | Default |
| Manifest V2 | Not supported as a primary target | Supported via `manifestVersion: 2` |
| Reload model | HMR for popup/options/devtools, targeted reload for content scripts and SW | HMR for popup/options/devtools, targeted reload for content scripts |
| `browser.*` polyfill | `--polyfill` flag | Auto-polyfilled `browser` namespace |
| Templates | React, Preact, Vue, Svelte, TypeScript, JavaScript, init | React, Vue, Svelte, Solid, Preact, vanilla |
| TypeScript | First-class | First-class |
| AI surfaces | Hosted MCP server + `llms.txt` ([details](/docs/ai-access)) | `llms.txt` available |
## Mental model
**Extension.js stays close to the platform.** You author a `manifest.json` and reference real files. The CLI compiles, filters per browser, and ships. If you already understand how a browser extension is structured, the framework gets out of your way.
**WXT abstracts the platform.** Entrypoints are inferred from a directory layout (`entrypoints/popup/`, `entrypoints/content.ts`), and the manifest is generated from your config and source. If you prefer convention over configuration, WXT does more for you per file you write.
Neither approach is universally better. The choice depends on whether you want to **see** your manifest or **declare** your manifest.
## CLI surface
### Extension.js
```bash theme={null}
extension dev --browser=chrome,firefox
extension build --browser=chrome,firefox --zip
extension dev https://github.com/user/repo/tree/main/path
```
The argument can also be a remote GitHub URL or a ZIP archive, useful for spinning up a sample without cloning. See [Get started immediately](/docs/getting-started/immediately).
### WXT
```bash theme={null}
wxt
wxt build
wxt build -b firefox
wxt zip
```
WXT splits dev/build/zip into separate commands. Extension.js consolidates packaging into `build --zip`.
## Cross-browser strategy
Both frameworks ship one codebase to multiple browsers, with different mechanics:
* **Extension.js** uses [browser-prefixed manifest fields](/docs/features/browser-specific-fields) (`chrome:`, `firefox:`, `gecko:`, etc.) inside one `manifest.json`. The unprefixed keys apply everywhere; prefixed keys land only in matching builds.
* **WXT** computes the manifest from `wxt.config.ts` and per-target overrides. Browser differences live in TypeScript config rather than in the manifest itself.
If your team includes designers or PMs who read `manifest.json`, prefixed keys keep that file as the source of truth. If your team wants extension config alongside other TypeScript config, WXT's approach reads more naturally.
## Migration paths
If you are already on WXT and looking to evaluate Extension.js, the typical migration touches three things:
1. Move `entrypoints/` content back to flat files referenced from a hand-authored `manifest.json`.
2. Replace `wxt.config.ts` with [`extension.config.js`](/docs/features/extension-configuration) for build defaults.
3. Replace `wxt`/`wxt build` scripts with `extension dev`/`extension build`.
Most React, Vue, and Svelte source files copy over without changes.
## When to choose Extension.js
* You want the manifest as the source of truth, not generated.
* You want one CLI argument to dev a remote sample (`extension dev `).
* You want first-class hosted MCP for AI tooling on the docs.
* You want Rspack's Rust-speed compile times for large extensions.
## When to choose WXT
* You prefer file-system convention over an explicit manifest.
* You need Manifest V2 as a primary target.
* You want auto-polyfilled `browser.*` everywhere by default.
## See also
* [Cross-browser compatibility](/docs/features/cross-browser-compatibility)
* [Browser-specific manifest fields](/docs/features/browser-specific-fields)
* [Templates](/docs/getting-started/templates)
# JavaScript and TypeScript files in browser extensions
Source: https://extension.js.org/docs/concepts/javascript-typescript-browser-extension-files
Learn how .js, .ts, .tsx, manifest.json, background scripts, and content scripts work in Chrome, Firefox, and Edge extensions built with Extension.js.
Browser extensions use JavaScript, TypeScript, HTML, CSS, and a `manifest.json` to define behavior, UI, permissions, and browser integration. This page covers the file extensions you will see in a browser extension project and which ones land where.
If you are looking for "extension" as in a software add-on for Chrome or Firefox, start at [What is a browser extension?](/docs/concepts/what-is-a-browser-extension) instead.
## JavaScript file extensions: `.js` and `.mjs`
Browser extensions accept the same JavaScript file extensions the rest of the web platform uses:
| File extension | What it means |
| -------------- | ----------------------------------------------------------------------------------------- |
| `.js` | Plain JavaScript. Treated as ES module or classic script depending on how it is loaded. |
| `.mjs` | ES module. Useful when you want to force module semantics, especially in service workers. |
| `.cjs` | CommonJS. Rare in extension source code; sometimes appears in `extension.config.cjs`. |
In Manifest V3, the background `service_worker` runs as a module when `manifest.json` includes `"type": "module"` in the `background` block. See [Manifest V3 troubleshooting](/docs/concepts/manifest-v3) for the details.
## TypeScript file extensions: `.ts` and `.tsx`
TypeScript works as a first-class source language in Extension.js:
| File extension | What it means |
| -------------- | ------------------------------------------------------- |
| `.ts` | TypeScript without JSX. |
| `.tsx` | TypeScript with JSX (React, Preact). |
| `.d.ts` | Type declaration file. Not emitted to the build output. |
You do not need to write a `tsconfig.json` from scratch. Extension.js ships sensible defaults. Types for `chrome.*`, `browser.*`, `import.meta.env`, and the public env keys come from `@types/chrome` and Extension.js's own ambient types. See [TypeScript](/docs/languages-and-frameworks/typescript).
## React file extensions: `.jsx` and `.tsx`
React in a browser extension uses the standard JSX file extensions:
| File extension | What it means |
| -------------- | -------------------- |
| `.jsx` | JavaScript with JSX. |
| `.tsx` | TypeScript with JSX. |
React works inside extension pages (popup, options, side panel, new-tab) and inside content scripts injected into web pages. See [React](/docs/languages-and-frameworks/react) for setup and shadow-DOM patterns.
## Browser extension files: `manifest.json`, background, content scripts, pages
Beyond JavaScript and TypeScript source, a browser extension folder usually contains:
| File or folder | Purpose |
| ---------------- | ----------------------------------------------------------------- |
| `manifest.json` | Declares name, version, permissions, entry points, and metadata. |
| Background entry | Long-lived event handler. Manifest V3 service worker on Chromium. |
| Content scripts | Code injected into web pages matching a URL pattern. |
| Extension pages | Popup, options, side panel, or new-tab HTML. |
| Locales | `_locales//messages.json` for translated strings. |
| Icons and assets | Toolbar icons, web-accessible resources, fonts, images. |
Extension.js compiles your `.ts`, `.tsx`, `.jsx`, `.vue`, `.svelte`, `.css`, `.less`, `.scss`, and `.module.*` source down to that on-disk layout. It produces a separate folder per browser target (`dist/chrome`, `dist/firefox`, `dist/edge`).
## How Extension.js compiles JavaScript and TypeScript extensions
When you run `extension dev` or `extension build`, Extension.js:
1. Reads `manifest.json` and finds every entry point (background, content scripts, popup, options, side panel, new-tab, web-accessible HTML).
2. Resolves source files referenced from those entries, including imports across `.ts`, `.tsx`, `.jsx`, `.vue`, `.svelte`, and stylesheet types.
3. Compiles through Rspack with extension-aware defaults: code splitting where it helps, no chunking where the browser refuses it (service workers, content scripts).
4. Emits the result into `dist/` with a manifest filtered for that target.
You write source in any of the file extensions above. Extension.js handles the bundling, polyfills, reload-on-save loop, and per-browser packaging.
## Next steps
* Try [`extension create`](/docs/commands/create) to scaffold a TypeScript or React extension.
* Read [TypeScript](/docs/languages-and-frameworks/typescript) and [React](/docs/languages-and-frameworks/react).
* Read [Manifest V3 troubleshooting](/docs/concepts/manifest-v3) for service worker and module rules.
* Build with [`extension build`](/docs/commands/build).
# Manifest V3 troubleshooting for browser extensions
Source: https://extension.js.org/docs/concepts/manifest-v3
Fix common Manifest V3 issues with service workers, web_accessible_resources, host_permissions, and browser-specific behavior in Chrome and Firefox.
Manifest V3 (MV3) replaced background pages with service workers, tightened content security policy, and reshaped how extensions declare network and host access. Most day-to-day pain points share one root cause: MV3 assumes ephemeral, event-driven background code. Chrome and Firefox also interpret a few keys differently.
This page collects the issues that come up most often when building MV3 extensions, with the exact fix and what Extension.js handles for you.
## Manifest V3 background `service_worker` with `type: "module"`
Chromium uses a service worker for the MV3 background. To import ES modules from it, the manifest needs `type: "module"`:
```json theme={null}
{
"manifest_version": 3,
"background": {
"service_worker": "service_worker.js",
"type": "module"
}
}
```
Without `"type": "module"`, `import` statements fail at registration with no clear error in the extension console. With it, you can write modern ES module syntax in the worker file.
Extension.js sets `type: "module"` automatically when your background entry uses ES module syntax. It also compiles `.ts`/`.tsx` workers to a single bundled output so module resolution still works in production.
## Why `background.js` behaves differently in Manifest V3
In Manifest V2, `background.js` ran in a persistent background page with a DOM. In Manifest V3 on Chromium, the background runs as a service worker:
* No DOM. `window`, `document`, `XMLHttpRequest`, and `localStorage` are gone. Use `fetch` and `chrome.storage`.
* The worker can be **terminated at any time** when idle and woken on the next event. Do not store state in module-scope variables; persist it in `chrome.storage`.
* Top-level `await` is allowed, but long initialization will not keep the worker alive on its own.
* Register event listeners **synchronously** at the top of the file, not inside async callbacks. Listeners registered inside async callbacks will not fire on the wake-up event that loaded the worker.
Firefox keeps non-persistent event pages instead of service workers. Extension.js routes Chromium to `service_worker` and Firefox to `scripts` from the same source, so the same background entry compiles correctly per target.
## `web_accessible_resources` in Manifest V3
Manifest V3 changed `web_accessible_resources` from a flat array of files to a list of `{ resources, matches }` blocks:
```json theme={null}
{
"web_accessible_resources": [
{
"resources": ["images/logo.png", "pages/injected.html"],
"matches": ["https://example.com/*"]
}
]
}
```
Common mistakes:
* Listing a path that is not in the build output. Extension.js only emits files that are referenced from `manifest.json`, the entry HTML, or imported code. Add the file as an asset so it lands in `dist/`.
* Forgetting `matches`. Without it, the resource is not exposed to any origin.
* Using V2-style flat strings. The browser silently ignores them in MV3.
See [`web_accessible_resources` implementation](/docs/implementation-guide/web-accessible-resources) for the full pattern, including injecting an extension URL into a content script.
## `host_permissions` vs `permissions`
Manifest V3 split host access out of the `permissions` array:
| Key | What it controls |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `permissions` | Named API surfaces (`storage`, `tabs`, `cookies`, `scripting`, `alarms`, etc.). |
| `host_permissions` | URL match patterns the extension can read or modify (`https://*/*`, `*://api.example.com/*`). |
| `optional_permissions` and `optional_host_permissions` | Permissions requested at runtime via `chrome.permissions.request`. |
Symptoms of mixing them up:
* `chrome.cookies.get` returning empty for a site you have access to: the URL needs `host_permissions`, not just `cookies`.
* `chrome.scripting.executeScript` failing with "Cannot access contents of url": add the URL to `host_permissions`.
* Web store warning users about "all-site" access when you only need one origin: narrow the host pattern.
See [Permissions and host permissions](/docs/implementation-guide/permissions-and-host-permissions) for the per-API reference.
## `declarative_net_request` in Firefox
`declarative_net_request` (DNR) is the MV3 replacement for blocking `webRequest`. Firefox supports it, but with a few constraints:
* Static rule resource files (`rule_resources`) must be valid JSON arrays. Firefox is stricter about empty or malformed rule files than Chrome.
* Some rule actions and conditions Chrome supports are still partial in Firefox. Check [MDN's `declarativeNetRequest` reference](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest) for the current matrix.
* `host_permissions` (or ``) is required for DNR rules to apply on cross-origin requests.
Extension.js validates the DNR resources at build time and emits per-browser artifacts so a Firefox-specific rule file does not end up in the Chrome build.
## Content scripts, workers, and extension URLs
Three places where MV3 trips people up:
* **Content scripts cannot access the page's JavaScript scope.** They share the DOM, not the window. Use `window.postMessage` (or a `