← Back to articles

How to Build a Chrome Extension in 2026 (Complete Guide)

Chrome extensions are one of the most underrated software businesses. Low competition, sticky users, and distribution through the Chrome Web Store. Here's how to build one from scratch in 2026.

The Landscape in 2026

  • Manifest V3 is now mandatory (Manifest V2 is deprecated)
  • Frameworks like WXT, Plasmo, and CRXJS make development much easier
  • React/TypeScript is the most popular stack for complex extensions
  • Service workers replace background pages
  • Chrome Web Store has ~200K extensions with far less competition than mobile app stores

Choose Your Tooling

If you want...Use
Fastest setup, most featuresWXT
React-first, simple configPlasmo
Vite integration, lightweightCRXJS
Full control, no frameworkVanilla (Vite + manual config)

Read our full comparison: WXT vs Plasmo vs CRXJS →

Recommended: WXT

WXT is the most popular Chrome extension framework in 2026. It handles the build pipeline, hot reloading, and browser compatibility.

npx wxt@latest init my-extension
cd my-extension
npm install
npm run dev  # Opens Chrome with your extension loaded

Project Structure (WXT)

my-extension/
├── src/
│   ├── entrypoints/
│   │   ├── popup/          # Popup UI (click extension icon)
│   │   │   ├── App.tsx
│   │   │   ├── main.tsx
│   │   │   └── style.css
│   │   ├── background.ts   # Service worker (background logic)
│   │   ├── content.ts      # Content script (runs on web pages)
│   │   └── options/        # Options page
│   ├── components/
│   └── utils/
├── public/
│   └── icon/               # Extension icons (16, 32, 48, 128px)
├── wxt.config.ts
└── package.json

Core Concepts

1. Popup (The UI)

The popup appears when users click your extension icon. Built with React, Vue, or vanilla HTML.

// src/entrypoints/popup/App.tsx
import { useState, useEffect } from 'react';

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Load saved data
    chrome.storage.local.get(['count'], (result) => {
      if (result.count) setCount(result.count);
    });
  }, []);

  const increment = () => {
    const newCount = count + 1;
    setCount(newCount);
    chrome.storage.local.set({ count: newCount });
  };

  return (
    <div style={{ width: 300, padding: 16 }}>
      <h1>My Extension</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

2. Background Service Worker

Runs in the background. Handles events, alarms, and cross-tab communication.

// src/entrypoints/background.ts
export default defineBackground(() => {
  // Listen for extension install
  chrome.runtime.onInstalled.addListener(() => {
    console.log('Extension installed!');
  });

  // Listen for messages from popup or content scripts
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.type === 'GET_DATA') {
      fetchData().then(sendResponse);
      return true; // Keep channel open for async response
    }
  });

  // Periodic task (replaces setInterval in MV2)
  chrome.alarms.create('sync', { periodInMinutes: 30 });
  chrome.alarms.onAlarm.addListener((alarm) => {
    if (alarm.name === 'sync') {
      syncData();
    }
  });
});

3. Content Scripts

Run on web pages. Can read/modify the DOM.

// src/entrypoints/content.ts
export default defineContentScript({
  matches: ['*://*.example.com/*'],
  main() {
    // This runs on every example.com page
    const element = document.querySelector('.target');
    if (element) {
      element.style.backgroundColor = 'yellow';
    }

    // Communicate with background
    chrome.runtime.sendMessage({ type: 'PAGE_VISITED', url: window.location.href });
  },
});

4. Storage

Chrome extensions have multiple storage options:

// Local storage (per device, 10MB limit)
await chrome.storage.local.set({ key: 'value' });
const result = await chrome.storage.local.get(['key']);

// Sync storage (syncs across devices, 100KB limit)
await chrome.storage.sync.set({ settings: { theme: 'dark' } });

// Session storage (cleared when browser closes)
await chrome.storage.session.set({ tempData: 'value' });

Manifest V3 Essentials

WXT generates the manifest, but you should understand the key permissions:

// wxt.config.ts
export default defineConfig({
  manifest: {
    name: 'My Extension',
    description: 'Does something useful',
    permissions: [
      'storage',        // Save data
      'activeTab',      // Access current tab (on user action)
      'alarms',         // Schedule periodic tasks
    ],
    // Only request what you need — Chrome reviews permissions
    optional_permissions: [
      'tabs',           // Access all tabs (request when needed)
      'notifications',  // Show notifications
    ],
  },
});

Permission tips:

  • Request the minimum permissions possible
  • Use optional_permissions for features that not all users need
  • activeTab is preferred over broad tabs permission
  • Excessive permissions trigger longer Chrome Web Store review

Adding an API / Backend

Most useful extensions need a backend. Options:

  1. Supabase — Free tier, auth + database + edge functions
  2. Firebase — Google integration, generous free tier
  3. Your own API — Vercel/Railway/Fly.io
// Example: Supabase integration
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
);

// In popup or background
const { data } = await supabase
  .from('user_data')
  .select('*')
  .eq('user_id', userId);

Publishing to Chrome Web Store

Prerequisites

  • Google Developer account ($5 one-time fee)
  • Extension icons (128x128 required, 48x48 and 16x16 recommended)
  • Screenshots (1280x800 or 640x400)
  • Privacy policy URL

Steps

  1. Build: npm run build
  2. Zip: Package the dist/ folder
  3. Upload: Go to Chrome Web Store Developer Dashboard
  4. Fill in listing: Description, screenshots, category
  5. Submit for review: Takes 1-7 days typically

Review Tips

  • Explain what your extension does clearly
  • Justify every permission in the review notes
  • Include a privacy policy (even for simple extensions)
  • Don't use misleading descriptions or screenshots

Monetization Options

1. Freemium

Free core features, paid premium features. Most common model.

  • Payment: Stripe Checkout or LemonSqueezy
  • License validation: Check against your API on extension load
  • Example: Free for 10 uses/day, unlimited on Pro ($5/month)

2. One-Time Purchase

Simple, no recurring billing infrastructure needed.

  • Sell on Gumroad, LemonSqueezy, or your own site
  • Provide a license key that unlocks the extension

3. Subscription

Recurring revenue with a backend for license management.

  • Monthly/annual plans via Stripe
  • Best for extensions with ongoing API costs

Common Patterns

Authentication

// OAuth2 flow (e.g., Google sign-in)
chrome.identity.getAuthToken({ interactive: true }, (token) => {
  // Use token to authenticate with your backend
});

// Or redirect to your web app's auth page
chrome.tabs.create({ url: 'https://yourapp.com/auth?extension=true' });

Context Menu

chrome.contextMenus.create({
  id: 'myAction',
  title: 'Do something with "%s"',
  contexts: ['selection'], // Right-click on selected text
});

chrome.contextMenus.onClicked.addListener((info) => {
  if (info.menuItemId === 'myAction') {
    processText(info.selectionText);
  }
});

Side Panel (New in MV3)

// Show a side panel instead of a popup
chrome.sidePanel.setOptions({
  path: 'sidepanel.html',
  enabled: true,
});

FAQ

How long does Chrome Web Store review take?

Typically 1-3 business days for new extensions. Updates to existing extensions are usually faster (hours to 1 day). Complex permissions may take longer.

Can I build for Firefox too?

Yes. WXT supports cross-browser builds. Most Chrome extension APIs have Firefox equivalents. Build once, publish to both stores.

How do I handle Manifest V3 service worker limitations?

Service workers can be terminated after 30 seconds of inactivity. Use chrome.alarms for periodic tasks, chrome.storage for state persistence, and design for stateless operation.

What's the best way to debug?

  • chrome://extensions → Enable Developer Mode → click "Inspect" on your extension
  • console.log in content scripts appears in the page's DevTools
  • Background service worker has its own DevTools inspector
  • WXT provides hot module reloading during development

The Bottom Line

Building a Chrome extension in 2026:

  1. Use WXT for the best development experience
  2. Start simple — popup + storage covers most use cases
  3. Request minimal permissions — faster review, more trust
  4. Add a backend (Supabase) when you need user accounts or data sync
  5. Monetize with freemium — free core + paid Pro features via Stripe/LemonSqueezy

The Chrome Web Store is still an underrated distribution channel. Extensions with 10K+ users can generate significant revenue with the right monetization model.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.