A Chrome extension boilerplate for Manifest V3 (MV3) — webpack 5 + React + TypeScript, with live reload powered by
webpack-ext-reloader-next, typed message passing, and a service-worker keepalive baked in.
A modern, opinionated webpack-based starter for building Chrome extensions (and Edge / Firefox / browser extensions in general) on Manifest V3. If you're more comfortable with webpack than Vite, or your existing toolchain is webpack-based, this is the starter you want.
- 🔧 Webpack 5 + ts-loader (
transpileOnly: true) — fast cold builds, filesystem cache for warm rebuilds. - ⚛️ React 18 + TypeScript (strict) — popup and options are real React apps with full TS support.
- 🔁 Live reload via
webpack-ext-reloader-next— rebuild triggers an automatic extension reload. No manualReloadclicks. - 📡 Typed messaging via
mv3-message-router— oneMessagesinterface, end-to-end typing in SW + popup + content. - ⏰ Durable scheduling via
mv3-keepalive—chrome.alarmsthat survive SW termination. - 📦
pnpm zip— packsdist/into a ready-to-upload.zip.
git clone https://github.com/graybearo/chrome-extension-webpack-react my-extension
cd my-extension
pnpm install
pnpm devThen in Chrome:
- Open
chrome://extensions/ - Toggle Developer mode on
- Click Load unpacked
- Select the
dist/folder
Edit any file in src/; the extension reloads automatically.
src/
├── background/index.ts # service worker — router + alarms live here
├── content/index.ts # injected into every https:// page
├── popup/ # React popup (action.default_popup)
├── options/ # React options page
├── shared/
│ ├── messages.ts # typed message contract
│ └── storage.ts # thin chrome.storage wrapper
└── manifest.json # MV3 manifest (Chrome / Edge)
Declare your messages once:
// src/shared/messages.ts
export interface Messages {
GET_STATE: { input: void; output: { count: number } };
INCREMENT: { input: { by: number }; output: { count: number } };
}Register handlers in the SW:
// src/background/index.ts
const router = createRouter<Messages>();
router.on("INCREMENT", async ({ by }) => { /* ... */ });
router.listen();Call from popup / content / options:
const client = createClient<Messages>();
const { count } = await client.send("INCREMENT", { by: 1 });
// ^? numberNo untyped chrome.runtime.sendMessage, no return true foot-guns, no
manual error serialization.
pnpm dev # watch mode + live reload
pnpm build # production bundle into dist/
pnpm zip # zip dist/ for web-store uploadDrop icon-16.png, icon-32.png, icon-48.png, icon-128.png into
public/icons/ and reference them in src/manifest.json. Everything in
public/ is copied verbatim to dist/.
Choose based on your team's stack:
- Webpack — mature, huge plugin ecosystem, integrates with existing webpack-based build infrastructure. This starter.
- Vite — faster HMR, smaller config. See
chrome-extension-vite-react.
Both starters share the same src/ layout and use the same
mv3-keepalive +
mv3-message-router
packages, so you can switch between them without rewriting your app code.
Part of a small MV3 toolkit for Chrome / Edge / Firefox extensions by @graybearo:
webpack-ext-reloader-next— live reload (used here)mv3-keepalive— service-worker keepalive + durable alarmsmv3-message-router— type-safe message passingmv3-content-bridge— content-script ↔ page-context typed bridgemv3-storage— typedchrome.storagewrappermv3-wait-for-element—waitForElementfor content scriptschrome-extension-vite-react— Vite version of this starterchrome-extension-vite-svelte— Svelte + Vite starterchrome-extension-side-panel— Side Panel API starter (Chrome 114+)awesome-mv3— curated list of MV3 tools, libraries, and resources
MIT — see LICENSE.