CRXJS is a Vite plugin for Chrome extensions. If you have outgrown the Chrome-only scope, want first-class Firefox output, or hit reload-loop quirks, this guide migrates a typical CRXJS project to Extension.js without rewriting your UI code.Documentation Index
Fetch the complete documentation index at: https://extension.js.org/llms.txt
Use this file to discover all available pages before exploring further.
What changes, what stays
Stays the same: your React/Vue/Svelte components, your Tailwind config, your tests, yourchrome.* API calls.
Changes:
vite.config.ts+@crxjs/vite-pluginbecomesextension.config.js(or no config at all).manifest.config.ts(TypeScript module) becomes plainmanifest.jsonwith browser-prefixed keys.vite/vite buildscripts becomeextension dev/extension build.- The Vite dev server is replaced by Extension.js dev, which includes browser launch, profile management, and per-target reload.
Step 1: install Extension.js
Step 2: convert the manifest
CRXJS uses a TypeScript module:manifest.config.ts
manifest.json and reference real files:
manifest.json
- File extensions stay
.ts/.tsxin the manifest. Extension.js compiles them at build time. - Drop the
src/prefix if you flatten directories. Extension.js follows whatever paths your manifest declares. - For browser-specific values, use prefixed keys (
firefox:browser_specific_settings) rather than per-build manifests.
Step 3: update package.json scripts
Replace:Step 4: remove vite.config.ts
If yourvite.config.ts only existed to wire CRXJS, delete it. If it has other plugins, move equivalents to extension.config.js or to Rspack configuration for advanced bundler customization.
Step 5: handle HMR differences
CRXJS HMR pushes updates over a Vite WebSocket. Extension.js uses a different model documented in Reload and HMR:- Popup, options, devtools pages: HMR.
- Content scripts: targeted reload.
- Background service worker: full restart.
import.meta.hot for content-script logic, replace those branches with regular module code. Extension.js handles reload orchestration outside your source.
Step 6: cross-browser output
CRXJS targets Chromium. To start emitting Firefox output too:dist/chrome and dist/firefox with browser-correct manifests and .zip archives ready for the Chrome Web Store and addons.mozilla.org. See Cross-browser compatibility.
Step 7: verify
--polyfill if your code calls chrome.* and you want the same source to run on Firefox without changes.
Common gotchas
- Service worker
importstatements: Extension.js auto-setstype: "module"when your background entry uses ESM syntax. CRXJS did the same. No action needed. web_accessible_resourcestyping: Manifest V3 uses[{resources, matches}]blocks. Both frameworks emit the right shape; copy your existing entries verbatim.- Hashed asset paths: Extension.js uses stable paths under
dist/<browser>. If your code hardcoded Vite-style hashed filenames, replace with manifest-relative paths.

