---
title: "Wxt vs Plasmo vs Extension.js: Browser Extension Frameworks in 2026"
author: "Nate Laquis"
author_role: "Founder & CEO"
date: "2029-06-01"
category: "Technology"
tags:
  - browser extension framework
  - Wxt vs Plasmo
  - Manifest V3 development
  - Chrome extension tooling
  - cross-browser extension development
excerpt: "The Manifest V3 migration forced most extension teams to rethink their tooling. Here is how Wxt, Plasmo, and Extension.js actually compare when you are shipping production extensions in 2026."
reading_time: "14 min read"
canonical_url: "https://kanopylabs.com/blog/wxt-vs-plasmo-vs-extension-js"
---

# Wxt vs Plasmo vs Extension.js: Browser Extension Frameworks in 2026

## The Manifest V3 Rebuild Wave Changed Everything

The browser extension market crossed $2 billion in 2025, and a huge chunk of that growth came with a catch: Google's Manifest V3 migration forced nearly every existing extension to rebuild core functionality. Background pages became service workers. The webRequest API gave way to declarativeNetRequest. Remote code execution was banned entirely. Teams that had been running stable Manifest V2 extensions for years suddenly had to rearchitect their projects from the ground up.

That rebuild wave did something unexpected. It created a genuine market for browser extension frameworks. Before MV3, most teams either hand-rolled their extension setup or relied on simple boilerplate repos. The migration pain was severe enough that three frameworks emerged as serious contenders: Wxt, Plasmo, and Extension.js. Each one takes a fundamentally different approach to solving the same set of problems.

![Developer writing code on a laptop with a modern code editor open](https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=800&q=80)

At Kanopy, we have built production extensions using all three frameworks across client projects. We have opinions about when each one shines, where each one falls short, and which one you should pick based on your team, your timeline, and your product requirements. This is not a feature matrix copied from documentation. It is a practical comparison based on shipping real extensions to real users.

## Framework Origins and Design Philosophy

Before comparing features, you need to understand what each framework was actually built to do. Their design philosophies are different, and those differences ripple through every decision you make during development.

### Wxt: The Vite-native, cross-browser toolkit

Wxt was created by Aaron Klinker as a next-generation extension framework built on top of Vite. Its core thesis is that extension development should feel exactly like modern web development: fast builds, hot module replacement, TypeScript by default, and a file-based project structure that mirrors how your extension actually runs. Wxt treats cross-browser support as a first-class concern, generating separate builds for Chrome, Firefox, Safari, and Edge from a single codebase. It also provides a unified browser API wrapper that smooths over the differences between Chrome's `chrome.*` namespace and Firefox's `browser.*` namespace.

### Plasmo: The React-first extension platform

Plasmo, built by the team behind the Plasmo company, takes a more opinionated stance. It is designed primarily for React developers and treats content script UI injection (what they call CSUI) as a core primitive. Plasmo also bundles a messaging system for communication between extension components (background, popup, content scripts, side panels) and offers a publishing tool called BPP (Browser Platform Publisher) that automates store submissions. If your team lives in the React ecosystem and your extension needs to inject complex UI into web pages, Plasmo was built specifically for that workflow.

### Extension.js: The zero-config runner

Extension.js, created by Cezar Augusto, has a radically different goal. It wants to eliminate configuration entirely. You can point Extension.js at any existing extension directory and it will run it with hot reload, regardless of how the project is structured. It supports React, Vue, Svelte, and plain JavaScript out of the box. Where Wxt and Plasmo want you to adopt their project structure, Extension.js works with whatever you already have. Think of it as the Parcel of extension frameworks: zero config, maximum flexibility, but with trade-offs in how much it can optimize for you.

## MV3 Compliance and Cross-Browser Support

If you are starting a new extension project in 2026, Manifest V3 is not optional. Chrome stopped accepting MV2 extensions entirely in late 2024, and Firefox has adopted its own flavor of MV3 (with some differences that matter). Safari uses a WebExtensions-compatible API but requires an Xcode wrapper for distribution. Any framework you choose needs to handle these realities cleanly.

### Wxt

Wxt generates fully MV3-compliant manifests by default. Its `wxt.config.ts` file lets you define manifest fields in a typed, composable way, and the build system produces separate output directories for each target browser. Cross-browser support is where Wxt genuinely excels. You write your code once, and Wxt handles the API polyfills, manifest format differences (Chrome uses `manifest.json` with MV3 syntax while Firefox still supports some MV2 fields), and output structure. Safari support requires the additional step of wrapping the output in an Xcode project, but Wxt's output is structured to make that straightforward.

### Plasmo

Plasmo also generates MV3 manifests by default and does it well. However, its cross-browser story is more limited. Plasmo was built Chrome-first, and while Firefox builds work, they require more manual configuration. Safari support is not a core focus, so you will need to handle the Xcode wrapper yourself. If your extension only needs to target Chrome and maybe Firefox, Plasmo covers you. If Safari is a hard requirement, expect extra work.

### Extension.js

Extension.js supports both MV2 and MV3, which is actually a strength for teams migrating legacy extensions. It reads your existing `manifest.json` as-is and does not try to generate or transform it. Cross-browser support exists but is more manual. You are responsible for maintaining browser-specific manifest differences yourself. Extension.js will run whatever you give it, but it will not write your manifest for you.

Our recommendation: if cross-browser support is a priority (and with Firefox holding 7% market share and Safari growing on iOS, it usually should be), Wxt's approach saves you the most time. If you are Chrome-only and plan to stay that way, Plasmo's Chrome-first design is not a weakness.

## Developer Experience: Setup, Hot Reload, and Daily Workflow

Developer experience is where these frameworks differ most dramatically. The daily workflow of building, testing, and iterating on an extension varies significantly across all three.

![Close-up of a monitor displaying programming code in a dark-themed IDE](https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=800&q=80)

### Project setup

Wxt uses a CLI scaffolding tool: `npx wxt@latest init my-extension`. It generates a clean project structure with directories for background, content scripts, popup, and other entrypoints. The structure is file-based, meaning a file at `entrypoints/popup/main.ts` automatically becomes your popup entrypoint. No manual manifest wiring required.

Plasmo also uses a CLI: `npm create plasmo`. It generates a React-based project with a `popup.tsx` file, a `contents/` directory for content scripts, and a `background.ts` file. Plasmo's convention is even more opinionated. File naming determines behavior: a file named `contents/inline.tsx` with the right export becomes an inline content script UI component automatically.

Extension.js takes the simplest approach: `npx extension create my-extension`. You pick a template (React, Vue, Svelte, or vanilla) and get a minimal project. Alternatively, you can run `npx extension dev path/to/existing-extension` on any extension directory, even one that was not built with Extension.js. This is genuinely useful for teams migrating legacy extensions that do not want to restructure their entire project.

### Hot reload

Wxt provides full HMR (hot module replacement) through Vite. When you change your popup code, it updates in the browser without a full reload. Content script changes trigger a targeted reload of just the affected tab. Background script changes require a full extension reload, but Wxt handles that automatically. The experience is fast. Sub-second updates on most changes.

Plasmo offers similar HMR capabilities with live reloading for popup and options pages. Content script hot reload works but can be inconsistent depending on the complexity of your CSUI components. Background script changes trigger a full extension reload.

Extension.js provides hot reload for all extension components, and it works well for a zero-config tool. The reload speed is slightly slower than Wxt's Vite-based approach, but the difference is small. The real advantage is that hot reload works even on extensions that were not built with Extension.js, which is something neither Wxt nor Plasmo can claim.

### TypeScript support

All three frameworks support TypeScript. Wxt and Plasmo include it by default with full type definitions for the browser extension APIs. Extension.js supports TypeScript but does not enforce it. If your team already has strong opinions about TypeScript (and in 2026, you should, as our [TypeScript vs JavaScript comparison](/blog/typescript-vs-javascript) explains), Wxt and Plasmo make it the default path. Extension.js lets you choose.

## Content Script Injection and Messaging

Content scripts are where extension development gets genuinely complicated. Injecting JavaScript and UI into third-party web pages, communicating between the injected code and your background service worker, and handling the dozens of edge cases (iframes, shadow DOM, CSP restrictions, page lifecycle events) is where most extension bugs live.

### Plasmo's CSUI: the standout feature

Plasmo's Content Script UI (CSUI) system is, frankly, the best content script injection tool available in any extension framework. You export a React component from a file in the `contents/` directory, add a config export specifying where to mount it (by CSS selector, URL pattern, or page event), and Plasmo handles the rest. It creates a shadow DOM container so your styles do not leak into the host page, mounts your React component inside it, and manages the lifecycle automatically. For extensions that need to add buttons to Gmail, overlays to LinkedIn, or sidebars to any webpage, CSUI eliminates hours of boilerplate.

Plasmo also ships a built-in messaging system. You define message handlers in your background script using `@plasmohq/messaging`, and content scripts can call them with type-safe functions. The messaging layer handles serialization, routing, and error handling. This is a real productivity boost compared to manually wiring up `chrome.runtime.sendMessage` and `chrome.runtime.onMessage` calls everywhere.

### Wxt's content script approach

Wxt handles content scripts through its file-based entrypoint system. You create a file at `entrypoints/content.ts` (or `content/index.ts`), export a `main` function, and define your matching patterns in the export config. For UI injection, Wxt provides a `createContentScriptUi` helper that supports shadow DOM isolation, but it is less automated than Plasmo's CSUI. You get more control but write more code. Wxt does not ship a built-in messaging abstraction, so you use the browser APIs directly or bring your own messaging library. This is fine for simple extensions but adds boilerplate for complex multi-component architectures.

### Extension.js and content scripts

Extension.js does not provide any content script abstractions. You write your content scripts the same way you would without a framework, using standard `manifest.json` declarations and vanilla DOM manipulation. If you are using React or Vue in your content scripts, you set up the mounting yourself. This is consistent with Extension.js's zero-config philosophy: it does not add opinions about how you structure your content scripts, but it also does not save you any work.

If content script injection is central to your extension, Plasmo's CSUI gives you a measurable speed advantage. We have seen it cut content script development time by 30 to 40 percent on projects where the extension needs to inject UI into multiple third-party sites.

## Testing, Publishing, and Production Readiness

Getting an extension to work locally is one thing. Shipping it to the Chrome Web Store, Firefox Add-ons, and the Safari App Store with automated testing and CI/CD is another thing entirely. This is where the frameworks diverge sharply.

![Dashboard analytics screen showing data visualizations and performance metrics](https://images.unsplash.com/photo-1504868584819-f8e8b4b6d7e3?w=800&q=80)

### Testing approaches

None of these frameworks ship a complete testing solution, which is worth acknowledging upfront. Extension testing is hard because you need a real browser environment to test most extension APIs.

Wxt integrates well with Vitest (since it is Vite-based) for unit testing business logic. For end-to-end testing, you pair it with Playwright or Puppeteer and load the built extension into a test browser instance. Wxt's clean output directory structure makes this straightforward. The team also maintains a `wxt/testing` module that provides mock utilities for browser extension APIs, which saves significant setup time.

Plasmo works with Jest or Vitest for unit tests. E2E testing follows a similar pattern: build the extension, load it into a Playwright browser, and run your test suite. Plasmo does not provide extension-specific testing utilities, so you mock the Chrome APIs yourself or use a library like `webextensions-api-fake`.

Extension.js does not provide testing utilities. Since it works with any project structure, you bring your own test framework and set up your own mocking. This is the trade-off of zero-config: the framework cannot make assumptions about how you want to test.

### Publishing automation

Plasmo's Browser Platform Publisher (BPP) is a genuine differentiator here. BPP automates submission to the Chrome Web Store, Firefox Add-ons, and Edge Add-ons from your CI/CD pipeline. You configure API keys, and BPP handles zipping, uploading, and submitting for review. For teams that ship updates frequently, this saves hours per release cycle. It is not perfect (Safari requires manual submission through Xcode regardless), but it covers the three major stores.

Wxt does not ship a publishing tool, but its multi-browser build output is structured for easy integration with `chrome-webstore-upload` and `web-ext` (Mozilla's CLI tool). You write the CI/CD scripts yourself, but the build output makes it straightforward.

Extension.js does not offer publishing automation. You handle builds and submissions manually or with third-party tools.

### Bundle size and performance

Wxt produces the smallest output bundles thanks to Vite's tree-shaking and code splitting. Plasmo's bundles are slightly larger due to its runtime for CSUI and messaging, but the difference is usually a few kilobytes. Extension.js bundles vary depending on your configuration, but since it does not add runtime abstractions, the output is close to what you would get from a manual Webpack or Vite setup.

## When to Pick Each Framework

After building with all three frameworks across multiple client projects, here is our honest recommendation for each one.

### Pick Wxt if:

- **Cross-browser support is a requirement.** Wxt's multi-browser build system is the most mature of the three. If you need Chrome, Firefox, and Safari from a single codebase, Wxt saves you significant time.

- **You want framework-agnostic UI.** Wxt works equally well with React, Vue, Svelte, or vanilla TypeScript. If your team uses Vue or Svelte, Wxt is the only serious option among these three.

- **Performance matters.** Vite-based builds produce the smallest bundles and the fastest HMR. If your extension runs on every page load (like an ad blocker or accessibility tool), every kilobyte counts.

- **You value long-term maintainability.** Wxt's project structure is clean and conventional. New developers can understand the codebase quickly. The file-based entrypoint system eliminates most manifest configuration errors.

### Pick Plasmo if:

- **Your team is React-native (the web framework, not the mobile one).** Plasmo's conventions, CSUI system, and messaging layer all assume React. If your team thinks in React components, Plasmo feels natural.

- **Content script UI is your core feature.** No other framework makes injecting React components into third-party pages as easy as Plasmo's CSUI. If your extension's value depends on overlaying UI onto Gmail, Salesforce, LinkedIn, or other web apps, Plasmo is the fastest path to production.

- **You want publishing automation.** BPP is a real time-saver for teams that ship weekly or biweekly updates across multiple browser stores. If you are planning an [extension with ongoing iteration](/blog/how-much-does-it-cost-to-build-a-chrome-extension), automated publishing pays for itself quickly.

- **You are Chrome-first.** If Chrome is your primary target and Firefox is a nice-to-have, Plasmo's Chrome-first design is an advantage, not a limitation.

### Pick Extension.js if:

- **You are migrating a legacy extension.** Extension.js can run any existing extension with hot reload, no restructuring required. This is invaluable for teams that need to modernize their dev workflow without rewriting their extension.

- **You want zero lock-in.** Extension.js does not impose a project structure or add runtime abstractions. If you decide to drop the framework later, your code works exactly the same way without it.

- **You are prototyping or experimenting.** The zero-config setup means you can go from idea to running extension in under a minute. For hackathons, proof-of-concepts, or quick experiments, Extension.js has the lowest barrier to entry.

- **You use a less common UI framework.** Extension.js supports any framework that compiles to standard web assets. If you are using Solid, Lit, or something else that Wxt and Plasmo do not officially support, Extension.js accommodates it.

## A Practical Side-by-Side: Setting Up a Tab Manager Extension

Theory is useful, but seeing actual setup differences makes the choice more concrete. Let us walk through creating a simple tab manager extension (popup with a list of open tabs, ability to close or group them) in each framework.

### Wxt setup

Run `npx wxt@latest init tab-manager --template vue` (or react, svelte, vanilla). Wxt creates a project with `entrypoints/popup/`, `entrypoints/background.ts`, and `wxt.config.ts`. You add the `tabs` permission in the config file, write your popup component, and run `npx wxt dev`. The extension loads in Chrome automatically with HMR. To build for Firefox, run `npx wxt build --browser firefox`. Total setup time: about 3 minutes.

### Plasmo setup

Run `npm create plasmo -- --with-popup tab-manager`. Plasmo generates a React project with `popup.tsx` and `package.json` that includes Plasmo-specific fields for permissions. You add `"permissions": ["tabs"]` to the package.json manifest section, write your React popup component, and run `npx plasmo dev`. The extension loads in Chrome with HMR. Total setup time: about 3 minutes. Building for Firefox requires adding a `--target=firefox-mv3` flag and potentially adjusting some manifest fields.

### Extension.js setup

Run `npx extension create tab-manager --template=react`. Extension.js generates a minimal project with a `manifest.json` you edit directly. Add the `tabs` permission, write your popup, and run `npx extension dev`. The extension loads with hot reload. Total setup time: about 2 minutes. The manifest editing is more manual, but the initial setup is the fastest of the three.

For a simple extension like a tab manager, all three frameworks get you to a working prototype in roughly the same time. The differences emerge as complexity grows: when you add content scripts, cross-component messaging, multiple browser targets, and automated publishing pipelines. That is where Wxt and Plasmo's abstractions start paying dividends, and where Extension.js's simplicity becomes a liability.

## Our Recommendation and Next Steps

If you are starting a new extension project in 2026 and your team uses React, start with Plasmo. Its CSUI and messaging systems will save you weeks of development time, and BPP handles the publishing headaches. If your team prefers Vue or Svelte, or if cross-browser support is non-negotiable, go with Wxt. Its Vite-based architecture and multi-browser build system are best in class. If you are migrating a legacy MV2 extension and need a development server today without restructuring your project, Extension.js gets you there fastest.

The framework choice matters, but it is not the hardest part of building a successful extension. The hard parts are designing a content script injection strategy that does not break on third-party site updates, building a service worker architecture that handles MV3's lifecycle constraints gracefully, and setting up a testing pipeline that catches real bugs before your users do. Those challenges exist regardless of which framework you pick.

At Kanopy, we help teams navigate the full lifecycle of [extension development](/blog/chrome-extension-vs-web-app-which-to-build), from framework selection through Chrome Web Store launch and beyond. If you are planning a browser extension and want expert guidance on architecture, framework choice, or MV3 migration, [book a free strategy call](/get-started) and let us help you build it right the first time.

---

*Originally published on [Kanopy Labs](https://kanopylabs.com/blog/wxt-vs-plasmo-vs-extension-js)*
