CLI tool
extension-js/extension.js avatar
extension-js/extension.js

Extension.js: a zero-config build tool for cross-browser extensions

The cross-browser extension framework.

5,175 stars141 forksTypeScriptMIT

At a glance

What is it?
Extension.js wraps Chrome, Edge, Firefox and Safari extension development in one CLI with hot module replacement. It is aimed at teams tired of maintaining a separate build pipeline per browser, and it trades configuration control for that convenience.
Who is it for?
Adopt Extension.js if you ship the same Manifest V3 extension to more than one browser and want the dev loop handled for you. Skip it if your build depends on a webpack or Rollup plugin chain you cannot give up, since the README states there is no webpack and no rollup to configure.
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 1 day 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 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem Extension.js targets: one extension, four browsers

Browser extension development carries a reputation the README states bluntly: "Browser extensions ship with the worst dev experience in modern web." The concrete complaints it lists are Manifest V3 fragmentation, browser-specific quirks, missing hot reload for content scripts, and a separate build pipeline for every target. Anyone who has maintained a webpack config that emits one bundle for Chrome and a differently patched bundle for Firefox will recognise the shape of that problem.

The intended user is a developer already writing a Manifest V3 extension who wants Chrome, Edge, Firefox and Safari from a single codebase. The README also positions it as a drop-in for existing extensions, adding one devDependency rather than a rewrite. That second claim is the one worth testing on your own project before you believe it.

How the CLI, dev server and reload logic fit together

The repository is a pnpm monorepo. The root package.json is named extension-workspace and describes itself as containing "the Extension.js CLI, its programs, packages, website, and documentation". The CLI entry point is compiled to programs/extension/dist/cli.cjs and the workspace exposes it through the script "extension": "dotenv -- node programs/extension/dist/cli.cjs". So the published package is a Node program, not a library you import into your source.

On the dev side, the README describes hot module replacement for popup, options, devtools and other extension pages, with support for React, Vue, Svelte and Preact components. Content scripts and the service worker are handled differently: they get targeted reloads, and the README states that "only the changed entries re-inject, no plugin glue". That distinction matters. Full page HMR and script re-injection are different mechanisms, and the framework treats them as such rather than reloading the whole extension on every save.

Manifest handling is the other half. Manifest V3 is the default, and the README says adapters for Chrome, Edge, Firefox and Safari targets are applied automatically. The README does not explain how those adapters rewrite a manifest, so the transformation is something to inspect in the build output rather than assume.

Installing Extension.js and running a first dev session

The README gives a three-command start. The first scaffolds a project, the second enters it, the third starts the dev server:

bash
npx extension@latest create my-extension
cd my-extension
npm run dev

What you should see is a generated project directory and, after npm run dev, a browser launched with the extension loaded. The README states the CLI itself runs on Node.js 22.12+, Deno 2.5+ and Bun 1.2+, and that the generated project works with npm, pnpm, yarn, bun and deno. If your Node version is older than 22.12, the CLI will not start.

To pick a browser explicitly, the README documents flags shared by extension dev, extension start and extension preview:

bash
npx extension@latest dev --browser=chrome
npx extension@latest dev --browser=edge
npx extension@latest dev --chromium-binary "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
npx extension@latest dev --gecko-binary "/Applications/Firefox.app/Contents/MacOS/firefox"

The --browser flag accepts chrome, edge, firefox or safari. The two binary flags point at a specific Chromium or Gecko executable, which is how you test against a build you already have installed instead of the system default.

The README also documents a managed binary path: extension install firefox downloads an isolated build. That is separate from --gecko-binary, which uses a binary you supply. For a production artifact, the README gives extension build --zip and states the output is ready for the Chrome Web Store and Firefox Add-ons. The README does not document what the zip contains beyond that.

Where Extension.js stops being the right tool

The selling point is also the constraint. The README lists "Zero config, no webpack, no rollup, no plugins to maintain" as a feature. If your extension already depends on a specific loader, a custom transform, or a plugin that only exists for webpack or Rollup, that absence is a migration cost, not a benefit. There is no documented plugin API in the README, so you cannot assume you can reimplement the missing piece inside Extension.js.

Safari deserves separate scrutiny. The README lists Safari as a target and includes --browser=safari among the flags, but shipping to Safari normally involves Xcode and a containing app. The README does not describe that step, and the documentation is the place to check before promising a Safari release.

The environment file hints at a second boundary. .env.example defines EXTENSION_DEV_API_URL, EXTENSION_DEV_DOCS_URL and EXTENSION_DEV_TOKEN, with a comment stating that when these are left empty "the CLI prints no links to them (build hint, scaffold README, publish remedies)". So the share and store submission flow is tied to an external service, and the README names extension.dev as the sponsor that hosts that side. The build and dev loop work without it; the publishing conveniences do not.

Extension.js compared with WXT, Plasmo and CRXJS

The README names Plasmo, WXT and CRXJS directly and lists what Extension.js does that "the others do not". The differences it claims are behavioural rather than architectural: running any GitHub sample with extension dev https://github.com/.../sample, downloading an isolated browser build with extension install firefox, targeted content-script reload across browsers without plugin glue, a store-ready zip from extension build --zip, framework-agnostic templates for vanilla JavaScript, TypeScript, React, Vue, Svelte and Preact, and custom Chromium or Gecko binaries via --chromium-binary and --gecko-binary.

The honest read is that these are workflow differences, not a different model of the world. All four projects build a Manifest V3 extension from a source tree. Extension.js bets on doing more of it from the CLI: fetching samples, managing browser binaries, and emitting the store zip. WXT and Plasmo are also framework-oriented and have their own module systems, which is exactly the kind of extensibility Extension.js gives up by having no plugin layer. If your build is plain and your pain is browser plumbing, the trade is favourable. If your build is unusual, it is not.

Maintenance cadence, licence and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-22. Recent releases follow quickly: v4.1.25 on 2026-09-18, v4.1.26 on 2026-09-20, and v4.1.27 on 2026-09-22. That cadence cuts both ways. Fixes arrive fast, and so do version bumps, which means a lockfile pin is worth having if you do not want a patch release landing mid-sprint.

The licence is MIT, stated in the repository and in the LICENSE file at the root. MIT is permissive and imposes no source-disclosure requirement on your extension. That is a statement about the licence text, not legal advice; if your organisation has rules about dependency licences, run it past whoever handles that.

The upgrade cost is mostly the manifest adapters. Because the framework rewrites manifests per browser target, a version bump can change the emitted manifest without any change on your side. Diff the build output after upgrading rather than only reading the changelog. The repository root also carries CHANGELOG.md and RELEASE_HIGHLIGHTS.md, which is where that diff should be explained.

One more thing to check before adopting: .env.example documents telemetry. POSTHOG_KEY and POSTHOG_HOST are set by default, and the file lists EXTENSION_TELEMETRY=0 as the "hardest override", with EXTENSION_TELEMETRY_SAMPLE_RATE, EXTENSION_TELEMETRY_MAX_EVENTS, EXTENSION_TELEMETRY_DEBOUNCE_MS and EXTENSION_TELEMETRY_DEBUG as further controls. If your build environment forbids outbound analytics, set EXTENSION_TELEMETRY=0 before the first run.

Editorial conclusion

Adopt Extension.js if you ship the same Manifest V3 extension to more than one browser and want the dev loop handled for you. Skip it if your build depends on a webpack or Rollup plugin chain you cannot give up, since the README states there is no webpack and no rollup to configure. Before committing, run the scaffold command, open the generated manifest, and confirm the browser flags you need (--browser, --chromium-binary, --gecko-binary) cover your targets.

Frequently asked questions

What is the extension of js?

In this context the question is about Extension.js, a cross-browser extension framework written in TypeScript that builds a Manifest V3 extension for Chrome, Edge, Firefox and Safari from one source tree. The README describes it as a zero-config CLI with no webpack or rollup to maintain.

What is a .js file?

That question is about JavaScript source files in general, not about Extension.js. What is relevant here is that Extension.js is distributed as a Node program, compiled to programs/extension/dist/cli.cjs in the repository, and is run through npx rather than imported into your own source.

How do I open a .js file?

Opening a JavaScript file is not something Extension.js does; it is a build tool. The closest documented action is starting a project, which the README gives as npx extension@latest create my-extension, then cd my-extension, then npm run dev.

What is the .js format?

The .js format refers to JavaScript source files generally. Extension.js itself is written in TypeScript and its CLI requires Node.js 22.12+, Deno 2.5+ or Bun 1.2+ to run.

What is extension js?

Extension.js is a cross-browser extension framework and CLI. The README states it builds for Chrome, Edge, Firefox and Safari with Manifest V3 by default, hot module replacement for extension pages, and a production zip via extension build --zip.

Official sources

  1. extension-js/extension.js on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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/extension-js-extension-js.svg)](https://hysenlabs.com/projects/extension-js-extension-js)