| name | wxt |
| description | Build cross-browser extensions with WXT — the modern framework for Chrome, Firefox, Safari, and Edge extensions. Use when someone asks to "build a browser extension", "Chrome extension with React", "WXT framework", "cross- browser extension", "manifest v3 extension", "build Firefox extension", or "browser extension with TypeScript". Covers content scripts, background workers, popup/options pages, storage, messaging, and publishing. |
| license | Apache-2.0 |
| compatibility | Chrome, Firefox, Safari, Edge. Supports React, Vue, Svelte, Solid. |
| metadata | {"author":"terminal-skills","version":"1.0.0","category":"development","tags":["browser-extension","chrome","firefox","wxt","manifest-v3"]} |
WXT
Overview
WXT is a Vite-based framework for building browser extensions — think "Next.js for extensions." File-based entrypoints, hot reload, TypeScript-first, and it outputs a single extension that works on Chrome (MV3), Firefox (MV2/MV3), Safari, and Edge. No more manually editing manifest.json or reloading the extension after every change.
When to Use
- Building a browser extension for Chrome, Firefox, or all browsers
- Want hot reload during development (not manual reload)
- Need TypeScript + React/Vue/Svelte in your extension
- Migrating from Manifest V2 to V3
- Want one codebase that targets multiple browsers
Instructions
Setup
npx wxt@latest init my-extension
cd my-extension
npm install
npm run dev
Project Structure
my-extension/
├── entrypoints/
│ ├── popup/ # Popup UI (click extension icon)
│ │ ├── index.html
│ │ ├── main.tsx
│ │ └── App.tsx
│ ├── options/ # Options page
│ │ ├── index.html
│ │ └── main.tsx
│ ├── content.ts # Content script (runs on web pages)
│ └── background.ts # Service worker (background logic)
├── public/
│ └── icon/
│ ├── 16.png
│ ├── 48.png
│ └── 128.png
├── wxt.config.ts
└── package.json
Content Script
export default defineContentScript({
matches: ["*://*.github.com/*"],
main() {
const prHeader = document.querySelector(".gh-header-actions");
if (prHeader) {
const btn = document.createElement("button");
btn.textContent = "🤖 AI Review";
btn.className = "btn btn-sm";
btn.onclick = async () => {
const diff = document.querySelector(".diff-view")?.textContent;
const review = await browser.runtime.sendMessage({
type: "REVIEW_PR",
diff: diff?.slice(0, 5000),
});
alert(review.summary);
};
prHeader.prepend(btn);
}
},
});
Background Service Worker
export default defineBackground(() => {
browser.runtime.onMessage.addListener(async (msg, sender) => {
if (msg.type === "REVIEW_PR") {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${await storage.getItem("local:apiKey")}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: `Review this diff:\n${msg.diff}` }],
}),
});
const data = await response.json();
return { summary: data.choices[0].message.content };
}
});
browser.alarms.create("check-notifications", { periodInMinutes: 5 });
browser.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === "check-notifications") {
const count = await checkNotifications();
if (count > 0) {
browser.action.setBadgeText({ text: String(count) });
}
}
});
});
Popup UI (React)
import { useState, useEffect } from "react";
export default function App() {
const [apiKey, setApiKey] = useState("");
const [saved, setSaved] = useState(false);
useEffect(() => {
storage.getItem<string>("local:apiKey").then((key) => {
if (key) setApiKey(key);
});
}, []);
const save = async () => {
await storage.setItem("local:apiKey", apiKey);
setSaved(true);
setTimeout(() => setSaved(false), 2000);
};
return (
<div style={{ width: 300, padding: 16 }}>
<h2>🤖 AI PR Reviewer</h2>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="OpenAI API Key"
style={{ width: "100%" }}
/>
<button onClick={save} style={{ marginTop: 8 }}>
{saved ? "✅ Saved!" : "Save Key"}
</button>
</div>
);
}
Build for Multiple Browsers
npm run dev
npm run dev:firefox
npm run build
npm run build:firefox
npm run zip
npm run zip:firefox
Examples
Example 1: Build a productivity extension
User prompt: "Build a Chrome extension that blocks distracting websites during focus time."
The agent will create a WXT extension with a popup for configuring blocked sites and focus timer, a content script that shows a block page on matched domains, and background alarms for timer management.
Example 2: Content enhancement extension
User prompt: "Build an extension that adds AI-powered summaries to any article page."
The agent will create a content script that detects article content, a floating sidebar UI, and background API calls to summarize text.
Guidelines
- File-based entrypoints — file name and location determine the extension component
browser.* API — WXT polyfills Chrome and Firefox differences automatically
storage helper — type-safe extension storage with storage.getItem/setItem
- Hot reload works —
npm run dev reloads content scripts and popup on save
- MV3 service workers — background scripts are service workers (no persistent state)
matches for content scripts — define URL patterns where scripts should inject
- Message passing — content script ↔ background communication via
browser.runtime.sendMessage
- One codebase, all browsers — build targets handle manifest differences
- Icons at 16/48/128px — required for Chrome Web Store