Why Extro
What Extro optimizes for, and how it compares to WXT, Plasmo, and CRXJS.
A Manifest V3 extension is up to five programs in a trench coat: a popup, an options page, a side panel, a background service worker, and content scripts. Each runs in a different context with different constraints, and the manifest has to describe all of them, correctly, at all times. Hand-rolled setups end up with one Vite config per surface and a manifest that drifts out of sync with the code.
Extro's bet is that this should feel like building a Next.js app instead. There is one src/app/ tree. Surfaces are folders. Drop a page.tsx in popup/, and the manifest entry, the HTML shell, the bundle, and the route all exist. Nothing else to register.
What Extro optimizes for
Routing inside surfaces. Most tools treat a popup as a single mounted component. Extro treats every HTML surface as a small app: nested page.tsx routes, dynamic [id] segments, layouts, error boundaries, a Link component, and hooks like useParams and useSearchParams. A settings screen inside your popup is a folder, not a state machine.
A generated manifest you can override. Permissions, host matches, content-script registration, CSP, and icons are inferred from the tree and extro.config.ts. Every field has an escape hatch, up to a transformManifest() that can rewrite anything. You should rarely need to think about manifest.json, and never be blocked by it.
One package, one plugin. extrojs ships the CLI, the Vite plugin, and the runtime as subpath exports. There is no config file taxonomy and no per-surface build setup.
A real dev loop. React Fast Refresh with state preservation in every HTML surface, soft-remounting content-script UIs, and automatic extension reloads when the service worker changes. You load the unpacked directory once.
How it compares
WXT is the most complete alternative and a genuinely good project. It is framework-agnostic (React, Vue, Svelte, and more), targets Chrome and Firefox across MV2 and MV3, and includes publishing tooling. Extro is deliberately narrower: Chrome MV3 and React only. That narrowness buys deeper conventions, chiefly file-based routing inside each surface, where WXT gives you one entrypoint per surface and you bring your own router. If you need Firefox or a non-React framework today, use WXT.
Plasmo pioneered React-first extension DX and popularized content-script UI. It is effectively unmaintained today: as of mid-2026 its last release shipped in May 2025, and hundreds of issues sit open. It is also built on Parcel, so the Vite ecosystem (plugins, aliases, env handling) does not apply. If you are on Plasmo and want a maintained, Vite-native framework with the same React-first instincts, Extro is built to be that, and it extends the file conventions into full routing rather than one component per surface.
CRXJS is a Vite plugin that takes your hand-written manifest.json and wires the build around it, including content-script HMR. It is a bundler layer, and a good one. Extro inverts the direction: the filesystem is the source of truth and the manifest is generated. It also layers a router, a scaffolder, and a CLI on top of the same Vite foundation.
When not to use Extro
- You need Firefox or Safari support now. Extro is Chrome MV3 only today.
- Your UI is not React.
- You want to own
manifest.jsonby hand and just need a bundler. CRXJS fits that shape better. - You need a stable 1.0 with a frozen API. See below.
Stability
Extro is pre-1.0. The public API is small and documented, but it can still change between minor versions; every change ships with a changeset and is listed in the GitHub releases. If you build on Extro today, pin a minor and read release notes when you bump. The path to 1.0, and what comes after it, is tracked in the pinned roadmap issue.