CLI tool
webextension-toolbox/webextension-toolbox avatar
webextension-toolbox/webextension-toolbox

webextension-toolbox compiles one extension into per-vendor bundles

Small CLI toolbox for cross-browser WebExtension development

790 stars71 forksTypeScriptMIT

At a glance

What is it?
webextension-toolbox is a small CLI that compiles a WebExtension with webpack for a named browser, rewrites vendor specific manifest keys, and packs the result as .xpi, .crx or .zip.
Who is it for?
Teams shipping the same extension to Chrome, Firefox, Edge, Safari and Opera should adopt webextension-toolbox, since the vendor argument, the per-vendor dist directory and the manifest key rewriting remove most of the boilerplate of a multi-browser release. A team with a single target browser, or one that needs runtime polyfills between the browser and chrome APIs, will find little here and should read the Polyfill section first.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One vendor argument drives the whole build

The toolbox exists to remove the repetitive part of shipping a browser extension to several vendors at once. It is a command line program, not a framework you configure in code, and its central idea is simple: every command takes the vendor as an argument, and everything else about the build follows from that name.

The commands are dev for a watched development build and build for a production build. Both set process.env.VENDOR to the current vendor and process.env.NODE_ENV to development or production respectively, so code in your extension can branch on which browser it is running in without any runtime detection. The dev command additionally watches all extension files, recompiles on demand and reloads the extension or extension page as soon as something changed.

That build-time vendor detection is the piece that matters most for cross-browser work, and it is also where the toolbox draws a line. The README is explicit that the toolbox no longer provides any polyfills for cross-browser support, and that polyfill gaps such as the difference between the browser and chrome APIs have to be handled by detecting the browser during the build time using process.env.VENDOR.

Vendor manifest keys let one manifest serve several browsers

A WebExtension manifest often needs a key that only makes sense for one browser, such as a Chrome update key. The toolbox handles this with a prefix syntax in manifest.json where the vendor prefix is written before the key name:

json
"name": "my-extension",
"__chrome__key": "yourchromekey",
"__chrome|opera__key2": "yourblinkkey"

The double underscore marks the field as vendor specific, and the pipe separates multiple vendors that should receive the same value. Compiling for chrome produces:

json
"name": "my-extension",
"key": "yourchromekey",
"key2": "yourblinkkey"

Compiling for opera produces:

json
"name": "my-extension",
"key2": "yourblinkkey"

For any other vendor the extra fields are dropped entirely, leaving:

json
"name": "my-extension"

This is a small mechanism with a large effect on maintenance. Instead of maintaining separate manifest templates per browser, you keep one manifest and let the build strip or rewrite the fields each target needs. The limitation is that the mechanism only handles manifest fields; it does nothing for the JavaScript APIs behind them.

Manifest validation runs inside the webpack compile

The manifest is validated while compiling, so a malformed manifest.json fails the build rather than the browser load. Validation can be turned off by adding --validateManifest to a build or dev command, which is the escape hatch for a manifest that intentionally contains fields the validator rejects.

Defaults come from your package.json as well. The toolbox reads name, version and description from that file and uses them as the default values for the corresponding manifest fields, so the version number lives in one place instead of being duplicated between package.json and manifest.json.

The build output also carries a persistent configuration. The --save option writes the config to .webextensiontoolboxrc, and the -c, --config flag points the command at a config file path. Combined with the other flags, a team can record the source directory, target directory, devtool setting and ignore lists once and reuse them in CI. The --vendor-version option records the last supported vendor version, which defaults to the current one, and the --copy-ignore and --compile-ignore flags accept glob patterns for files that should be copied without being compiled, or excluded from both.

The dev command watches, reloads and exposes the vendor as an env var

The development loop is the part of the toolbox most users will touch daily. The dev command compiles the extension via webpack to dist/<vendor>, watches all extension files, recompiles on demand and reloads the extension or extension page as soon as something changed. The vendor argument selects the target, and the same environment variables are set as in a production build.

The available examples in the README cover the browsers that the project tests through GitHub Actions:

shell
$ webextension-toolbox dev --help
$ webextension-toolbox dev chrome
$ webextension-toolbox dev firefox
$ webextension-toolbox dev edge
$ webextension-toolbox dev safari

The five officially supported browsers are Edge (edge), Firefox (firefox), Chrome (chrome), Safari (safari) and Opera (opera). A longer list will compile but is not tested, including Internet Explorer (ie), iOS Safari (ios_saf), Opera Mini (op_mini), Android Browser (android), Blackberry Browser (bb), Opera Mobile (op_mob), Chrome for Android (and_chr), Firefox for Android (and_ff), Internet Explorer Mobile (ie_mob), UC Browser (and_uc), Samsung Internet (samsung), QQ Browser (and_qq), Baidu Browser (baidu) and KaiOS (kaios). Treat the untested group as a compile target you still have to verify by hand.

Installing the CLI and running a first build

The package is published on npm under a scoped name, and the README gives both a global and a local install. A local install is the better fit for a project, since it pins the version in package.json and lets the build script invoke the binary directly:

shell
$ npm install -D @webextension-toolbox/webextension-toolbox

The global variant puts the binary on your PATH for ad hoc use across projects:

shell
$ npm install -g @webextension-toolbox/webextension-toolbox

The binary name is webextension-toolbox, mapped in package.json to ./bin/webextension-toolbox.mjs. The package is published as an ES module, declares TypeScript types at ./dist/index.d.ts and exposes the library entry point at ./dist/index.js. Its own scripts show the internal toolchain: build runs tsc, dev runs tsc --watch, and lint runs eslint over the ./src directory.

A first production build then looks like this, where the source directory defaults to app and the target directory defaults to dist/[vendor]:

shell
$ webextension-toolbox build chrome

The build command compiles the extension for production via webpack to dist/<vendor>, minifies the code, sets process.env.NODE_ENV to production and process.env.VENDOR to the current vendor, and packs the extension into a packages directory. Useful flags taken straight from the help output include:

shell
  -c, --config [config]                specify config file path
  -s, --src [src]                      specify source directory (default: "app")
  -t, --target [target]                specify target directory (default: "dist/[vendor]")
  -d, --devtool [string | false]       controls if and how source maps are generated (default: "cheap-source-map")

The --swc flag switches the transpiler from Babel to SWC, and --no-minimize turns off minification, which matters when you are chasing a production bug in a minified bundle.

Packing decides the file extension from the target browser

The build task creates browser specific bundles, and the format follows the vendor rather than a flag you have to remember. Firefox receives a .xpi archive and Opera receives a .crx archive, while every other bundle is a plain .zip file.

That distinction is not cosmetic. Chrome and Edge load an unpacked directory during development and accept a .zip for upload, Firefox expects .xpi for its add-on distribution, and Opera expects .crx. Since the toolbox derives the extension from the vendor, a release pipeline that runs the same command per browser produces the right artefact each time.

The build also supports incremental build reporting through --verbose, which prints messages at the beginning and end of an incremental build, and the SWC option for a faster transpile than Babel. The dependency list in package.json shows why this is a webpack program rather than a bare script: babel-loader and swc-loader sit alongside webpack, webpack-dev-server, copy-webpack-plugin, ts-loader, tsconfig-paths-webpack-plugin, webpack-watched-glob-entries-plugin, browserslist, case-sensitive-paths-webpack-plugin, chalk, commander, yaml and find-up, with @webextension-toolbox/webpack-webextension-plugin as the plugin that ties it together.

Polyfills are out of scope, so browser branching happens at build time

The README states that the WebExtension specification is currently supported on Chrome, Firefox, Edge (Chromium) and Safari, and then draws the line: this toolbox no longer provides any polyfills for cross-browser support. If you need polyfills, for example between the browser and chrome APIs, the project suggests detecting the browser during the build time using process.env.VENDOR.

For teams that relied on an earlier behaviour of this package, that sentence is the single most important line in the README. A codebase that used to assume a browser namespace always existed will need to read process.env.VENDOR at build time and branch, or ship its own shim. The toolbox does ship babel-preset-env and the README invites you to add custom Babel configuration if you need custom polyfills, which is the supported path for filling gaps yourself.

TypeScript support is native but, in the README's phrasing, not enforced. The package is written in TypeScript with a tsconfig.json in the repository, but a JavaScript project can use it without adopting TypeScript. The README also carries a FAQ entry on using React and another on what SWC is, both of which point at the same principle: the toolbox is a build layer and it leaves the framework choice to you.

MIT licensed, published from npm, and moving ahead of its last release

The package is MIT licensed and distributed through npm at @webextension-toolbox/webextension-toolbox, with the repository at github.com/webextension-toolbox/webextension-toolbox and the entry page pointing to the npm listing. The published versions visible in the release history are 7.1.1 on 2024-02-01, 7.1.0 on 2024-01-31 and 7.0.0 on 2024-01-29, a cadence that suggests the maintainers ship versions in bursts after breaking changes.

The repository is not archived and the last push was on 2026-10-12, which is well after those releases. So the source tree is moving while the published npm version is not, and a project pinning 7.1.1 is pinning code that predates the most recent commits. The version 7.1.1 in package.json matches the newest release, which confirms the working tree has not been re-versioned.

Upgrade cost is therefore a version jump rather than a routine pull. The 7.0.0 release is a major bump, and the removal of built-in polyfills is the kind of change that shows up as a runtime error in a browser rather than as a build failure, because the code still compiles. The repository includes a CHANGELOG.md, a Makefile with act-build and act-lint targets that run the GitHub Actions workflows locally with act, and an AI-POLICY.md file, so the migration path is documented in the tree rather than only in release notes.

Editorial conclusion

Teams shipping the same extension to Chrome, Firefox, Edge, Safari and Opera should adopt webextension-toolbox, since the vendor argument, the per-vendor dist directory and the manifest key rewriting remove most of the boilerplate of a multi-browser release. A team with a single target browser, or one that needs runtime polyfills between the browser and chrome APIs, will find little here and should read the Polyfill section first. Before upgrading, check that the flags your build script relies on still exist in the current build help output, because the latest published release is 7.1.1 from 2024-02-01 while the last push was on 2026-10-12.

Frequently asked questions

How do I install webextension-toolbox in a project?

The README gives a local install with npm install -D @webextension-toolbox/webextension-toolbox and a global one with npm install -g @webextension-toolbox/webextension-toolbox. The binary is named webextension-toolbox and is mapped to ./bin/webextension-toolbox.mjs in package.json.

Does webextension-toolbox provide polyfills for cross-browser APIs?

No. The README states that this toolbox no longer provides any polyfills for cross-browser support and suggests detecting the browser during the build time using process.env.VENDOR instead. It ships babel-preset-env and invites custom Babel configuration if you need custom polyfills.

Which browsers does webextension-toolbox support and test?

Edge (edge), Firefox (firefox), Chrome (chrome), Safari (safari) and Opera (opera) are tested through GitHub Actions. A longer list of targets including Internet Explorer, iOS Safari, Opera Mini, UC Browser and KaiOS will compile but is not tested.

How do vendor specific manifest keys work in webextension-toolbox?

A vendor prefix written before a key name, as in "__chrome__key" or "__chrome|opera__key2", marks the field as vendor specific. Compiling for chrome emits both keys, compiling for opera emits only the second, and any other vendor gets a manifest without them.

What format does a webextension-toolbox build produce?

The build task creates specific bundles per browser: a .xpi for Firefox and a .crx for Opera, with all other bundles being .zip files. The compiled output goes to dist/<vendor> and the packed artefacts are written to a packages directory.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. webextension-toolbox/webextension-toolbox on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/webextension-toolbox-webextension-toolbox.svg)](https://hysenlabs.com/projects/webextension-toolbox-webextension-toolbox)

Was this useful?