You can create a new repository using this template by using the binary below:
npx starter-web-extension
# or
yarn create starter-web-extension- React 18
- React
useReducer(reducer-based state management) - Google Chrome Extension Manifest v3
- HeroUI
- Esbuild
- TypeScript
- Tailwind CSS (via PostCSS)
- Use Node > 22.7.0.
- Clone/Download this repository.
- Replace package's
name,descriptionandrepositoryfields inpackage.json. - Replace the name of extension in
src/manifest.json.
- Run
yarn installto install dependencies. - Run
yarn startto start the development server. - Go to
chrome://extensions/and enableDeveloper mode. - Click on
Load unpackedand select thebuildfolder.
- Chrome: open
chrome://extensions→ enable Developer mode → Load unpacked → selectbuild/ - Edge: open
edge://extensions→ enable Developer mode → Load unpacked → selectbuild/ - Brave: open
brave://extensions→ enable Developer mode → Load unpacked → selectbuild/
Tip: while yarn start is running, UI pages hot-reload. For manifest or background changes, click the extension’s Reload button in the extensions page.
We are using esbuild, which allows for lightning fast builds. As well as hot reloading in development.
yarn build # production build
yarn start # development live reload server
Code of all the pages, popups and options are inside src/pages folder. Each page has its own folder with the following structure:
src
└── components
└── Used for shared components between pages
└── pages
└── background
└── Used for background service workers (MV3) `https://developer.chrome.com/docs/extensions/mv3/service_workers/`
└── content
└── Used for content scripts `https://developer.chrome.com/docs/extensions/mv3/content_scripts/`
└── options
└── Used for options page `https://developer.chrome.com/docs/extensions/mv3/options/`
└── popup
└── Used for popup page `https://developer.chrome.com/docs/extensions/mv3/user_interface/`
└── panel
└── Used for DevTools integration `https://developer.chrome.com/docs/extensions/reference/devtools_panels/`
└── newtab
└── Used for overriding new tab page
└── manifest.json
└── Defines the extension manifest `https://developer.chrome.com/docs/extensions/mv3/manifest/`
Template file is located in .env.example. You can create two files for environment variables:
.env.localfor development (yarn startloads this via dotenv).envfor production builds (yarn buildloads this via dotenv)
Recognized variables:
PORT– dev server port for esbuild serve and hot-reload overlay (default 3000)ESBUILD_EVENT_SOURCE– override the EventSource URL for hot reload. Defaults tohttp://localhost:${PORT}/esbuildNODE_ENV– optional, influences minification/sourcemaps; when not set,yarn startruns in development mode
Example .env.local:
PORT=3000
# ESBUILD_EVENT_SOURCE=http://localhost:3000/esbuild
For production builds, place variables in .env.
This extension includes a sophisticated middleware system built with React's useReducer for managing Chrome extension communication between content scripts and React components.
- State Management: Centralized state management using
useReducer - Type Safety: Full TypeScript support for all actions and state
- Error Handling: Comprehensive error states and loading indicators
- React Integration: Easy integration with React components via context
- Chrome Extension Communication: Seamless message passing between content scripts and React components
- Modern Architecture: Pure
useReducerimplementation with no legacy code - Cross-Context Support: Works in both extension pages (popup, options) and regular web pages
The middleware system automatically detects its context and behaves accordingly:
- Detects
chrome-extension:protocol - Components should send messages to the active tab's content script using
chrome.tabs.sendMessage()(see examples below) - Perfect for controlling web pages from extension UI
- Detects regular web page context
- Listens for messages using
chrome.runtime.onMessage.addListener() - Executes DOM manipulation and responds to extension commands
import { MiddlewareProvider } from "./content/modules/middleware";
function App() {
return (
<MiddlewareProvider>
<YourComponents />
</MiddlewareProvider>
);
}import { useMiddleware } from "./content/modules/middleware";
function MyComponent() {
const { state, actions } = useMiddleware();
const handleSendColors = () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]?.id) {
chrome.tabs.sendMessage(tabs[0].id, {
type: "POST_COLORS",
colors: ["#ff0000", "#00ff00", "#0000ff"],
});
}
});
};
return (
<div>
<p>Loading: {state.isLoading ? "Yes" : "No"}</p>
<p>Error: {state.error || "None"}</p>
<p>Color Boxes: {state.colorBoxes.length}</p>
<button onClick={handleSendColors}>Send Colors</button>
<button onClick={actions.clearColorBoxes}>Clear Boxes</button>
</div>
);
}setLoading(isLoading: boolean)- Set loading statesetError(error: string)- Set error messagesetSuccess(response: ContentMessageResponse)- Set success responseaddColorBox(id: string, color: string, element: HTMLElement)- Add color box to stateremoveColorBox(id: string)- Remove color box by IDclearColorBoxes()- Clear all color boxes
interface MiddlewareState {
isLoading: boolean;
error: string | undefined;
lastResponse: ContentMessageResponse | undefined;
colorBoxes: Array<{
id: string;
color: string;
element: HTMLElement;
}>;
}GET_BODY- Retrieve document title and HTML body content from the active web pagePOST_COLORS- Send color array to content script for rendering color boxesCLEAR_BOXES- Clear all color boxes from the page
- Retrieves the current web page's title and HTML content
- Useful for page analysis, content extraction, or debugging
- Shows page title and body length in the UI
- Works on any website where the content script is injected
- Creates colored boxes on the web page
- Each color becomes a 32x32px box positioned at the top
- Clears tracked boxes in state before adding new ones; to remove existing DOM boxes, send a
CLEAR_BOXESmessage first - Great for visual testing and page manipulation
- Removes all color boxes from the web page
- Cleans up both DOM elements and React state
- Useful for resetting the page state
The middleware is demonstrated in:
- Popup: Shows middleware state and provides buttons to test functionality
- Options: Settings UI (wrapped by
MiddlewareProvider)
-
src/pages/devtools/– Provides DevTools integrationindex.html– HTML loaded asdevtools_pageby Chromeindex.ts– Runs in DevTools context and creates a panel viachrome.devtools.panels.create()
-
src/pages/panel/– Regular extension page (popup/options style)index.tsx– React entry pointpanel.tsx– Functional interface component
- Already configured:
- Build includes
src/pages/devtools/index.tsand producesdevtools/devtools.html - Manifest contains
"devtools_page": "devtools/devtools.html"
- Build includes
- The default DevTools script creates a panel that points to the existing panel UI at
panel/panel.html.
To point the DevTools panel at a different UI, change the third argument of chrome.devtools.panels.create() in src/pages/devtools/index.ts.
- Load the extension → Open Chrome DevTools (F12) → Look for the extension panel
If you add additional entry points, ensure unique outputs in the esbuild config.
- Run Playwright tests:
yarn test:e2e - In Docker:
yarn test:dockeror update snapshots withyarn test:docker:update-snapshots