How-to guide
Chrome Extension Development Setup: Tooling, Hot Reload and Testing
How-to
Chrome Extension Development Setup: Tooling, Hot Reload and Testing
On this page
- The short answer
- What do you need to start Chrome extension development?
- How do you load and reload an extension while developing?
- Should you use WXT, Plasmo or plain Vite?
- How do you get hot reload for a Chrome extension?
- How do you debug each part of an extension?
- Which Chrome extensions help with web and extension development?
- How do you test a Chrome extension?
- How do you run tests and builds in CI?
- What should you check before publishing to the Chrome Web Store?
- Frequently asked
The short answer
For Chrome extension development, start with a Manifest V3 project, load it unpacked from chrome://extensions with Developer mode on, and add a framework such as WXT or Plasmo for TypeScript, hot reload and bundling. Test with Vitest for logic and Playwright for real browser behaviour, then run both in CI before publishing.
What do you need to start Chrome extension development?
You need Chrome, a code editor, Node.js and a manifest.json file. Nothing else is required to run an extension, because Chrome loads plain HTML, CSS and JavaScript directly from a folder.
Modern extensions use Manifest V3. The manifest declares the extension name, version, permissions and entry points: a background service worker, content scripts that run inside web pages, and UI pieces such as the popup, options page and side panel.
A minimal project is only a folder with three files. That is worth building once by hand before you pick a framework, because it shows you what the frameworks generate for you.
{
"manifest_version": 3,
"name": "Hello Extension",
"version": "0.1.0",
"action": { "default_popup": "popup.html" },
"background": { "service_worker": "background.js" },
"content_scripts": [
{ "matches": ["https://example.com/*"], "js": ["content.js"] }
],
"permissions": ["storage"]
}Keep permissions narrow from day one. Every host pattern and permission you add appears in the install warning, and reviewers and users both read it. Our permissions guide shows how those warnings look to a visitor.
Use Manifest V3
Chrome has been phasing out Manifest V2, so start new work on V3. The biggest difference in daily work is that the background page became a service worker that can be stopped and restarted, so it must not depend on in-memory state.
How do you load and reload an extension while developing?
Turn on Developer mode at chrome://extensions, click Load unpacked and pick your project folder. Chrome installs it immediately and keeps reading from that folder.
- Open
chrome://extensionsin Chrome. If you need a refresher, see our guide to enabling Developer mode in Chrome. - Switch on Developer mode in the top-right corner.
- Click Load unpacked and select the folder that contains
manifest.json(or your build output folder, such asdistor.output/chrome-mv3). - Pin the extension from the puzzle-piece menu in the toolbar so its icon stays visible.
- After you change code, click the circular Reload arrow on the extension card, then refresh any page where a content script runs.
Chrome shows an Errors button on the card when it finds problems, such as a bad manifest key or an uncaught exception. Read it first, because many failures are manifest typos.
Content scripts only attach to pages loaded after the extension is (re)loaded. If your change seems to do nothing, refresh the tab. That single step explains most where-did-my-change-go questions.
Use a separate development profile
Create a dedicated Chrome profile for extension work. It keeps test extensions away from your real logins, avoids clashes with policies on a work profile, and lets you reset state by deleting the profile.
Should you use WXT, Plasmo or plain Vite?
Use a framework once your extension has more than one entry point or needs TypeScript. WXT and Plasmo are the two common choices, and a Vite-based setup with a plugin such as CRXJS is the lighter alternative.
All of them solve the same chores: bundling each entry point, generating the manifest, hot reload while you edit, and packaging a zip for the store. They differ mainly in how much they decide for you. Check each project's own documentation for current features, since these tools change quickly.
| Tool | Approach | Good fit when | Main catch |
|---|---|---|---|
| WXT | Vite-based framework with file-based entry points and generated manifest. Works with several UI frameworks. | You want cross-browser builds and a framework-agnostic setup. | You adopt its folder conventions and config file. |
| Plasmo | Opinionated framework centred on React-style components and convention over configuration. | You want to move quickly on a React UI with minimal setup. | More of the build is hidden, so debugging tooling issues can take longer. |
| Vite plus a manifest plugin | Plain Vite with a plugin that reads your manifest and enables hot reload. | You want the smallest layer between you and the browser. | You wire up more yourself, including packaging and multi-browser support. |
| No build step | Hand-written JavaScript loaded directly. | You are writing a small single-purpose extension. | No TypeScript, no npm packages, no hot reload. |
If you are unsure, pick WXT or plain Vite for control and Plasmo for speed on a React project. Whichever you choose, keep business logic in plain modules that do not import browser APIs, so you can unit-test it without a browser.
How do you get hot reload for a Chrome extension?
Run your framework's dev command, which builds the extension into a folder and reloads it for you when files change. In WXT that is npm run dev, and in Plasmo it is also a dev script, and both usually open a Chrome window with the extension already loaded.
The reload behaviour differs by entry point. Popup and options pages can update in place. Content scripts typically reload and may need a page refresh. Changes to the manifest or the service worker often trigger a full extension reload, which briefly resets its state.
- Popup, options and side panel: update quickly, because they are ordinary pages.
- Content scripts: reinject on change, and some setups refresh the matching tab for you.
- Service worker: restarts on reload. Do not store state in globals; use
chrome.storageinstead. - Manifest changes: need a full reload of the extension, and permission changes may prompt again.
If hot reload stops working, check the terminal first for build errors, then open the extension's card in chrome://extensions and press Reload once by hand. A stale service worker or a crashed dev server are the usual causes.
Keeping a tab auto-refreshing during content-script work also saves clicks. Tools such as Tab Auto Refresh and Tab Reloader reload pages on a timer, which helps when you test scripts against pages that update on their own.
How do you debug each part of an extension?
Each part has its own DevTools window, and opening the wrong one is the most common debugging mistake. Console output from a content script appears in the page's DevTools, not in the extension's.
- Service worker
- On
chrome://extensions, click the service worker link on the extension card to open its DevTools. Logs, breakpoints and network requests from background code appear there. - Popup
- Right-click the extension icon and choose Inspect popup. The popup closes when it loses focus, so keep DevTools open while you work.
- Content script
- Open DevTools on the page, go to the Sources panel and find your script under the extension's entry in the content scripts area. Use the context selector in the Console to run code in the extension's isolated world.
- Options page and side panel
- Right-click inside the page and choose Inspect, as with any web page.
Use chrome.storage for state and check it in the Application panel of the relevant DevTools. Many bugs that look like broken messaging are actually a service worker that went idle and lost its variables.
Log the sender in message handlers
When content scripts and the service worker talk to each other, log the message type and the sender's tab or frame in the handler. It shortens the hunt for messages that never arrive.
Cookie problems are another common time sink when your extension deals with logins. Extensions such as Cookie-Editor let you inspect and edit a site's cookies while you reproduce the bug.
Which Chrome extensions help with web and extension development?
The most useful ones inspect what a page is doing or speed up repetitive checks. These fit the developer workflow described above, and each has a catch you should know about.
Helpful extensions for developers
-
Developer Tools
Inspect React component trees and props while you test a React-based popup or the pages you inject into. Free, and it only helps on React apps.
-
JSON FormatterFree
Developer Tools
Pretty-prints JSON in the browser, handy for checking API responses your extension calls. Free, and it changes how raw JSON is displayed.
-
ViolentmonkeyFree
Developer Tools
An open-source userscript manager. Try a content-script idea as a userscript before turning it into a full extension. Free, but userscripts run with fewer extension APIs.
-
WappalyzerFreemium
Developer Tools
Shows the tech stack behind a site, useful when you target pages built with a specific framework. Freemium, so some data sits behind a plan.
-
WhatFontFree
Design
Hover to identify any web font, useful for matching styles when you inject UI into another site. Free.
-
Clear CacheFree
Developer Tools
Clears cache and cookies in one click, so you can test a fresh-install state quickly. Free, and it removes data for sites you may want to stay logged in to.
-
LighthouseFree
Developer Tools
Runs Google Lighthouse audits for performance, accessibility and SEO on the current page, useful for checking the pages your extension injects UI into. Free, and it audits pages rather than your extension's own code.
See the wider set in the developer tools category or the extensions for developers page. Install these in your development profile only, since each one adds another set of permissions to the browser you test in.
How do you test a Chrome extension?
Test in two layers: unit tests for logic, and end-to-end tests that load the built extension into a real Chromium browser. The first runs in seconds; the second proves the manifest, permissions and content scripts really work.
For unit tests, use a runner such as Vitest or Jest, and mock the chrome API at the edge. Keep parsing, formatting and decision logic in modules that never touch chrome.*, so you rarely need mocks at all.
For end-to-end tests, Playwright and Puppeteer can launch Chromium with your extension. With Playwright you use a persistent context and pass the --disable-extensions-except and --load-extension flags pointing at your build folder.
import { chromium } from '@playwright/test';
import path from 'node:path';
const extensionPath = path.resolve('dist');
const context = await chromium.launchPersistentContext('', {
channel: 'chromium',
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
],
});
const page = await context.newPage();
await page.goto('https://example.com');
// assert on what your content script changedRecent branded Chrome builds stopped honouring the --load-extension flag, so run automated tests against Chromium or Chrome for Testing, not your everyday Chrome. If a flag change breaks your tests after an update, check the Playwright and Chrome extension documentation.
Test against pages you control
Run end-to-end tests against a local fixture page or your own staging site. Live third-party pages change without warning, and automated visits may break their terms.
How do you run tests and builds in CI?
Run the same commands you run locally: install, type-check, unit tests, build, then end-to-end tests against the build output. GitHub Actions or any CI runner with Node can do it.
- Install dependencies with a locked install command such as
npm ciso builds are reproducible. - Run the type check and linter as separate steps, so failures are easy to read.
- Run unit tests with your test runner.
- Build the extension and keep the output folder as an artifact.
- Install the browser your end-to-end tests use, then run them against the built folder.
- Zip the build output and upload it as the release artifact.
Extension tests need a real browser window on some setups. On Linux runners, wrap the test command in a virtual display such as xvfb-run if your browser mode requires one, or use a headless mode that supports extensions.
Publishing can be automated through the Chrome Web Store API with a service credential stored as a CI secret. Tools such as WXT include submit helpers, and community CLIs wrap the same API. Automate upload first and keep the final publish click manual until you trust the pipeline.
Keep secrets out of the repo
Store Web Store credentials in your CI provider's secret storage, never in the repository or in the extension bundle. Anything in the bundle is readable by every user.
What should you check before publishing to the Chrome Web Store?
Check the manifest, permissions, privacy disclosures and store listing, then upload a clean production build. Review time can vary, so leave slack before any launch date.
- Permissions: remove every permission and host pattern you do not use. Broad access invites slower review and fewer installs.
- Version: raise
versioninmanifest.jsonfor every upload, because the store rejects a repeat. - Privacy: be ready to explain what data the extension handles and why, and keep your listing consistent with what the code does.
- Remote code: Manifest V3 does not allow loading and running remotely hosted code, so bundle every script.
- Assets: prepare icons, screenshots and a clear description before you submit.
- Production build: test the zip you upload, not the dev build, on a clean profile.
Extension developers often scrape or read pages too. If that is part of your project, our web scraper use-case page covers the tools users already rely on, and the AI tools for developers roundup covers coding helpers. Official documentation on developer.chrome.com stays the source of truth for policy details.
Install your own build on a clean profile
Before submitting, load the exact zip on a fresh profile with no other extensions. It catches missing files, wrong paths and permissions that only worked because of something already installed.
Extensions mentioned in this guide
- React Developer ToolsInspect React component trees and props.
- JSON FormatterPretty-print JSON right in the browser.
- ViolentmonkeyOpen-source userscript manager that executes custom scripts on web pages.
- WappalyzerSee the tech stack behind any website.
- WhatFontHover to identify any web font.
- Clear CacheClear cache and cookies in one click.
- Cookie-EditorView, edit, create, and delete browser cookies for any site right from the toolbar
- Tab Auto RefreshReload any tab automatically on a custom timer you set
- LighthouseRuns Google Lighthouse audits for performance, accessibility and SEO on the current page
Frequently asked
How do I start developing a Chrome extension?
Create a folder with a manifest.json, then load it from chrome://extensions using Developer mode and Load unpacked. Add a framework later once you need TypeScript or several entry points.
What language are Chrome extensions written in?
HTML, CSS and JavaScript. TypeScript is popular because it compiles to JavaScript, and frameworks such as WXT and Plasmo support it out of the box.
Is WXT better than Plasmo?
Neither is better in every case. WXT is framework-agnostic and Vite-based, while Plasmo is more opinionated and centred on React. Choose by UI framework and how much configuration you want.
Does Chrome support hot reload for extensions?
Not by itself. Frameworks such as WXT and Plasmo, or Vite plugins, add it by rebuilding and reloading the extension when files change.
Why is my content script not running?
Check that the page URL matches the matches pattern, that you reloaded the extension, and that you refreshed the tab afterwards. Content scripts do not attach to pages that were already open.
How do I inspect the background service worker?
Open chrome://extensions and click the service worker link on your extension's card. That opens its DevTools with console, sources and network.
Why does my service worker lose its variables?
Manifest V3 service workers stop when idle and restart on demand, wiping in-memory state. Save state to chrome.storage instead.
How do I test a Chrome extension with Playwright?
Launch a persistent Chromium context with --disable-extensions-except and --load-extension pointing to your build folder, then drive pages as usual.
Can I run extension tests in GitHub Actions?
Yes. Install Node and the test browser, build the extension, then run your end-to-end tests. Use a virtual display like xvfb-run on Linux if your browser mode needs one.
Which web development extensions for Chrome are worth installing?
React Developer Tools, JSON Formatter, Wappalyzer and WhatFont are common picks for inspecting pages, data and fonts. Keep them in a development profile.
Do I need a developer account to test an extension?
No. Loading unpacked needs only Developer mode. You need a Chrome Web Store developer account when you want to publish.
Can I use npm packages in a Chrome extension?
Yes, through a bundler such as Vite, which WXT and Plasmo use. Bundle the code into your build, because Manifest V3 does not allow remotely hosted scripts.