# mozilla/web-ext: a CLI for building, running, linting and signing WebExtensions

> web-ext is Mozilla's command line tool for the Firefox extension workflow, covering run, lint, build and sign. It is a Firefox-first tool with a limited Node API, and the README is explicit about that boundary.

**mozilla/web-ext** — A command line tool to help build, run, and test web extensions

- Repository: https://github.com/mozilla/web-ext
- Stars: 3,140 · Forks: 387
- Language: JavaScript
- License: MPL-2.0
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/mozilla-web-ext

## The problem web-ext solves for Firefox add-on developers

Writing a WebExtension involves a loop that the browser does not handle for you. You edit a manifest, a background script or a content script, then you need the extension loaded in a browser, reloaded after each change, and inspected for errors before you package it. web-ext is the command line tool Mozilla publishes to close that loop. The README describes it as a tool to help build, run, and test WebExtensions, and names five commands: run, lint, sign, build and docs.

The audience is narrow and worth stating plainly. The README says the tool ultimately aims to support browser extensions in a standard, portable, cross-platform way, but that initially it provides a streamlined experience for developing Firefox Extensions. That word initially is doing real work. If you maintain a Chrome extension, web-ext will still lint and package a source directory, but the run command is built around Firefox and the sign command is built around addons.mozilla.org. Treat it as a Firefox tool that happens to understand the shared WebExtension format, not as a neutral packaging layer.

## What run, lint, build and sign actually do

The commands map onto distinct stages. web-ext run launches a browser with your extension installed and watches the source directory so edits trigger a reload. web-ext lint validates the extension source, and the README's own description of the command is validate the extension source, with the actual validation delegated to the addons-linter package listed in dependencies. web-ext build creates an extension package from source, which is the zip you would upload. web-ext sign signs the extension so it can be installed in Firefox, which means it talks to the add-on signing service rather than producing a local artifact.

The Node API mirrors these commands. The README shows webExt.cmd.run taking an options object whose keys are derived from the CLI counterparts, so --source-dir becomes sourceDir. Every call takes a second argument for non-CLI options, and shouldExitProgram: false is what keeps a host Node process alive after the command finishes. The resolved value is an extensionRunner with methods the README lists as reloadAllExtensions() and exit().

The package also exports three utility entry points through the exports field in package.json: ./util/adb, ./util/logger and ./util/submit-addon. The adb utility is what makes Android targets possible. The README shows listADBDevices and listADBFirefoxAPKs returning arrays of device ids and APK paths, which you then pass to webExt.cmd.run with target: 'firefox-android'. That is a real capability, but note that it is exposed through a subpath import rather than the main entry, so it is a separate surface with its own stability risk.

## Installing web-ext and running an extension for the first time

The README requires the current LTS version of NodeJS before anything else. package.json is stricter: engines declares node >=20.0.0 and npm >=8.0.0, and engine-strict is set to true, so an older Node will fail the install rather than warn.

The global install is the shortest path:

```bash
npm install --global web-ext
```

For a project, the README recommends a devDependency so the version is pinned for the whole team:

```bash
npm install --save-dev web-ext
```

With that in place you wire it into package.json as an npm script. The README gives this exact example, where --source-dir points at the built extension:

```json
"scripts": {
  "start:firefox": "web-ext run --source-dir ./extension-dist/",
}
```

Running npm run start:firefox should open Firefox with the extension loaded from that directory. Extra CLI flags go after a double dash, so the README's example of selecting a Firefox channel is npm run start:firefox -- --firefox=nightly. If you prefer not to use npm for the install, the README notes that the community maintains a Homebrew formula and gives brew install web-ext, labelling that route unofficial.

Building from source follows a different path. The README lists Node.js, npm 8.0.0 or higher, and optionally nvm, then git clone, npm ci, npm run build and npm link. It warns that a previous global npm install should be removed first with npm uninstall --global web-ext. Updates after that only need git pull and npm run build; the README states you do not need to relink.

## The Node API is a documented second-class citizen

The README carries a note that web-ext is primarily a command line tool and that there is limited support for direct use of its internal API. It goes further: backward incompatible changes may be introduced in minor and patch version updates to the web-ext npm package. That is an unusual and honest warning. If you build a product on webExt.cmd.run, a 10.6 to 10.7 bump is not covered by semantic versioning guarantees for the API surface. Pin the version and read the changelog before upgrading.

There is a second constraint in the same area. Since version 7.0.0 the npm package exports NodeJS native ES modules only, and CommonJS consumers have to use dynamic imports. The package.json type field is module, which is consistent with that. A build that assumes require('web-ext') will not work.

Two smaller escape hatches are documented. You can import web-ext/util/logger and call consoleStream.makeVerbose() to turn on verbose logging, which is the practical way to see why a run or a sign attempt failed. You can pass noInput: true to disable use of standard input, which matters in CI where there is no terminal attached. Both are shown as code examples in the README rather than described in prose, so expect to read the source for anything beyond them.

## Where web-ext is the wrong tool

The clearest limitation is stated by the project itself. The README says web-ext is designed for WebExtensions but that you can try disabling manifest validation to work with legacy extensions, and adds that this is not officially supported. The mechanism is a getValidatedManifest override that returns a fake manifest with a name and a version. That is a testing hook, not a compatibility layer, and using it means you have opted out of the validation the lint command exists to provide.

The second limitation is the Firefox orientation. The README's stated goal is eventual standard, portable, cross-platform support, and the current scope is Firefox. If your release target is a Chrome Web Store package, web-ext build will produce a zip, but nothing in the documentation suggests the run or sign commands are useful there, and the signing path is Firefox-specific.

The third is the API stability note described above. A team that wants to embed extension packaging inside a larger Node service is working against a surface the maintainers explicitly say may break in minor or patch releases. That is a design choice, not an oversight, and it should shape whether you depend on the CLI or the module.

Finally, the README does not document rollback or unpublishing for signed artifacts. The sign command is described only as signing the extension so it can be installed in Firefox. If you need to understand what happens to a signed build after submission, the README is silent and you have to look at the add-on platform documentation instead.

## How web-ext compares with calling the browser and linter yourself

The obvious alternative is to assemble the pieces by hand. The dependencies listed in package.json show what that would involve: addons-linter for validation, chrome-launcher for starting a browser, and @devicefarmer/adbkit for Android device communication. A team that wants only validation can depend on addons-linter directly and skip web-ext entirely, which removes the CLI, the Node API stability caveat and the Firefox signing path in one step. The difference in approach is that addons-linter is a library you drive, while web-ext is an opinionated workflow that happens to embed it.

The other alternative is a task runner such as a webpack or Vite plugin that wraps the same steps. Those give you tighter integration with a bundler and its watch mode, at the cost of reimplementing the browser launch and reload behaviour that web-ext run already provides. If your extension has no build step at all, a bundler plugin is more machinery than the problem needs.

The split is fairly clean. Choose web-ext when you want the Firefox reload loop and the signing command out of the box. Choose addons-linter directly when you only need to fail a CI job on an invalid manifest, and choose a bundler plugin when your source pipeline is already the hard part.

## Maintenance, licence and upgrade cost

The repository is not archived and the last push was on 2026-09-23, one day before this article. Releases are frequent: 10.7.0 on 2026-09-21, 10.6.0 on 2026-08-04 and 10.5.0 on 2026-07-10. That cadence is the practical upgrade cost. Because the README warns that backward incompatible changes may land in minor and patch versions of the npm package, a pinned dependency plus a changelog read is the realistic policy for anyone using the Node API. CLI users are less exposed, since flags tend to be additive.

The licence is MPL-2.0, as declared in the repository's LICENSE file and the package metadata. MPL-2.0 is a file-level copyleft licence: modifications to covered files generally have to be made available under the same licence, while larger works that combine it with other code can be distributed under other terms. That is a summary of the licence's structure, not legal advice, and anyone embedding web-ext in a distributed product should read the licence text and, where the stakes justify it, take advice. For the common case of running the CLI as a development tool, the licence question rarely comes up.

One maintenance detail worth knowing: the README describes the Homebrew formula as community maintained and unofficial. Only the npm package and the source build are described as first-party routes.

## Conclusion

Adopt web-ext if you ship Firefox WebExtensions and want one command to reload an extension in a live browser, lint the source and produce a signed xpi. Do not adopt it as a cross-browser packaging pipeline: the README states the tool is designed for WebExtensions and only aims at standard, portable, cross-platform support eventually, and it currently gives a streamlined experience for Firefox extensions. Before committing, verify your Node version against the engines field (node >=20.0.0, npm >=8.0.0), check that your add-on id and signing credentials are in place for web-ext sign, and read the note that the npm package exports ES modules only since version 7.0.0 if your build is CommonJS.

## FAQ

### How do I install web-ext?

The README gives two supported routes: npm install --global web-ext for a machine-wide command, or npm install --save-dev web-ext to pin it inside a project. A community-maintained Homebrew formula also exists and the README labels it unofficial.

### What is web-ext used for?

It is a command line tool to help build, run, and test WebExtensions, with five commands: run, lint, sign, build and docs. The README states it initially provides a streamlined experience for developing Firefox extensions.

### How do I use web-ext to run an extension?

The README's example adds a package.json script that calls web-ext run --source-dir ./extension-dist/, which launches Firefox with the extension loaded from that directory. Extra flags go after a double dash, for example npm run start:firefox -- --firefox=nightly.

## Sources

- [Issues](https://github.com/mozilla/web-ext/issues)
- [License: MPL-2.0](https://github.com/mozilla/web-ext/blob/master/LICENSE)
- [mozilla/web-ext on GitHub](https://github.com/mozilla/web-ext)
- [README](https://github.com/mozilla/web-ext/blob/master/README.md)
- [Releases](https://github.com/mozilla/web-ext/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mozilla-web-ext
