> ## Documentation Index
> Fetch the complete documentation index at: https://extension.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser extension framework comparison

> Compare browser extension frameworks: Extension.js, WXT, CRXJS, and Plasmo. Manifest model, bundler, cross-browser output, and migration guides for each.

A browser extension framework handles the work that browsers do not. It compiles TypeScript and modern JavaScript for every extension surface, wires the manifest to real output files, reloads service workers and content scripts during development, and emits per-browser builds. This page compares the active options so you can choose with full information.

## The frameworks

| Dimension | Extension.js | WXT | CRXJS | Plasmo |
| - | - | - | - | - |
| Model | `manifest.json` as source of truth | File conventions + config | Vite plugin reading a manifest | File conventions |
| Bundler | Rspack (internal, zero config) | Vite | Vite (plugin layer) | Parcel |
| Cross-browser | Chrome, Edge, Firefox, Safari from one source | Chrome, Firefox, MV2 support | Chromium focus | Chrome, Firefox targets |
| Setup | None required | `wxt.config.ts` | `vite.config.ts` + plugin | `package.json` manifest field |
| Bundler coupling | Internal detail | Tracks Vite majors | Breaks with bundler changes | Tied to Parcel |

Two structural differences matter more than any feature row:

* **Where the manifest lives.** Extension.js reads your `manifest.json` and compiles whatever it declares. WXT and Plasmo generate the manifest from file conventions and config. CRXJS reads a manifest but resolves it through Vite's plugin API, which is why bundler upgrades can break it (see [the Vite 8 fileName error](/docs/migrate/crxjs-content-script-filename-undefined)).
* **Who owns the bundler.** A framework that wraps a general-purpose bundler inherits that bundler's breaking changes. Extension.js treats the bundler as an internal detail: you never configure it, and upgrades are the framework's problem, not yours.

## Detailed comparisons and migrations

* [Extension.js vs WXT](/docs/compare/extension-js-vs-wxt): the closest comparison, dimension by dimension.
* [Extension.js vs Plasmo](/docs/compare/extension-js-vs-plasmo): manifest model, dev loop, packaging, and maintenance status with dates.
* [Migrate from CRXJS](/docs/migrate/from-crxjs): step-by-step, about ten minutes for a typical project.
* [Fix: Content script fileName is undefined](/docs/migrate/crxjs-content-script-filename-undefined): the CRXJS + Vite 8 build error, with workarounds.
* [Migrate from Plasmo](/docs/migrate/from-plasmo): convention-to-manifest mapping, env vars, and CSUI.
* [Migrate a webpack or Vite extension](/docs/migrate/from-webpack-or-vite): entries to manifest.json, loaders and plugins, and the traps that cost real migrations time.

## Try it without committing

Run any extension template, or any extension repository on GitHub, with one command:

```bash theme={null}
npx extension@latest dev
```

Your components, `chrome.*` calls, and styling carry over from any of these frameworks. The [migration guides](/docs/migrate/from-crxjs) cover the wiring that changes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.