Develop, build, preview, and package Extension.js projects.
npm install extension-develop[npm-version-image]: https://img.shields.io/npm/v/extension-develop.svg?color=0971fe
[npm-version-url]: https://www.npmjs.com/package/extension-develop
[npm-downloads-image]: https://img.shields.io/npm/dm/extension-develop.svg?color=0971fe
[npm-downloads-url]: https://www.npmjs.com/package/extension-develop
[powered-image]: https://img.shields.io/badge/Powered%20by-Extension.js-0971fe
[powered-url]: https://extension.js.org
[action-image]: https://github.com/extension-js/extension.js/actions/workflows/ci.yml/badge.svg?branch=main&color=0971fe
[action-url]: https://github.com/extension-js/extension.js/actions
[![Powered by Extension.js][powered-image]][powered-url]
Develop, build, preview, and package Extension.js projects.
This package powers Extension.js during local development and production builds. It provides the commands and build plugins that compile your extension, run it in browsers, and produce distributable artifacts.
```
pnpm add extension-develop
`ts
import {
extensionDev,
extensionBuild,
extensionStart,
extensionPreview,
type DevOptions,
type BuildOptions,
type StartOptions,
type PreviewOptions
} from 'extension-develop'
async function run() {
// Development server
await extensionDev(undefined, {
browser: 'chrome',
open: true
} satisfies DevOptions)
// Production build + zip
await extensionBuild(undefined, {
browser: 'firefox',
zip: true
} satisfies BuildOptions)
// Build then preview from dist/
await extensionStart(undefined, {browser: 'edge'} satisfies StartOptions)
// Preview using an existing output folder or project path
await extensionPreview(undefined, {
browser: 'chrome',
mode: 'production'
} satisfies PreviewOptions)
}
run()
`
- Live reload/HMR development server with per-instance browser runners
- Cross-browser support: Chrome, Edge, Firefox, Chromium-based, Gecko-based
- Rspack-based build with opinionated plugin stack
- Clean production output in dist/.gitignore
- Zipping: distribution and/or source packages (respects )extension-env.d.ts
- Auto-install of missing build + optional dependencies on first run
- Type generation for TS projects via extension.config.(js|mjs)
- User config via for commands, browser start, unified logger defaults, and webpack config hooks
- Managed dependency guard to avoid conflicts
| Name | Summary |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| dev | - Starts a local development server with live reload/HMR
- Auto-installs build + optional deps if missing
- Generates TypeScript shim types (extension-env.d.ts) when applicabledist/
- Runs a target browser with an isolated/stable profile |
| build | - Production build using the webpack/Rspack plugin stack
- Cleans before emittingdist/
- Optional packaging: distribution zip and/or source zip
- Merges user config; excludes browser runners during compilation |
| start | - Runs a silent production build, then runs preview from dist/
- Mirrors the runtime environment of shipped output |
| preview | - Runs the extension for manual testing without dev server
- Uses when present, otherwise uses the project directory
- Preserves production settings; only browser runners are applied |
| cleanup | - Removes orphaned browser instances and temporary profiles created during development |
Options accepted by each command. Values shown are typical types or enumerations; see the tables for specifics.
| Option | Type / Values | Description |
| -------------- | -------------------------------------------------- | --------------------------------------------------------------- |
| browser | chrome, edge, firefox, chromium-based, gecko-based | Target browser/runtime |
| profile | string or false | Profile path or disable profile persistence |
| startingUrl | string | Initial URL to open |
| open | boolean | Focus/open the browser window (CLI: use --no-open to disable) |
| --no-runner | boolean | Skip launching the browser runner |
| chromiumBinary | string | Custom Chromium-based executable path |
| geckoBinary | string | Custom Gecko-based executable path |
| Option | Type / Values | Description |
| ------------- | ----------------------------------- | --------------------------------------------------------------------------------- |
| mode | development, production, none | Build mode |
| polyfill | boolean | Include webextension-polyfill when possible |all
| port | number or string | Dev server port |
| source | string | Inspect a source directory |
| watchSource | boolean | Watch the source directory |
| logs | off,error,warn,info,debug,trace,all | Unified logger verbosity (all shows everything) |
| logContext | list or | Comma-separated contexts (background,content,page,sidebar,popup,options,devtools) |
| logFormat | pretty,json | Pretty text or JSON output |
| logTimestamps | boolean | Include timestamps in pretty output |
| logColor | boolean | Colorize pretty output |
| logUrl | string or /regex/flags | Filter by URL substring or JS-style regex literal |
| logTab | number | Filter by tabId |
| Option | Type / Values | Description |
| ----------- | ------------- | -------------------------------------------------------------- |
| zip | boolean | Package dist/ as an artifact (e.g., .zip, .xpi) |.gitignore
| zipSource | boolean | Package source files (respects ) |webextension-polyfill
| zipFilename | string | Custom base name for artifacts |
| polyfill | boolean | Include when possible |
| silent | boolean | Suppress non-error logs |
| Option | Type / Values | Description |
| -------- | ------------- | --------------------------------------------- |
| mode | production | Build mode |webextension-polyfill
| polyfill | boolean | Include when possible |
| Option | Type / Values | Description |
| ---------- | ------------- | ------------------------------------------------------------------- |
| mode | production | Build mode |dist/
| outputPath | string | Directory to run from (defaults to when available) |
- Path or remote: Commands accept a local path or a remote URL.
- GitHub repo URL: downloaded via go-git-it into a subfolder named after the repository.manifest.json
- Other HTTP(S) URLs: treated as zip archives and extracted locally.
- Monorepos: The nearest is resolved recursively; the nearest valid package.json is then located and validated.
- manifest.json may live in any subdirectory of your project.package.json
- The project root for build/dev is the directory containing the nearest valid (i.e., webpack context).public/
- Special folders and root-relative paths are anchored at the package root:
- , pages/, scripts/, and URLs starting with / resolve relative to the package root (e.g., /logo.png → ).package.json
- Web-only mode: if no is found, the manifest directory is used as a fallback project root.
- Provide extension.config.js or extension.config.mjs in your project root.browser.
- Supported sections:
- config(config: Configuration): mutate the assembled Rspack config. Supports a function or a plain object. When an object is provided, it is deep‑merged on top of the assembled config.
- commands.dev | .build | .start | .preview: per‑command options (browser, profile, binaries, flags, preferences, unified logger defaults, packaging). These defaults are applied for all respective commands.
- browser.chrome | .firefox | .edge | .chromium-based | .gecko-based: start flags, excluded flags, preferences, binaries, and profile reuse (persistProfile).
- extensions: load-only companion extensions (unpacked dirs) loaded alongside your extension in dev/preview/start.
- Example: { dir: "./extensions" } loads every "./extensions/\*" folder that contains a manifest.json.
- Precedence when composing options: browser._ → commands._ → CLI flags. CLI flags always win over config defaults.
- Browser key aliases when resolving from extension.config.:chromium
- When the runtime asks for , loadBrowserConfig prefers browser.chromium and then falls back to browser['chromium-based'].chromium-based
- When the runtime asks for , it prefers browser['chromium-based'] and then browser.chromium.firefox
- When the runtime asks for , it prefers browser.firefox and then browser['gecko-based'].gecko-based
- When the runtime asks for , it prefers browser['gecko-based'] and then browser.firefox.
- When detected, a one‑time notice is printed to indicate config is active.
Use this when you have other unpacked extensions you want loaded alongside your main extension during dev, start, and preview.
- What it loads: directories that contain a manifest.json at their root (unpacked extension roots).--load-extension
- How they’re loaded: they’re appended into the browser runner’s list (Chromium) / addon install list (Firefox) before your extension. Your extension is always loaded last for precedence.extensions.dir
- Discovery:
- : scans one level deep (e.g. ./extensions/*/manifest.json)extensions.paths
- : explicit directories (absolute or relative to the project root)extensions
- Overrides: top-level applies to all commands, but commands. overrides it for that command.EXTENSION_AUTHOR_MODE=true
- Invalid entries: ignored. In author mode () we print a warning if extensions is configured but nothing resolves.
Example:
`js`
// extension.config.mjs
export default {
// Applies to dev/start/preview unless overridden per-command
extensions: {
dir: './extensions',
paths: ['./vendor/some-unpacked-extension']
},
commands: {
dev: {
// Override only for dev
extensions: ['./extensions/debug-helper']
}
}
}
- extension.config.* runs in a Node context during command startup.process.env.*
- Use to read environment variables inside the config file.import.meta.env.*
- is available in your extension code at bundle time (via the Env plugin), not in the Node config.process.env
- During config loading, develop preloads environment files from the project directory into using the following order (first match wins):.env.defaults
1. (always merged first when present).env.development
2. .env.local
3. .env
4. EXTENSION_PUBLIC_*
- Only variables you read explicitly in the config are used there; client-side injection still requires the prefix.
- Example:
`jsextension dev
// extension.config.js (Node-based)
export default {
browser: {
chrome: {
startingUrl:
process.env.EXTENSION_PUBLIC_START_URL || 'https://example.com'
}
},
commands: {
dev: {
// Unified logger defaults for `
logLevel: 'off',
// omit or set to undefined to include all
logContexts: ['background', 'content'],
logFormat: 'pretty',
logTimestamps: true,
logColor: true,
// Optional filters
logUrl: '/example\\.com/i',
logTab: 123
}
},
// Either a function
config: (config) => config
// Or a plain object to merge
// config: { resolve: { alias: { react: 'preact/compat' } } }
}
- Managed dependency guard: If your extension.config.* references dependencies that are managed by Extension.js itself, the command aborts with a detailed message to prevent version conflicts.node_modules
- Auto‑install: If is missing, the appropriate package manager is detected and dependencies are installed before running.extension-env.d.ts
- Type generation: For TypeScript projects, is generated/updated to include required types and polyfills.
- Distribution artifacts live under dist/ and source artifacts under dist/.manifest.name
- File names default to a sanitized form of plus manifest.version (override with zipFilename)..gitignore
- Source packaging respects .
Example layout when both zip and zipSource are enabled:
``
dist/
chrome/
- Built on the same Rspack stack as @/webpack; user config is loaded when an extension.config.* is present.EXTENSION_PUBLIC_*
- Only variables are injected into client code; avoid secrets in templated .json/.html.
| Name | Group | Summary |
| -------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| plugin-extension | core | - Core builder: emits pages and scripts
- Validates and rewrites manifest.jsontsconfig
- Ships icons, JSON, locales, and web resources
- Ensures dev parity between local and shipped output |
| plugin-css | core | - Auto‑wires CSS for HTML and content scripts
- Optional SASS/LESS/PostCSS when configs exist
- Integrates Stylelint when configured |
| plugin-js-frameworks | core | - Detects React/Preact/Vue/Svelte and TypeScript
- Configures SWC parsing, loaders/plugins, and safe aliases
- Sets resolutionbeforeRun
- Defers heavy work to in production |assets/
| plugin-static-assets | core | - Emits images, fonts, and misc files to webextension-polyfill
- Inlines small SVGs (≤2KB), emits larger ones
- Content hashing in production; stable names in development
- Respects existing custom SVG rules |
| plugin-compatibility | core | - Cross‑browser helpers
- Normalizes browser‑specific manifest fields
- Optional for Chromium |EXTENSION_PUBLIC_*
| plugin-compilation | core | - Loads env and templating ()dist/
- Optional cleaning
- Compact, de‑duplicated compilation summary |
- Built against @rspack/core; Webpack 5 may work for some plugins but is not officially supported here.EXTENSION_PUBLIC_*
- Only variables are injected into client code; avoid embedding secrets in templated .json/.html.
- webpack/webpack-config.ts: Assembles the plugin stack and shared configuration.dev-server/
- : Local development server wiring and reload orchestration.webpack/webpack-types.ts`: Common types for the plugin stack.
-
MIT (c) Cezar Augusto and the Extension.js Authors.