Disclosure: We maintain CRXJS and have contributed 27+ pull requests to the project. This comparison is based on our production experience with all three tools. We have done our best to be fair and accurate, but you should know where we stand.
Table of Contents
1. Why you need a framework at all
Building a Chrome extension from scratch is surprisingly painful. You need to manually manage a manifest.json that references every script, page, and asset. You need a build system that outputs the right file structure for Chrome to load. You get no hot module replacement — every change means manually reloading the extension and refreshing the test page. And if you want to use React, Vue, or Svelte in your popup or content scripts, you are on your own for configuring the bundler.
This is not just inconvenient — it is slow. A vanilla extension workflow adds 30-60 seconds per iteration: save, build, reload extension, reload page, check results. At 50+ iterations per day, that is nearly an hour lost to tooling friction.
All three tools — CRXJS, Plasmo, and WXT — solve these problems. They all provide:
- -Hot module replacement (HMR) so you see changes instantly without reloading
- -Build pipelines that output correct extension file structures
- -Framework support (React, Vue, Svelte) out of the box
- -Manifest V3 compatibility
- -Development server with automatic extension reloading
Where they differ is in philosophy: how much abstraction to add on top of the Chrome APIs, what bundler to use, and how opinionated the developer experience should be. That difference matters more than you might think.
2. CRXJS: the Vite plugin approach
CRXJS is not a framework — it is a Vite plugin. Created by Jack and Amy, it has 3.9k+ GitHub stars and takes a deliberately minimal approach: it reads your manifest.json, understands Chrome extension structure, and wires up Vite to build everything correctly. You write standard Chrome extension code. CRXJS just makes the build and development experience fast.
How CRXJS works
You create a normal Vite project with a manifest.json at the root. CRXJS reads the manifest and handles:
- -Content script bundling and injection with full HMR support
- -Background service worker compilation
- -Popup and options page builds
- -Public asset copying and manifest generation
- -Automatic extension reloading on changes
The key philosophy: CRXJS does not abstract away the Chrome APIs. You still use chrome.runtime.sendMessage, chrome.storage, and chrome.tabs directly. Your manifest is a real manifest. Your content scripts are real content scripts. CRXJS is the build tool, not the architecture.
Strengths
- -Closest to the metal — no abstractions between you and Chrome APIs
- -Full control over architecture and patterns
- -Excellent HMR, including in content scripts injected into live pages
- -Supports React, Vue, and Svelte
- -Vite ecosystem — supports Vite 3 through 8, access to all Vite plugins
- -Manifest V3 native
- -Active maintenance — we contribute directly and use it in production daily
Weaknesses
- -Requires solid Chrome API knowledge — CRXJS does not teach you how extensions work
- -Less opinionated — you make more architectural decisions yourself
- -No built-in messaging abstraction or storage wrapper
- -Cross-browser support (Firefox, Safari) is not a primary focus
CRXJS is for teams that want Vite's speed and ecosystem without giving up control over how their extension works. If you know Chrome APIs well (or are willing to learn them), CRXJS stays out of your way and lets you build exactly what you need.
3. Plasmo: the full framework approach
Plasmo takes the opposite approach from CRXJS. With ~10k GitHub stars, it is the most popular of the three and positions itself as a full framework for browser extension development. Where CRXJS says “here is a better build tool,” Plasmo says “here is a better way to build extensions.”
How Plasmo works
Plasmo uses convention-over-configuration. You create files in specific directories and Plasmo generates the manifest and build output automatically:
- -
popup.tsxbecomes your popup page — no manifest entry needed - -
content.tsxbecomes a content script with CSUI (Content Script UI) framework - -Built-in
@plasmohq/messagingfor type-safe messaging between contexts - -Built-in
@plasmohq/storagefor reactive storage with hooks - -Manifest is auto-generated from your code and config
The CSUI framework is Plasmo's most distinctive feature. It handles mounting React components into web pages via content scripts, managing Shadow DOM isolation, and providing a component lifecycle that is aware of the host page. For simple content script UIs, this is genuinely convenient.
Strengths
- -Fastest time from zero to a working extension
- -Good documentation with plenty of examples
- -Built-in messaging and storage abstractions save boilerplate
- -CSUI framework for content script React components
- -Largest community (most GitHub stars, most Stack Overflow answers)
- -Great for prototyping and MVPs
Weaknesses
- -Abstractions can fight you on complex projects — when CSUI does not handle your injection pattern, working around it is harder than building from scratch
- -Lock-in to Plasmo's patterns — migrating away means rewriting messaging, storage, and content script injection
- -Custom Parcel-based bundler, not Vite — smaller plugin ecosystem, less community tooling compatibility
- -Debugging is harder when abstractions leak — error messages reference Plasmo internals, not your code
- -Auto-generated manifest means less visibility into what Chrome actually sees
Plasmo is excellent for getting started quickly and for extensions where the built-in patterns match your requirements. The challenge comes when you outgrow those patterns — and in our experience, complex production extensions often do. That said, for many projects, Plasmo's abstractions are exactly right, and the productivity gains are real.
4. WXT: the Nuxt-inspired approach
WXT is the newest of the three, with ~5k GitHub stars and growing fast. Inspired by Nuxt, it combines file-based conventions with Vite under the hood. Think of it as a middle ground: more structure than CRXJS, less abstraction than Plasmo.
How WXT works
WXT uses a file-based routing system for extension entry points. Drop files into the right directories and WXT wires everything up:
- -
entrypoints/popup.tsxbecomes your popup - -
entrypoints/background.tsbecomes your service worker - -
entrypoints/content.tsbecomes a content script - -Auto-imports for common APIs (no manual import statements)
- -Cross-browser support built in — build for Chrome, Firefox, and Safari from one codebase
WXT's standout feature is cross-browser support. While CRXJS and Plasmo focus primarily on Chrome (with varying levels of Firefox support), WXT treats multi-browser builds as a first-class feature. You write your extension once and WXT outputs builds for Chrome, Firefox, and Safari with the correct manifest format for each.
Strengths
- -Best cross-browser support of the three (Chrome, Firefox, Safari)
- -Vite under the hood — access to the full Vite plugin ecosystem
- -File-based conventions are intuitive if you have used Nuxt or Next.js
- -Auto-imports reduce boilerplate
- -Good developer experience with clear documentation
- -Growing community with active maintenance
Weaknesses
- -Newer ecosystem — fewer community resources and third-party guides
- -Some advanced patterns (complex content script injection, multi-page communication) are not yet as battle-tested
- -File-based conventions can feel restrictive for non-standard extension architectures
- -Smaller community means fewer answered questions when you hit edge cases
WXT is a strong choice that is maturing quickly. If cross-browser support is important for your project — and it should be if you are targeting enterprise users who may use Firefox or Safari — WXT is the most straightforward path. The Nuxt-inspired conventions also make it approachable for teams coming from web development.
5. Side-by-side comparison
Here is how the three tools stack up across the dimensions that matter most in production extension development:
| CRXJS | Plasmo | WXT | |
|---|---|---|---|
| Type | Vite plugin | Full framework | Toolkit (Nuxt-style) |
| GitHub Stars | ~3.9k | ~10k | ~5k |
| Bundler | Vite (3–8) | Parcel (custom) | Vite |
| Framework Support | React, Vue, Svelte | React, Vue, Svelte | React, Vue, Svelte |
| Manifest | You write it | Auto-generated | Auto-generated |
| HMR | Excellent | Good | Good |
| Cross-browser | Chrome-focused | Chrome + Firefox | Chrome + Firefox + Safari |
| Messaging API | Use Chrome APIs | Built-in abstraction | Use Chrome APIs |
| Storage API | Use Chrome APIs | Built-in abstraction | Built-in utility |
| Abstraction Level | Low (plugin) | High (framework) | Medium (toolkit) |
None of these tools is universally “better.” They are optimized for different situations and different teams. The right choice depends on your project's requirements, your team's experience with Chrome APIs, and how much control you need over the extension architecture.
6. When to use what
After shipping production extensions with all three tools, here is our honest recommendation for when each one makes the most sense:
Use CRXJS when:
- -You need maximum control over your extension architecture
- -You are building complex content script injection (SPAs, dynamic pages, multi-mount)
- -Your team has Chrome API experience or is willing to invest in learning
- -You want to use Vite and its plugin ecosystem directly
- -You are building a long-lived production extension that will evolve over years
- -You want to avoid framework lock-in — migrating away from CRXJS means swapping a Vite plugin, not rewriting your app
Use Plasmo when:
- -You need to prototype or ship an MVP quickly
- -Your extension has a straightforward architecture (popup + simple content script)
- -You want built-in messaging and storage abstractions to reduce boilerplate
- -Your team is new to Chrome extension development and wants guardrails
- -You value a large community and lots of existing tutorials
Use WXT when:
- -Cross-browser support (Chrome + Firefox + Safari) is a hard requirement
- -You like Nuxt-style file-based conventions
- -You want Vite under the hood but with more structure than CRXJS provides
- -Your team comes from a Nuxt or Next.js background
- -You want auto-imports and a more opinionated project structure without full framework lock-in
7. Our take
We use CRXJS for our production work. The reason is specific to what we build: complex extensions that inject React UIs into third-party SPAs, handle dynamic content script mounting across client-side route changes, and need fine-grained control over how and when scripts execute. For this kind of work, we need to be close to the Chrome APIs, and we need a build tool that stays out of the way.
We are also contributors to CRXJS, which gives us a practical advantage: when we hit a bug or need a feature, we can fix it upstream. We have contributed 27+ pull requests including cross-platform CI/CD, Vite 8 support, HMR fixes for CSS in content scripts, and Windows development support. This means our clients get fixes faster than waiting for a third-party release.
That said, we are not dogmatic about it. For a client who needs a simple popup extension shipped in a week, Plasmo's built-in abstractions would save time. For a client who needs the same extension on Chrome, Firefox, and Safari, WXT's cross-browser builds would be the obvious choice.
The best tool is the one that matches your constraints. Here is a quick decision framework:
- 1.Start with your complexity. Simple extension? Plasmo will get you there fastest. Complex content script injection? CRXJS gives you the control.
- 2.Check your browser requirements. Chrome only? Any tool works. Chrome + Firefox + Safari? WXT is purpose-built for this.
- 3.Consider your team. New to extensions? Plasmo's guardrails help. Experienced with Chrome APIs? CRXJS will feel natural. Coming from Nuxt? WXT will be familiar.
- 4.Think about lock-in. CRXJS is a Vite plugin — easy to remove. Plasmo's abstractions are deeper — harder to migrate. WXT sits in between.
All three tools are actively maintained, production-ready, and a massive improvement over building extensions without any tooling. You cannot go badly wrong with any of them. The differences are in the edges — and those edges matter most when your extension grows in complexity.
Need help choosing or building?
We have shipped production extensions with CRXJS, Plasmo, and WXT. Whether you need help picking the right tool or want us to build the extension for you, we can help.
