Skip to main content
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

DimensionExtension.jsWXTCRXJSPlasmo
Modelmanifest.json as source of truthFile conventions + configVite plugin reading a manifestFile conventions
BundlerRspack (internal, zero config)ViteVite (plugin layer)Parcel
Cross-browserChrome, Edge, Firefox from one sourceChrome, Firefox, MV2 supportChromium focusChrome, Firefox targets
SetupNone requiredwxt.config.tsvite.config.ts + pluginpackage.json manifest field
Bundler couplingInternal detailTracks Vite majorsBreaks with bundler changesTied 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).
  • 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

Try it without committing

Run any extension template, or any extension repository on GitHub, with one command:
npx extension@latest dev
Your components, chrome.* calls, and styling carry over from any of these frameworks; the migration guides cover the wiring that changes.