plasmo and wxt are build tools designed to simplify browser extension development. They handle cross-browser compatibility, bundling, and hot reloading. plasmo acts as a full framework with strong conventions, especially for React. wxt functions as a toolkit built on Vite, offering flexibility for various frontend frameworks.
Both plasmo and wxt aim to solve the same problem โ browser extension development is historically painful. They abstract away manifest management, cross-browser quirks, and bundling complexity. However, they take different approaches to structure and flexibility. Let's compare how they handle real engineering tasks.
plasmo uses a dedicated config file with strong defaults.
plasmo.config.ts.// plasmo: plasmo.config.ts
import type { PlasmoConfig } from "plasmo";
const config: PlasmoConfig = {
manifest: {
permissions: ["storage", "tabs"]
}
};
export default config;
wxt uses a Vite-based config with modular entrypoints.
wxt.config.ts.entrypoints folder.// wxt: wxt.config.ts
import { defineConfig } from "wxt";
export default defineConfig({
manifest: {
permissions: ["storage", "tabs"]
}
});
plasmo treats content scripts as modules with exported config.
content.ts file.// plasmo: content.ts
export const config = {
matches: ["<all_urls>"]
};
export default async function main() {
const container = document.createElement("div");
container.id = "my-app";
document.body.appendChild(container);
// Render React/Vue app into container
}
wxt uses a definition function for content scripts.
entrypoints/content.ts.defineContentScript to set matches and logic.// wxt: entrypoints/content.ts
import { defineContentScript } from "wxt/sandbox";
export default defineContentScript({
matches: ["<all_urls>"],
async main(ctx) {
const container = document.createElement("div");
container.id = "my-app";
document.body.appendChild(container);
// Render app into container
}
});
plasmo uses a dedicated background.ts file.
// plasmo: background.ts
export default function Background() {
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
console.log("Tab updated", tabId);
});
}
wxt uses a definition function in the entrypoints folder.
entrypoints/background.ts.// wxt: entrypoints/background.ts
import { defineBackground } from "wxt/sandbox";
export default defineBackground({
type: "module",
main() {
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
console.log("Tab updated", tabId);
});
}
});
plasmo uses single-file components for UI surfaces.
popup.tsx file becomes your extension popup.// plasmo: popup.tsx
import { useState } from "react";
export default function Popup() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
wxt separates HTML and logic in the entrypoints folder.
entrypoints/popup/index.html.<!-- wxt: entrypoints/popup/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Popup</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>
plasmo handles browser targets via command flags.
# plasmo: Build for Firefox
plasmo build --target=firefox-mv2
wxt handles browser targets via config or flags.
# wxt: Build for Firefox
wxt build --browser firefox
While the differences are clear, both tools share many core ideas and capabilities. Here are key overlaps:
// Both: Development server handles updates
// No extra config needed for basic HMR
manifest.json automatically.// Both: Define permissions in config
// plasmo.config.ts or wxt.config.ts
permissions: ["storage"]
// Both: Typed Chrome API
chrome.storage.local.get({ key: "value" }, (result) => {
// result is typed
});
// Both: Import assets directly
import logo from "~/assets/logo.png";
# Both: Build for production
plasmo build
wxt build
| Feature | Shared by Plasmo and Wxt |
|---|---|
| Core Tech | โก Vite-based bundling |
| Language | ๐ TypeScript first |
| Manifest | ๐ Auto-generated from config |
| Development | ๐ Hot Module Replacement |
| Assets | ๐ผ๏ธ Automatic bundling |
| Targets | ๐ Chrome, Firefox, Edge, Safari |
| Feature | plasmo | wxt |
|---|---|---|
| Philosophy | ๐๏ธ Opinionated Framework | ๐งฐ Flexible Toolkit |
| Config File | plasmo.config.ts | wxt.config.ts |
| Content Scripts | Export config object | defineContentScript function |
| UI Structure | Single file components | HTML + TS entrypoints |
| Framework Focus | React-first (supports others) | Framework-neutral (Vite plugins) |
| Background | background.ts function | defineBackground function |
plasmo is like a pre-fabricated house ๐ โ great for teams that want to move fast with React and prefer standard structures. Ideal for startups, marketing extensions, and teams already invested in the React ecosystem.
wxt is like a custom build kit ๐ ๏ธ โ perfect for teams who want control over every file and use various frameworks. Shines in complex enterprise extensions, multi-framework repos, and projects needing specific Vite plugins.
Final Thought: Despite their different approaches, both tools make extension development significantly easier than the vanilla workflow. Choose based on your team's framework preference and desire for convention versus control.
Choose plasmo if your team uses React heavily and prefers a structured framework with built-in UI components for extension surfaces. It is ideal for projects that benefit from convention over configuration and need a stable, opinionated setup for content scripts and popups.
Choose wxt if you want Vite-based speed, support for multiple frameworks like Vue or Svelte, and fine-grained control over entrypoints. It is suitable for teams that value modular architecture and want to leverage the wider Vite plugin ecosystem.
English | ็ฎไฝไธญๆ | Tiแบฟng Viแปt | Deutsch | French | Indonesian | ะ ัััะบะธะน | Turkish | ๆฅๆฌ่ช | ํ๊ตญ์ด
Production Cloud: We've built a cloud offering for browser extensions called Itero. Check it out if you want instant beta testing and more awesome features.
The Plasmo Framework is a battery-packed browser extension SDK made by hackers for hackers. Build your product and stop worrying about config files and the odd peculiarities of building browser extensions.
It's like Next.js for browser extensions!

.env* filesAnd many, many more! ๐
We have examples showcasing how one can use Plasmo with Firebase Authentication, Redux, Supabase authentication, Tailwind, and many more. To check them out, visit our examples repository.
Check out the documentation to get a more in-depth view into the Plasmo Framework.
For a more in-depth view into how browser extensions work, and how to develop them, we highly recommend Matt Frisbie's new book "Building Browser Extensions"
pnpm create plasmo example-dir
cd example-dir
pnpm dev
The road ahead is filled with many turns.
popup.tsxoptions.tsxcontent.tsbackground.tsYou can also organize these files in their own directories:
ext-dir
โโโโassets
| โโโโicon.png
โโโโpopup
| โโโโindex.tsx
| โโโโbutton.tsx
โโโโoptions
| โโโโindex.tsx
| โโโโutils.ts
| โโโโinput.tsx
โโโโcontents
| โโโโsite-one.ts
| โโโโsite-two.ts
| โโโโsite-three.ts
...
Finally, you can also avoid putting source code in your root directory by putting them in a src sub-directory, following this guide. Note that assets and other config files will still need to be in the root directory.
To see a list of supported browser targets, please refer to our documentation here.
The Plasmo community can be found on Discord. This is the appropriate channel to get help with using the Plasmo Framework.
Our Code of Conduct applies to all Plasmo community channels.
Please see the contributing guidelines to learn more.
A big thanks to all of our amazing contributors โค๏ธ
Feel free to join the fun and send a PR!
Plasmo is currently alpha software, and some things might change from version to version, so please be mindful and use it at your own risk.