Building a production-grade browser extension today is very different from the early days of Manifest V2. Modern extensions require proper architecture, tooling, automation, and a clear understanding of how different contexts communicate.
This playbook walks through the entire lifecycle — from ideation to deployment and long-term maintenance.
Phase 0: Foundation & Ideation
Before writing any code, clarify the purpose and interaction model of your extension.
1. Define the Core Problem
Identify the exact problem you’re solving.
Vague: “Helps users save articles.”
Precise: “Saves the active tab’s article to a backend read-later service with one click.”
You need that level of clarity to avoid feature creep.
2. Choose the Extension Type
Extensions can expose multiple UX surfaces:
- Popup (Browser Action): The toolbar icon opens a small UI.
- Page Action: Rare now — appears only on specific pages.
- Content Script: Injected into websites, modifies or reads page content.
- Background Script (Service Worker): Manages events, permissions, and state.
- Options Page: Full settings page.
- DevTools Panel: Custom tools integrated into browser DevTools.
- Override Pages: Replace the New Tab page, history page, etc.
3. Map the User Flow
Sketch the entire user journey:
Install → Grant permissions → Use popup → Trigger action → View result.
This defines your UI, architecture, and data flow.
4. Research the Competition
Audit extensions solving similar problems. Identify gaps. Don’t copy; out-execute.
Phase 1: Choose the Right Tech Stack
Plain JavaScript and manual builds slow you down. A modern extension needs a modern stack.
⭐ Recommended Stack
| Category | Recommendation | Rationale |
|---|---|---|
| Build Tool | Vite | Fast HMR, stable output, minimal config. |
| Framework | React / Vue / Svelte | Component-based, scalable, familiar. |
| Vite Plugin | @crxjs/vite-plugin | Handles Manifest V3, HMR, bundling, and polyfills. |
| Language | TypeScript | Mandatory for maintainability. |
| Browser API Wrapper | webextension-polyfill | One API that works across Chrome, Firefox, Edge, Safari. |
| Styling | Tailwind CSS | Fast UI iteration, removes CSS boilerplate. |
| Component Library | Shadcn/UI or Radix | Accessible primitives for popup UIs. |
Recommended Project Structure
my-extension/
├── public/
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
├── src/
│ ├── background/ # Service Worker
│ ├── content/ # Content Scripts
│ ├── popup/ # Popup UI
│ ├── options/ # Settings page
│ ├── components/ # Shared UI components
│ ├── lib/ # Storage, API, helpers
│ └── manifest.json
├── package.json
└── vite.config.ts
markdown Copy code
This layout avoids chaos later when you add more surfaces (DevTools, offscreen pages, etc.).
Phase 2: Architecture & Development
1. Manifest V3 Is Mandatory
Key differences from V2:
- Background pages → Service Workers
- No remote-code execution
- Stricter CSP
- Unified
actionAPI - Offscreen documents required for DOM access
Ignoring these early guarantees a painful refactor later.
2. Master Extension Communication
Every part of your extension runs in its own isolated world. You must pass messages explicitly.
| From | To | Method | Purpose |
|---|---|---|---|
| Popup | Background | runtime.sendMessage |
Trigger actions or read state |
| Background | Popup | onMessage |
Send updates back |
| Content Script | Background | sendMessage |
Share page data |
| Background | Content Script | tabs.sendMessage |
Inject commands / data |
| Page Scripts | Content Scripts | window.postMessage |
Bypass sandboxing |
If you misunderstand this, your extension will behave unpredictably.
3. Offscreen Documents
Background service workers cannot use DOM APIs, so tasks like:
MediaRecorder- HTML parsing
- Canvas operations
- Audio processing
must run in an offscreen document.
You create one with:
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['USER_MEDIA'],
justification: 'Record audio from microphone'
});
This is essential for modern extensions (screen recorders, converters, editors, etc.).
Phase 3: Build & Deployment
1. Local Development with Vite + CRXJS
Steps:
Run npm run dev.
Open chrome://extensions.
Enable Developer Mode.
Load the dist folder as an unpacked extension.
CRXJS handles HMR even for content scripts — something most toolchains break.
2. Publishing to Chrome Web Store
Create a developer account and pay the one-time fee.
Upload a zipped dist folder.
Add screenshots, description, and privacy policy.
Submit for review.
Approval can take hours or days depending on permissions.
3. Publishing to Firefox Add-ons (AMO)
If you use webextension-polyfill, most code works without modification.
Firefox reviews are stricter but usually faster.
4. CI/CD with GitHub Actions
A recommended workflow:
On push → lint, test, build.
On release tag → build, zip, attach to GitHub Release.
Optional → auto-upload to Chrome/Firefox for beta channels.
Automate everything except writing the code.
Phase 4: Testing & Quality Assurance
1. Unit Testing
Use Vitest or Jest for:
API helpers
Storage wrappers
Pure logic functions
2. E2E Testing
Use Playwright or Cypress to:
Launch a browser with the extension loaded
Interact with the popup
Interact with content scripts
Verify background events
Few developers test extensions properly — that’s why so many break.
3. Manual Testing Checklist
Chrome / Firefox / Edge
Windows / macOS / Linux
Install → Upgrade → Reinstall flows
Behavior with other extensions installed
Permission prompts
Offline mode
Phase 5: Post-Launch Maintenance
Launching is the midpoint, not the finish line.
1. Error Tracking
Use Sentry to capture:
Runtime errors
Unhandled promise rejections
Background script failures
You will find issues users never report.
2. Privacy-Focused Analytics
Plausible or Fathom works well.
Avoid Google Analytics — heavy, slow, and poor fit for extensions.
3. Feedback Loop
Provide a link to your GitHub Issues page or a lightweight feedback form.
4. Regular Updates
Browsers ship changes constantly.
Update dependencies frequently to avoid silent failures.
Conclusion
A modern browser extension is essentially a multi-surface web application with strict sandboxing and event-driven architecture.
If you plan correctly, choose the right tools, understand Manifest V3 limitations, and automate your pipeline, you can ship stable, scalable extensions with far less friction.
This playbook gives you the foundation to build extensions that don’t fall apart as the codebase grows — and prepares you for long-term maintenance across every major browser.