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.
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.
npx wxt@latest init my-extension
cd my-extension
npm install
npm run dev # Opens Chrome with hot-reloading extension
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
// entrypoints/content.ts — Runs on matched web pages
/**
* Content scripts have access to the DOM of the page.
* Define which URLs to match with the `matches` export.
*/
export default defineContentScript({
matches: ["*://*.github.com/*"], // Run on GitHub pages
main() {
// Add a custom button to every GitHub PR page
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;
// Send to background for API call
const review = await browser.runtime.sendMessage({
type: "REVIEW_PR",
diff: diff?.slice(0, 5000),
});
alert(review.summary);
};
prHeader.prepend(btn);
}
},
});
// entrypoints/background.ts — Persistent background logic
/**
* Service worker handles API calls, alarms, and message routing.
* No DOM access here — communicate with content scripts via messaging.
*/
export default defineBackground(() => {
// Handle messages from content scripts
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 };
}
});
// Periodic tasks with alarms
browser.alarms.create("check-notifications", { periodInMinutes: 5 });
browser.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === "check-notifications") {
// Check for updates, show badge
const count = await checkNotifications();
if (count > 0) {
browser.action.setBadgeText({ text: String(count) });
}
}
});
});
// entrypoints/popup/App.tsx — Extension popup with 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>
);
}
# Development (Chrome with hot reload)
npm run dev
# Development for Firefox
npm run dev:firefox
# Build for all browsers
npm run build # Chrome MV3
npm run build:firefox # Firefox MV2/MV3
# Zip for store submission
npm run zip
npm run zip:firefox
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.
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.
browser.* API — WXT polyfills Chrome and Firefox differences automaticallystorage helper — type-safe extension storage with storage.getItem/setItemnpm run dev reloads content scripts and popup on savematches for content scripts — define URL patterns where scripts should injectbrowser.runtime.sendMessagenpx skills add TerminalSkills/wxt下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer