Claude Skill

chrome-extension-wxt

Build Chrome extensions using WXT framework with TypeScript, React, Vue, or Svelte. Use when creating browser extensions, developing cross-browser add-ons, or working with Chrome Web Store projects. Triggers on phrases like "chrome extension", "browser extension", "WXT framework"

LLM Mart · 0 points · 16 views 29 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download tenequm-skills-skills_chrome-extension-wxt-1ff2284.zip · 27 KB
Part of tenequm/skills — 25 skills

Install

skills CLI npx skills add https://github.com/tenequm/skills/tree/main/skills/chrome-extension-wxt
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install tenequm-skills@llmmart
Git git clone https://github.com/tenequm/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole tenequm/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Chrome Extension Development with WXT

Build modern, cross-browser extensions using WXT - the next-generation framework that supports Chrome, Firefox, Edge, Safari, and all Chromium browsers with a single codebase.

When to Use This Skill

Use this skill when:

  • Creating a new Chrome/browser extension
  • Setting up WXT development environment
  • Building extension features (popup, content scripts, background scripts)
  • Implementing cross-browser compatibility
  • Working with Manifest V3 (mandatory standard as of 2025, V2 deprecated)
  • Integrating React 19, Vue, Svelte, or Solid with extensions

Quick Start Workflow

1. Initialize WXT Project

# Create new project with framework of choice
npm create wxt@latest

# Or with specific template
npm create wxt@latest -- --template react-ts
npm create wxt@latest -- --template vue-ts
npm create wxt@latest -- --template svelte-ts

2. Project Structure

WXT uses file-based conventions:

project/
├── entrypoints/              # Auto-discovered entry points
│   ├── background.ts         # Service worker
│   ├── content.ts           # Content script
│   ├── popup.html           # Popup UI
│   └── options.html         # Options page
├── components/              # Auto-imported UI components
├── utils/                   # Auto-imported utilities
├── public/                  # Static assets
│   └── icon/               # Extension icons
├── wxt.config.ts           # Configuration
└── package.json

3. Development Commands

npm run dev              # Start dev server with HMR
npm run build           # Production build
npm run zip             # Package for store submission

Core Entry Points

WXT recognizes entry points by filename in entrypoints/ directory:

Background Script (Service Worker)

// entrypoints/background.ts
export default defineBackground({
  type: 'module',
  persistent: false,

  main() {
    // Listen for extension events
    browser.action.onClicked.addListener((tab) => {
      console.log('Extension clicked', tab);
    });

    // Handle messages
    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
      // Handle message
      sendResponse({ success: true });
      return true; // Keep channel open for async
    });
  },
});

Content Script

// entrypoints/content.ts
export default defineContentScript({
  matches: ['*://*.example.com/*'],
  runAt: 'document_end',

  main(ctx) {
    // Content script logic
    console.log('Content script loaded');

    // Create UI
    const ui = createShadowRootUi(ctx, {
      name: 'my-extension-ui',
      position: 'inline',
      anchor: 'body',

      onMount(container) {
        // Mount React/Vue component
        const root = ReactDOM.createRoot(container);
        root.render(<App />);
      },
    });

    ui.mount();
  },
});

Popup UI

// entrypoints/popup/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
<!-- entrypoints/popup/index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Extension Popup</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="./main.tsx"></script>
</body>
</html>

Configuration

Basic wxt.config.ts

import { defineConfig } from 'wxt';

export default defineConfig({
  // Framework integration
  modules: ['@wxt-dev/module-react'],

  // Manifest configuration
  manifest: {
    name: 'My Extension',
    description: 'Extension description',
    permissions: ['storage', 'activeTab'],
    host_permissions: ['*://example.com/*'],
  },

  // Browser target
  browser: 'chrome', // or 'firefox', 'edge', 'safari'
});

Common Patterns

Type-Safe Storage

// utils/storage.ts
import { storage } from 'wxt/storage';

export const storageHelper = {
  async get<T>(key: string): Promise<T | null> {
    return await storage.getItem<T>(`local:${key}`);
  },

  async set<T>(key: string, value: T): Promise<void> {
    await storage.setItem(`local:${key}`, value);
  },

  watch<T>(key: string, callback: (newValue: T | null) => void) {
    return storage.watch<T>(`local:${key}`, callback);
  },
};

Type-Safe Messaging

// utils/messaging.ts
interface Messages {
  'get-data': {
    request: { key: string };
    response: { value: any };
  };
}

export async function sendMessage<K extends keyof Messages>(
  type: K,
  payload: Messages[K]['request']
): Promise<Messages[K]['response']> {
  return await browser.runtime.sendMessage({ type, payload });
}

Script Injection

// Inject script into page context
import { injectScript } from 'wxt/client';

await injectScript('/injected.js', {
  keepInDom: false,
});

Building & Deployment

Production Build

# Build for specific browser
npm run build -- --browser=chrome
npm run build -- --browser=firefox

# Create store-ready ZIP
npm run zip
npm run zip -- --browser=firefox

Multi-Browser Build

# Build for all browsers
npm run zip:all

Output: .output/my-extension-{version}-{browser}.zip

Modern Stacks (2025)

Popular technology combinations for building Chrome extensions:

WXT + React + Tailwind + shadcn/ui

Most popular stack in 2025. Combines utility-first styling with pre-built accessible components.

npm create wxt@latest -- --template react-ts
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npx shadcn@latest init

Best for: Modern UIs with consistent design system Example: https://github.com/imtiger/wxt-react-shadcn-tailwindcss-chrome-extension

WXT + React + Mantine UI

Complete component library with 100+ components and built-in dark mode.

npm create wxt@latest -- --template react-ts
npm install @mantine/core @mantine/hooks

Best for: Feature-rich extensions needing complex components Example: https://github.com/ongkay/WXT-Mantine-Tailwind-Browser-Extension

WXT + React + TypeScript (Minimal)

Clean setup for custom designs without UI library dependencies.

npm create wxt@latest -- --template react-ts

Best for: Simple extensions or highly custom designs

Advanced Topics

For detailed information on advanced topics, see the reference files:

  • React Integration: See references/react-integration.md for complete React setup, hooks, state management, and popular UI libraries
  • Chrome APIs: See references/chrome-api.md for comprehensive Chrome Extension API reference with examples
  • Chrome 140+ Features: See references/chrome-140-features.md for latest Chrome Extension APIs (sidePanel.getLayout(), etc.)
  • WXT API: See references/wxt-api.md for complete WXT framework API documentation
  • Best Practices: See references/best-practices.md for security, performance, and architecture patterns

Troubleshooting

Common issues and solutions:

  1. Module not found errors: Ensure modules are installed and properly imported
  2. CSP violations: Update content_security_policy in manifest
  3. Hot reload not working: Check browser console for errors
  4. Storage not persisting: Use storage.local or storage.sync correctly

For deeper guidance on avoiding these issues, see references/best-practices.md.

Resources

Official Documentation

Bundled Resources

  • references/: Detailed documentation for advanced features

Use these resources as needed when building your extension.

Files (skills)
  • references
    • best-practices.md 7.8 KB
      # Chrome Extension Best Practices with WXT
      
      Security, performance, and architecture recommendations.
      
      ## Security
      
      ### Content Security Policy
      
      Always configure CSP for extension pages:
      
      ```typescript
      export default defineConfig({
        manifest: {
          content_security_policy: {
            extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
          },
        },
      });
      ```
      
      ### Minimal Permissions
      
      Request only necessary permissions:
      
      ```typescript
      // Good - specific permissions
      permissions: ['storage', 'activeTab']
      
      // Bad - excessive permissions
      permissions: ['<all_urls>', 'tabs', 'history', 'bookmarks']
      ```
      
      Use `optional_permissions` for features that might not be needed:
      
      ```typescript
      manifest: {
        permissions: ['storage'],
        optional_permissions: ['tabs', 'bookmarks'],
      }
      ```
      
      ### Input Validation
      
      Always sanitize user input:
      
      ```typescript
      function sanitizeInput(input: string): string {
        return input
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
          .replace(/"/g, '&quot;')
          .replace(/'/g, '&#x27;');
      }
      ```
      
      Use DOMPurify for HTML content:
      
      ```typescript
      import DOMPurify from 'dompurify';
      
      const clean = DOMPurify.sanitize(html, {
        ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
        ALLOWED_ATTR: ['href'],
      });
      ```
      
      ### Secure API Calls
      
      Never hardcode API keys:
      
      ```typescript
      // Store in browser.storage, not in code
      const { apiKey } = await browser.storage.local.get('apiKey');
      
      const response = await fetch(url, {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
        },
      });
      ```
      
      ## Performance
      
      ### Service Worker Optimization
      
      Keep service worker lightweight:
      
      ```typescript
      export default defineBackground({
        main() {
          // Use alarms for long delays
          browser.alarms.create('daily-sync', {
            periodInMinutes: 1440,
          });
      
          // Unregister listeners when not needed
          let listener: any;
      
          function enable() {
            listener = (msg: any) => handleMessage(msg);
            browser.runtime.onMessage.addListener(listener);
          }
      
          function disable() {
            if (listener) {
              browser.runtime.onMessage.removeListener(listener);
              listener = null;
            }
          }
        },
      });
      ```
      
      ### Lazy Loading
      
      Load heavy dependencies only when needed:
      
      ```typescript
      export default defineContentScript({
        matches: ['*://*.example.com/*'],
      
        async main(ctx) {
          // Wait for user interaction
          document.querySelector('#button')?.addEventListener('click', async () => {
            // Lazy load React
            const React = await import('react');
            const ReactDOM = await import('react-dom/client');
            const { App } = await import('./components/App');
      
            const root = ReactDOM.createRoot(document.getElementById('root')!);
            root.render(React.createElement(App));
          });
        },
      });
      ```
      
      ### Bundle Splitting
      
      Configure Vite for optimal chunks:
      
      ```typescript
      export default defineConfig({
        vite: () => ({
          build: {
            rollupOptions: {
              output: {
                manualChunks: {
                  vendor: ['react', 'react-dom'],
                  utils: ['date-fns', 'lodash-es'],
                },
              },
            },
          },
        }),
      });
      ```
      
      ### Caching Strategy
      
      Cache API responses appropriately:
      
      ```typescript
      const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
      
      async function getCachedData(key: string) {
        const cached = await storage.getItem<{data: any, timestamp: number}>(`cache:${key}`);
      
        if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
          return cached.data;
        }
      
        const freshData = await fetchData(key);
        await storage.setItem(`cache:${key}`, {
          data: freshData,
          timestamp: Date.now(),
        });
      
        return freshData;
      }
      ```
      
      ## Architecture
      
      ### File Organization
      
      ```
      src/
      ├── entrypoints/
      │   ├── background/          # Complex background logic
      │   │   ├── index.ts
      │   │   ├── handlers.ts
      │   │   └── utils.ts
      │   ├── content/            # Complex content script
      │   │   ├── index.ts
      │   │   ├── ui.tsx
      │   │   └── injector.ts
      │   └── popup/              # Popup UI
      │       ├── index.html
      │       ├── main.tsx
      │       └── App.tsx
      ├── components/             # Shared UI components
      │   ├── Button.tsx
      │   └── Modal.tsx
      ├── utils/                  # Shared utilities
      │   ├── storage.ts
      │   ├── messaging.ts
      │   └── api.ts
      └── types/                  # TypeScript types
          └── index.ts
      ```
      
      ### Type-Safe Communication
      
      Define message interfaces:
      
      ```typescript
      // types/messages.ts
      export interface MessageMap {
        'fetch-data': {
          request: { url: string };
          response: { data: any };
        };
        'save-settings': {
          request: { settings: Record<string, any> };
          response: { success: boolean };
        };
      }
      
      // utils/messaging.ts
      export async function sendMessage<K extends keyof MessageMap>(
        type: K,
        payload: MessageMap[K]['request']
      ): Promise<MessageMap[K]['response']> {
        return await browser.runtime.sendMessage({ type, payload });
      }
      ```
      
      ### Error Handling
      
      Implement comprehensive error handling:
      
      ```typescript
      // utils/errors.ts
      export class ExtensionError extends Error {
        constructor(
          message: string,
          public code: string,
          public context?: any
        ) {
          super(message);
          this.name = 'ExtensionError';
        }
      }
      
      // Usage
      try {
        await riskyOperation();
      } catch (error) {
        if (error instanceof ExtensionError) {
          // Handle known error
          console.error(`Error ${error.code}:`, error.message, error.context);
        } else {
          // Handle unknown error
          console.error('Unexpected error:', error);
        }
      
        // Report to user
        await browser.notifications.create({
          type: 'basic',
          title: 'Error',
          message: 'Something went wrong',
        });
      }
      ```
      
      ### State Management
      
      For complex state, use proper state management:
      
      ```typescript
      // utils/store.ts
      import { create } from 'zustand';
      import { persist } from 'zustand/middleware';
      
      interface State {
        settings: Record<string, any>;
        updateSettings: (updates: Record<string, any>) => void;
      }
      
      export const useStore = create<State>()(
        persist(
          (set) => ({
            settings: {},
            updateSettings: (updates) =>
              set((state) => ({
                settings: { ...state.settings, ...updates },
              })),
          }),
          {
            name: 'extension-storage',
          }
        )
      );
      ```
      
      ## Testing
      
      ### Unit Tests
      
      ```typescript
      // vitest.config.ts
      import { defineConfig } from 'vitest/config';
      
      export default defineConfig({
        test: {
          environment: 'jsdom',
          setupFiles: ['./test/setup.ts'],
          globals: true,
        },
      });
      
      // test/setup.ts
      import { vi } from 'vitest';
      
      global.browser = {
        runtime: {
          sendMessage: vi.fn(),
        },
        storage: {
          local: {
            get: vi.fn(),
            set: vi.fn(),
          },
        },
      } as any;
      ```
      
      ### E2E Tests
      
      ```typescript
      // e2e/extension.spec.ts
      import { test, expect } from '@playwright/test';
      
      test('popup loads correctly', async ({ page, extensionId }) => {
        await page.goto(`chrome-extension://${extensionId}/popup.html`);
        await expect(page.locator('h1')).toHaveText('My Extension');
      });
      ```
      
      ## Deployment
      
      ### Version Management
      
      Use semantic versioning:
      
      ```json
      {
        "version": "1.0.0"  // MAJOR.MINOR.PATCH
      }
      ```
      
      ### Store Submission Checklist
      
      - [ ] Icons provided (16, 32, 48, 128)
      - [ ] Permissions justified in description
      - [ ] Privacy policy provided (if handling user data)
      - [ ] Screenshot and promotional images
      - [ ] Tested on target browsers
      - [ ] No hardcoded secrets
      - [ ] CSP properly configured
      - [ ] Manifest complete and valid
      
      ### CI/CD Pipeline
      
      ```yaml
      # .github/workflows/release.yml
      name: Release
      
      on:
        push:
          tags:
            - 'v*'
      
      jobs:
        build:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
            - uses: actions/setup-node@v4
              with:
                node-version: '20'
      
            - run: npm ci
            - run: npm run build
            - run: npm run zip:all
      
            - name: Upload artifacts
              uses: actions/upload-artifact@v4
              with:
                name: extensions
                path: .output/*.zip
      ```
      
    • chrome-140-features.md 6.6 KB
      # Chrome 140+ Features (September 2025+)
      
      New Chrome Extension APIs introduced in Chrome 140 and later versions.
      
      ## Chrome 140 (September 2025)
      
      ### sidePanel.getLayout()
      
      Determines the position of the side panel (left or right) in the browser window.
      
      **Official Documentation:** https://developer.chrome.com/docs/extensions/reference/api/sidePanel#method-getLayout
      
      #### API Signature
      
      ```typescript
      chrome.sidePanel.getLayout(): Promise<{
        side: 'left' | 'right';
      }>
      ```
      
      #### Basic Usage
      
      ```typescript
      // Get current side panel layout
      const layout = await chrome.sidePanel.getLayout();
      console.log('Side panel is positioned on the:', layout.side);
      
      if (layout.side === 'right') {
        console.log('Side panel is on the right');
      } else {
        console.log('Side panel is on the left');
      }
      ```
      
      #### Use Cases
      
      ##### 1. RTL Language Support
      
      ```typescript
      export default defineContentScript({
        matches: ['*://*'],
        async main() {
          const layout = await chrome.sidePanel.getLayout();
          const documentDir = document.documentElement.dir;
      
          // Adjust UI based on panel side and text direction
          if (layout.side === 'right' && documentDir === 'rtl') {
            // Apply RTL-optimized positioning
            applyRTLStyles();
          }
        },
      });
      ```
      
      ##### 2. Dynamic Content Positioning
      
      ```typescript
      // Popup component
      function App() {
        const [panelSide, setPanelSide] = useState<'left' | 'right'>('left');
      
        useEffect(() => {
          chrome.sidePanel.getLayout().then(({ side }) => {
            setPanelSide(side);
          });
        }, []);
      
        return (
          <div className={`panel-${panelSide}`}>
            <p>Panel is positioned on the {panelSide}</p>
            {/* Adjust UI layout based on panel side */}
          </div>
        );
      }
      ```
      
      ##### 3. Optimal Notification Placement
      
      ```typescript
      // Background script
      browser.alarms.onAlarm.addListener(async (alarm) => {
        const layout = await chrome.sidePanel.getLayout();
      
        // Position notifications away from side panel
        const notificationPosition = layout.side === 'right'
          ? 'bottom-left'
          : 'bottom-right';
      
        await chrome.notifications.create({
          type: 'basic',
          title: 'Reminder',
          message: 'Task is due',
          iconUrl: '/icon/128.png',
        });
      });
      ```
      
      #### Browser Compatibility
      
      - **Chrome:** 140+ (September 2025)
      - **Firefox:** Not yet supported
      - **Edge:** 140+ (follows Chromium)
      - **Safari:** Not applicable (no side panel API)
      
      #### Feature Detection
      
      Always check if the API is available:
      
      ```typescript
      async function getSidePanelSide(): Promise<'left' | 'right' | null> {
        if (chrome.sidePanel?.getLayout) {
          try {
            const layout = await chrome.sidePanel.getLayout();
            return layout.side;
          } catch (error) {
            console.error('Failed to get side panel layout:', error);
            return null;
          }
        }
        return null; // API not available
      }
      ```
      
      #### Integration with WXT
      
      ```typescript
      // entrypoints/sidepanel/main.tsx
      import { useState, useEffect } from 'react';
      
      function SidePanel() {
        const [side, setSide] = useState<'left' | 'right'>('left');
      
        useEffect(() => {
          // Get initial side
          chrome.sidePanel.getLayout().then(({ side }) => {
            setSide(side);
          });
      
          // Note: Chrome doesn't fire events when user changes panel side
          // You may need to periodically check or reload when panel is opened
        }, []);
      
        return (
          <div className={`sidepanel-container side-${side}`}>
            <header className={side === 'right' ? 'rtl' : 'ltr'}>
              <h1>Side Panel Content</h1>
            </header>
            <main>
              <p>Current side: {side}</p>
            </main>
          </div>
        );
      }
      ```
      
      #### Default Behavior
      
      - **New Chrome installations (2025+):** May default to right side
      - **Upgraded Chrome installations:** Retains user's previous preference
      - **User can change:** Users can move side panel between left and right at any time
      
      #### Common Patterns
      
      ##### Responsive Layout Adjustment
      
      ```typescript
      // hooks/useSidePanelPosition.ts
      import { useState, useEffect } from 'react';
      
      export function useSidePanelSide() {
        const [side, setSide] = useState<'left' | 'right'>('left');
        const [isLoading, setIsLoading] = useState(true);
      
        useEffect(() => {
          if (chrome.sidePanel?.getLayout) {
            chrome.sidePanel
              .getLayout()
              .then(({ side }) => {
                setSide(side);
              })
              .catch((error) => {
                console.error('Failed to get side panel side:', error);
              })
              .finally(() => {
                setIsLoading(false);
              });
          } else {
            setIsLoading(false);
          }
        }, []);
      
        return { side, isLoading };
      }
      
      // Usage in component
      function MyComponent() {
        const { side, isLoading } = useSidePanelSide();
      
        if (isLoading) return <LoadingSpinner />;
      
        return (
          <div className={`content-${side}`}>
            {/* Content positioned based on panel location */}
          </div>
        );
      }
      ```
      
      #### Styling Based on Side
      
      ```css
      /* CSS for panel-aware layouts */
      .content-left {
        /* Panel is on left, content flows from right */
        margin-left: 20px;
        margin-right: 0;
        text-align: left;
      }
      
      .content-right {
        /* Panel is on right, content flows from left */
        margin-left: 0;
        margin-right: 20px;
        text-align: right;
      }
      
      /* RTL support */
      [dir="rtl"] .content-left {
        direction: rtl;
      }
      ```
      
      ## Staying Updated
      
      To stay informed about new Chrome Extension features:
      
      1. **Chrome Extensions What's New:** https://developer.chrome.com/docs/extensions/whats-new
      2. **Chrome Developers Blog:** https://developer.chrome.com/blog
      3. **Chrome Platform Status:** https://chromestatus.com/features
      4. **WXT Changelog:** https://github.com/wxt-dev/wxt/releases
      
      ## Migration Guide
      
      If your extension currently assumes side panel is always on the left:
      
      ### Before (Assumed Left Side)
      
      ```typescript
      // Old code - assumes left side
      function positionContent() {
        const content = document.getElementById('content');
        content.style.marginLeft = '400px'; // Fixed left margin
      }
      ```
      
      ### After (Side-Aware)
      
      ```typescript
      // New code - adapts to panel side
      async function positionContent() {
        const content = document.getElementById('content');
      
        if (chrome.sidePanel?.getLayout) {
          const { side } = await chrome.sidePanel.getLayout();
      
          if (side === 'right') {
            content.style.marginRight = '400px';
            content.style.marginLeft = '0';
          } else {
            content.style.marginLeft = '400px';
            content.style.marginRight = '0';
          }
        }
      }
      ```
      
      ## Related APIs
      
      - **chrome.sidePanel.open()** - Open side panel programmatically
      - **chrome.sidePanel.close()** - Close side panel
      - **chrome.sidePanel.setOptions()** - Configure side panel behavior
      - **chrome.sidePanel.getOptions()** - Get current side panel configuration
      
      **Full Side Panel API:** https://developer.chrome.com/docs/extensions/reference/api/sidePanel
      
    • chrome-api.md 20.6 KB
      # Chrome Extension API Reference
      
      Comprehensive guide to Chrome Extension APIs with WXT. Based on official Chrome Extension documentation at https://developer.chrome.com/docs/extensions.
      
      ## Core APIs
      
      ### chrome.action (Manifest V3)
      
      Control the extension's toolbar icon.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/action
      
      ```typescript
      // Set badge text (shows number on icon)
      await browser.action.setBadgeText({ text: '5' });
      
      // Set badge background color
      await browser.action.setBadgeBackgroundColor({ color: '#FF0000' });
      
      // Set icon
      await browser.action.setIcon({
        path: {
          16: '/icon/16.png',
          32: '/icon/32.png',
        }
      });
      
      // Set title (tooltip)
      await browser.action.setTitle({ title: 'Extension tooltip' });
      
      // Enable/disable for specific tabs
      await browser.action.enable(tabId);
      await browser.action.disable(tabId);
      
      // Listen for icon clicks
      browser.action.onClicked.addListener((tab) => {
        console.log('Extension icon clicked in tab:', tab.id);
      });
      
      // Set popup programmatically
      await browser.action.setPopup({ popup: 'popup.html' });
      ```
      
      ### chrome.tabs
      
      Interact with browser tabs.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/tabs
      
      ```typescript
      // Query tabs
      const tabs = await browser.tabs.query({
        active: true,
        currentWindow: true
      });
      
      // Get specific tab
      const tab = await browser.tabs.get(tabId);
      
      // Create new tab
      const newTab = await browser.tabs.create({
        url: 'https://example.com',
        active: true,
        pinned: false,
      });
      
      // Update tab
      await browser.tabs.update(tabId, {
        url: 'https://example.com',
        active: true,
      });
      
      // Close tab
      await browser.tabs.remove(tabId);
      
      // Duplicate tab
      await browser.tabs.duplicate(tabId);
      
      // Send message to content script
      const response = await browser.tabs.sendMessage(tabId, {
        type: 'getMessage',
        data: 'hello'
      });
      
      // Note: tabs.executeScript and tabs.insertCSS are deprecated in MV3
      // Use chrome.scripting API instead (see scripting section below)
      
      // Tab events
      browser.tabs.onCreated.addListener((tab) => {
        console.log('Tab created:', tab.id);
      });
      
      browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
        if (changeInfo.status === 'complete') {
          console.log('Tab loaded:', tab.url);
        }
      });
      
      browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
        console.log('Tab closed:', tabId);
      });
      
      browser.tabs.onActivated.addListener((activeInfo) => {
        console.log('Tab activated:', activeInfo.tabId);
      });
      ```
      
      ### chrome.runtime
      
      Access extension runtime information and communicate between components.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/runtime
      
      ```typescript
      // Get extension ID
      const extensionId = browser.runtime.id;
      
      // Get manifest
      const manifest = browser.runtime.getManifest();
      console.log('Version:', manifest.version);
      
      // Get URL of extension resource
      const iconUrl = browser.runtime.getURL('icon/128.png');
      
      // Send message to background
      const response = await browser.runtime.sendMessage({
        type: 'getData',
        payload: { key: 'value' }
      });
      
      // Listen for messages
      browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
        console.log('Message from:', sender.tab?.url || 'extension');
      
        if (message.type === 'getData') {
          // Handle async with Promise
          (async () => {
            const result = await fetchData(message.payload);
            sendResponse(result);
          })();
      
          return true; // Keep channel open for async
        }
      });
      
      // Connect for long-lived connections
      const port = browser.runtime.connect({ name: 'my-channel' });
      port.postMessage({ data: 'hello' });
      port.onMessage.addListener((msg) => {
        console.log('Received:', msg);
      });
      
      // Listen for connection
      browser.runtime.onConnect.addListener((port) => {
        console.log('Connected:', port.name);
      
        port.onMessage.addListener((msg) => {
          console.log('Message:', msg);
          port.postMessage({ response: 'received' });
        });
      });
      
      // Install/update events
      browser.runtime.onInstalled.addListener((details) => {
        if (details.reason === 'install') {
          console.log('Extension installed');
        } else if (details.reason === 'update') {
          console.log('Extension updated to version:', manifest.version);
        }
      });
      
      // Extension suspend warning
      browser.runtime.onSuspend.addListener(() => {
        console.log('Service worker about to suspend');
        // Clean up resources
      });
      ```
      
      ### chrome.storage
      
      Store and sync data.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/storage
      
      ```typescript
      // Local storage (not synced)
      await browser.storage.local.set({ key: 'value' });
      const result = await browser.storage.local.get('key');
      console.log(result.key); // 'value'
      
      // Sync storage (synced across devices)
      await browser.storage.sync.set({ settings: { theme: 'dark' } });
      const settings = await browser.storage.sync.get('settings');
      
      // Get multiple items
      const data = await browser.storage.local.get(['key1', 'key2']);
      console.log(data.key1, data.key2);
      
      // Get all items
      const all = await browser.storage.local.get(null);
      
      // Remove items
      await browser.storage.local.remove('key');
      await browser.storage.local.remove(['key1', 'key2']);
      
      // Clear all
      await browser.storage.local.clear();
      
      // Get bytes in use
      const bytes = await browser.storage.local.getBytesInUse('key');
      
      // Listen for changes
      browser.storage.onChanged.addListener((changes, area) => {
        console.log('Storage area:', area); // 'local' or 'sync'
      
        for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
          console.log(`${key} changed from ${oldValue} to ${newValue}`);
        }
      });
      
      // Storage limits
      // local: ~10MB
      // sync: 100KB total, 8KB per item
      ```
      
      ### chrome.alarms
      
      Schedule periodic tasks.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/alarms
      
      ```typescript
      // Create alarm that fires once
      await browser.alarms.create('reminder', {
        delayInMinutes: 1,
      });
      
      // Create periodic alarm
      await browser.alarms.create('daily-sync', {
        periodInMinutes: 1440, // 24 hours
      });
      
      // Create alarm at specific time
      await browser.alarms.create('scheduled', {
        when: Date.now() + 60000, // 1 minute from now
      });
      
      // Get alarm
      const alarm = await browser.alarms.get('reminder');
      
      // Get all alarms
      const alarms = await browser.alarms.getAll();
      
      // Clear alarm
      await browser.alarms.clear('reminder');
      
      // Clear all alarms
      await browser.alarms.clearAll();
      
      // Listen for alarms
      browser.alarms.onAlarm.addListener((alarm) => {
        console.log('Alarm fired:', alarm.name);
      
        if (alarm.name === 'daily-sync') {
          performDailySync();
        }
      });
      ```
      
      ### chrome.notifications
      
      Display system notifications.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/notifications
      
      ```typescript
      // Basic notification
      await browser.notifications.create({
        type: 'basic',
        iconUrl: '/icon/128.png',
        title: 'Notification Title',
        message: 'This is the notification message',
        priority: 2,
      });
      
      // Notification with buttons
      await browser.notifications.create('my-notification-id', {
        type: 'basic',
        iconUrl: '/icon/128.png',
        title: 'Action Required',
        message: 'Click a button to respond',
        buttons: [
          { title: 'Accept' },
          { title: 'Decline' }
        ],
        requireInteraction: true, // Don't auto-dismiss
      });
      
      // Progress notification
      await browser.notifications.create({
        type: 'progress',
        iconUrl: '/icon/128.png',
        title: 'Downloading...',
        message: 'File download in progress',
        progress: 50,
      });
      
      // List notification
      await browser.notifications.create({
        type: 'list',
        iconUrl: '/icon/128.png',
        title: 'Multiple Items',
        message: 'Summary message',
        items: [
          { title: 'Item 1', message: 'First item' },
          { title: 'Item 2', message: 'Second item' },
        ],
      });
      
      // Image notification
      await browser.notifications.create({
        type: 'image',
        iconUrl: '/icon/128.png',
        title: 'Image Notification',
        message: 'Notification with image',
        imageUrl: '/images/preview.png',
      });
      
      // Update notification
      await browser.notifications.update('my-notification-id', {
        progress: 75,
      });
      
      // Clear notification
      await browser.notifications.clear('my-notification-id');
      
      // Notification events
      browser.notifications.onClicked.addListener((notificationId) => {
        console.log('Notification clicked:', notificationId);
      });
      
      browser.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => {
        console.log(`Button ${buttonIndex} clicked on ${notificationId}`);
      });
      
      browser.notifications.onClosed.addListener((notificationId, byUser) => {
        console.log(`Notification ${notificationId} closed by user: ${byUser}`);
      });
      ```
      
      ### chrome.contextMenus
      
      Add items to browser context menu (right-click menu).
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/contextMenus
      
      ```typescript
      // Create context menu in background script
      browser.runtime.onInstalled.addListener(() => {
        // Simple menu item
        browser.contextMenus.create({
          id: 'search-selection',
          title: 'Search "%s"',
          contexts: ['selection'],
        });
      
        // Menu with submenu
        browser.contextMenus.create({
          id: 'parent',
          title: 'Extension Actions',
          contexts: ['page', 'selection'],
        });
      
        browser.contextMenus.create({
          id: 'child1',
          parentId: 'parent',
          title: 'Action 1',
          contexts: ['page'],
        });
      
        browser.contextMenus.create({
          id: 'child2',
          parentId: 'parent',
          title: 'Action 2',
          contexts: ['page'],
        });
      
        // Menu for specific URL patterns
        browser.contextMenus.create({
          id: 'github-actions',
          title: 'GitHub Actions',
          contexts: ['page'],
          documentUrlPatterns: ['*://github.com/*'],
        });
      });
      
      // Listen for clicks
      browser.contextMenus.onClicked.addListener((info, tab) => {
        if (info.menuItemId === 'search-selection') {
          const query = info.selectionText;
          browser.tabs.create({
            url: `https://www.google.com/search?q=${encodeURIComponent(query)}`,
          });
        }
      });
      
      // Context types
      // 'all', 'page', 'selection', 'link', 'editable', 'image', 'video', 'audio'
      ```
      
      ### chrome.webRequest
      
      Intercept and modify network requests.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/webRequest
      
      **Requires permission:** `"webRequest"` and host permissions
      
      ```typescript
      // Block requests
      browser.webRequest.onBeforeRequest.addListener(
        (details) => {
          // Block requests to certain URLs
          if (details.url.includes('ads.com')) {
            return { cancel: true };
          }
        },
        { urls: ['<all_urls>'] },
        ['blocking']
      );
      
      // Modify request headers
      browser.webRequest.onBeforeSendHeaders.addListener(
        (details) => {
          const headers = details.requestHeaders || [];
      
          // Add custom header
          headers.push({
            name: 'X-Custom-Header',
            value: 'my-value',
          });
      
          // Remove header
          const filtered = headers.filter(h => h.name !== 'User-Agent');
      
          return { requestHeaders: filtered };
        },
        { urls: ['*://*.example.com/*'] },
        ['blocking', 'requestHeaders']
      );
      
      // Modify response headers
      browser.webRequest.onHeadersReceived.addListener(
        (details) => {
          const headers = details.responseHeaders || [];
      
          // Modify CORS headers
          headers.push({
            name: 'Access-Control-Allow-Origin',
            value: '*',
          });
      
          return { responseHeaders: headers };
        },
        { urls: ['*://*.api.com/*'] },
        ['blocking', 'responseHeaders']
      );
      
      // Redirect requests
      browser.webRequest.onBeforeRequest.addListener(
        (details) => {
          return { redirectUrl: 'https://alternative.com' };
        },
        { urls: ['*://blocked.com/*'] },
        ['blocking']
      );
      ```
      
      ### chrome.cookies
      
      Manage browser cookies.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/cookies
      
      ```typescript
      // Get cookie
      const cookie = await browser.cookies.get({
        url: 'https://example.com',
        name: 'session',
      });
      
      // Get all cookies for URL
      const cookies = await browser.cookies.getAll({
        url: 'https://example.com',
      });
      
      // Set cookie
      await browser.cookies.set({
        url: 'https://example.com',
        name: 'session',
        value: 'abc123',
        expirationDate: Date.now() / 1000 + 3600, // 1 hour
        httpOnly: true,
        secure: true,
        sameSite: 'lax',
      });
      
      // Remove cookie
      await browser.cookies.remove({
        url: 'https://example.com',
        name: 'session',
      });
      
      // Listen for cookie changes
      browser.cookies.onChanged.addListener((changeInfo) => {
        console.log('Cookie changed:', changeInfo.cookie.name);
        console.log('Removed:', changeInfo.removed);
      });
      ```
      
      ### chrome.downloads
      
      Manage file downloads.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/downloads
      
      ```typescript
      // Download file
      const downloadId = await browser.downloads.download({
        url: 'https://example.com/file.pdf',
        filename: 'downloaded-file.pdf',
        saveAs: true, // Show save dialog
      });
      
      // Search downloads
      const downloads = await browser.downloads.search({
        query: ['pdf'],
        orderBy: ['-startTime'],
        limit: 10,
      });
      
      // Pause download
      await browser.downloads.pause(downloadId);
      
      // Resume download
      await browser.downloads.resume(downloadId);
      
      // Cancel download
      await browser.downloads.cancel(downloadId);
      
      // Show download in folder
      await browser.downloads.show(downloadId);
      
      // Open downloaded file
      await browser.downloads.open(downloadId);
      
      // Listen for download changes
      browser.downloads.onChanged.addListener((delta) => {
        if (delta.state?.current === 'complete') {
          console.log('Download complete:', delta.id);
        }
      });
      
      browser.downloads.onCreated.addListener((item) => {
        console.log('Download started:', item.filename);
      });
      ```
      
      ### chrome.bookmarks
      
      Access and modify bookmarks.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/bookmarks
      
      ```typescript
      // Get bookmarks
      const bookmarks = await browser.bookmarks.getTree();
      
      // Search bookmarks
      const results = await browser.bookmarks.search('github');
      
      // Create bookmark
      const bookmark = await browser.bookmarks.create({
        parentId: '1',
        title: 'GitHub',
        url: 'https://github.com',
      });
      
      // Create folder
      const folder = await browser.bookmarks.create({
        parentId: '1',
        title: 'My Folder',
      });
      
      // Update bookmark
      await browser.bookmarks.update(bookmark.id, {
        title: 'GitHub - Updated',
        url: 'https://github.com/explore',
      });
      
      // Move bookmark
      await browser.bookmarks.move(bookmark.id, {
        parentId: folder.id,
        index: 0,
      });
      
      // Remove bookmark
      await browser.bookmarks.remove(bookmark.id);
      
      // Remove folder recursively
      await browser.bookmarks.removeTree(folder.id);
      
      // Listen for changes
      browser.bookmarks.onCreated.addListener((id, bookmark) => {
        console.log('Bookmark created:', bookmark.title);
      });
      
      browser.bookmarks.onRemoved.addListener((id, removeInfo) => {
        console.log('Bookmark removed:', id);
      });
      ```
      
      ### chrome.scripting
      
      Inject JavaScript and CSS into web pages (replaces deprecated tabs.executeScript/insertCSS).
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/scripting
      
      **Required permission:** `"scripting"`
      
      ```typescript
      // Execute script in tab
      await chrome.scripting.executeScript({
        target: { tabId: tabId },
        files: ['content.js'],
      });
      
      // Execute inline function
      await chrome.scripting.executeScript({
        target: { tabId: tabId },
        func: () => {
          console.log('Hello from injected script');
        },
      });
      
      // Execute with arguments
      await chrome.scripting.executeScript({
        target: { tabId: tabId },
        func: (color) => {
          document.body.style.backgroundColor = color;
        },
        args: ['red'],
      });
      
      // Inject CSS file
      await chrome.scripting.insertCSS({
        target: { tabId: tabId },
        files: ['styles.css'],
      });
      
      // Inject inline CSS
      await chrome.scripting.insertCSS({
        target: { tabId: tabId },
        css: 'body { background: red; }',
      });
      
      // Remove CSS
      await chrome.scripting.removeCSS({
        target: { tabId: tabId },
        css: 'body { background: red; }',
      });
      
      // Register content scripts dynamically
      await chrome.scripting.registerContentScripts([{
        id: 'my-script',
        matches: ['*://example.com/*'],
        js: ['content.js'],
        runAt: 'document_idle',
      }]);
      
      // Get registered scripts
      const scripts = await chrome.scripting.getRegisteredContentScripts();
      
      // Unregister scripts
      await chrome.scripting.unregisterContentScripts({
        ids: ['my-script'],
      });
      
      // Update existing scripts
      await chrome.scripting.updateContentScripts([{
        id: 'my-script',
        matches: ['*://example.com/*', '*://example.org/*'],
      }]);
      ```
      
      ### chrome.history
      
      Access browser history.
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/history
      
      ```typescript
      // Search history
      const history = await browser.history.search({
        text: 'github',
        startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // Last 7 days
        maxResults: 100,
      });
      
      // Get visits for URL
      const visits = await browser.history.getVisits({
        url: 'https://github.com',
      });
      
      // Add URL to history
      await browser.history.addUrl({
        url: 'https://example.com',
        title: 'Example Domain',
      });
      
      // Remove URL from history
      await browser.history.deleteUrl({
        url: 'https://example.com',
      });
      
      // Remove all history in time range
      await browser.history.deleteRange({
        startTime: Date.now() - 24 * 60 * 60 * 1000, // Last 24 hours
        endTime: Date.now(),
      });
      
      // Delete all history
      await browser.history.deleteAll();
      
      // Listen for history changes
      browser.history.onVisited.addListener((result) => {
        console.log('Page visited:', result.url);
      });
      ```
      
      ## Chrome 140+ Features (September 2025)
      
      ### sidePanel.getLayout()
      
      New in Chrome 140 - determine side panel side (left or right).
      
      **Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/sidePanel#method-getLayout
      
      ```typescript
      // Get side panel layout
      const layout = await chrome.sidePanel.getLayout();
      console.log('Side panel side:', layout.side); // 'left' or 'right'
      
      // Useful for RTL language support
      if (layout.side === 'right') {
        // Apply RTL-specific styling or behavior
      }
      ```
      
      **Use Cases:**
      - Adapting UI for RTL languages
      - Adjusting panel content based on side
      - Optimizing user experience based on panel location
      
      **Browser Support:** Chrome 140+ (September 2025)
      
      ## Permission Patterns
      
      ### Required Permissions
      
      **manifest.json:**
      ```json
      {
        "permissions": [
          "storage",
          "tabs",
          "activeTab",
          "alarms",
          "notifications",
          "contextMenus"
        ],
        "host_permissions": [
          "*://example.com/*",
          "*://api.example.com/*"
        ],
        "optional_permissions": [
          "downloads",
          "bookmarks",
          "history"
        ]
      }
      ```
      
      ### Request Optional Permissions
      
      ```typescript
      // Check if permission granted
      const hasPermission = await browser.permissions.contains({
        permissions: ['downloads'],
        origins: ['*://downloads.example.com/*'],
      });
      
      // Request permission
      const granted = await browser.permissions.request({
        permissions: ['downloads'],
        origins: ['*://downloads.example.com/*'],
      });
      
      if (granted) {
        // Permission granted, use the API
        await browser.downloads.download({ url: 'https://example.com/file.pdf' });
      }
      
      // Remove permission
      await browser.permissions.remove({
        permissions: ['downloads'],
      });
      
      // Listen for permission changes
      browser.permissions.onAdded.addListener((permissions) => {
        console.log('Permissions added:', permissions);
      });
      
      browser.permissions.onRemoved.addListener((permissions) => {
        console.log('Permissions removed:', permissions);
      });
      ```
      
      ## Content Script Communication
      
      ### Sending Messages
      
      ```typescript
      // Content script → Background
      const response = await browser.runtime.sendMessage({
        type: 'getData',
        payload: { key: 'value' },
      });
      
      // Background → Content script
      const response = await browser.tabs.sendMessage(tabId, {
        type: 'updateUI',
        payload: { theme: 'dark' },
      });
      ```
      
      ### Long-Lived Connections
      
      ```typescript
      // Content script
      const port = browser.runtime.connect({ name: 'my-channel' });
      
      port.postMessage({ type: 'init' });
      
      port.onMessage.addListener((msg) => {
        console.log('Received:', msg);
      });
      
      port.onDisconnect.addListener(() => {
        console.log('Disconnected');
      });
      
      // Background script
      browser.runtime.onConnect.addListener((port) => {
        if (port.name === 'my-channel') {
          port.onMessage.addListener((msg) => {
            // Handle message
            port.postMessage({ response: 'acknowledged' });
          });
        }
      });
      ```
      
      ## Official Documentation Links
      
      - **Get Started:** https://developer.chrome.com/docs/extensions/get-started
      - **API Reference:** https://developer.chrome.com/docs/extensions/reference/api
      - **Manifest V3:** https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3
      - **Content Scripts:** https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts
      - **Service Workers:** https://developer.chrome.com/docs/extensions/develop/concepts/service-workers
      - **Message Passing:** https://developer.chrome.com/docs/extensions/develop/concepts/messaging
      - **Storage:** https://developer.chrome.com/docs/extensions/reference/api/storage
      - **Permissions:** https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions
      - **Publishing:** https://developer.chrome.com/docs/webstore/publish
      - **Best Practices:** https://developer.chrome.com/docs/extensions/develop/concepts/best-practices
      
    • react-integration.md 21 KB
      # React Integration with WXT
      
      Complete guide for building Chrome extensions with React and WXT.
      
      ## Setup
      
      ### Initialize with React Template
      
      ```bash
      npm create wxt@latest -- --template react-ts
      cd my-extension
      npm install
      ```
      
      ### Manual Setup
      
      ```bash
      npm install react react-dom
      npm install -D @types/react @types/react-dom @wxt-dev/module-react
      ```
      
      **wxt.config.ts:**
      ```typescript
      import { defineConfig } from 'wxt';
      
      export default defineConfig({
        modules: ['@wxt-dev/module-react'],
      
        manifest: {
          content_security_policy: {
            extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
          },
        },
      });
      ```
      
      ## Entry Point Patterns
      
      ### Popup with React
      
      **Directory structure:**
      ```
      entrypoints/popup/
      ├── index.html
      ├── main.tsx        # Entry point
      ├── App.tsx         # Root component
      └── components/     # UI components
          ├── Header.tsx
          └── Settings.tsx
      ```
      
      **entrypoints/popup/index.html:**
      ```html
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Extension Popup</title>
      </head>
      <body>
        <div id="root"></div>
        <script type="module" src="./main.tsx"></script>
      </body>
      </html>
      ```
      
      **entrypoints/popup/main.tsx:**
      ```typescript
      import React from 'react';
      import ReactDOM from 'react-dom/client';
      import App from './App';
      import './style.css';
      
      ReactDOM.createRoot(document.getElementById('root')!).render(
        <React.StrictMode>
          <App />
        </React.StrictMode>
      );
      ```
      
      **entrypoints/popup/App.tsx:**
      ```typescript
      import { useState, useEffect } from 'react';
      
      export default function App() {
        const [data, setData] = useState<any>(null);
        const [loading, setLoading] = useState(true);
      
        useEffect(() => {
          loadData();
        }, []);
      
        async function loadData() {
          try {
            // Get current tab
            const [tab] = await browser.tabs.query({
              active: true,
              currentWindow: true
            });
      
            // Load from storage
            const result = await browser.storage.local.get('settings');
            setData(result.settings);
          } catch (error) {
            console.error('Error loading data:', error);
          } finally {
            setLoading(false);
          }
        }
      
        async function handleSave(newSettings: any) {
          await browser.storage.local.set({ settings: newSettings });
          setData(newSettings);
        }
      
        if (loading) {
          return <div className="loading">Loading...</div>;
        }
      
        return (
          <div className="app">
            <h1>My Extension</h1>
            <div className="content">
              {/* Your UI here */}
            </div>
          </div>
        );
      }
      ```
      
      ### Options Page with React
      
      **entrypoints/options/index.html:**
      ```html
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <title>Extension Options</title>
      </head>
      <body>
        <div id="root"></div>
        <script type="module" src="./main.tsx"></script>
      </body>
      </html>
      ```
      
      **entrypoints/options/App.tsx:**
      ```typescript
      import { useState, useEffect } from 'react';
      
      export default function Options() {
        const [settings, setSettings] = useState({
          theme: 'light',
          notifications: true,
          apiKey: '',
        });
      
        useEffect(() => {
          // Load settings
          browser.storage.sync.get('settings').then((result) => {
            if (result.settings) {
              setSettings(result.settings);
            }
          });
        }, []);
      
        async function handleSave() {
          await browser.storage.sync.set({ settings });
      
          // Show success notification
          await browser.notifications.create({
            type: 'basic',
            title: 'Settings Saved',
            message: 'Your settings have been saved successfully',
            iconUrl: '/icon/128.png',
          });
        }
      
        return (
          <div className="options-page">
            <h1>Extension Settings</h1>
      
            <section>
              <label>
                Theme:
                <select
                  value={settings.theme}
                  onChange={(e) => setSettings({ ...settings, theme: e.target.value })}
                >
                  <option value="light">Light</option>
                  <option value="dark">Dark</option>
                </select>
              </label>
            </section>
      
            <section>
              <label>
                <input
                  type="checkbox"
                  checked={settings.notifications}
                  onChange={(e) => setSettings({ ...settings, notifications: e.target.checked })}
                />
                Enable Notifications
              </label>
            </section>
      
            <section>
              <label>
                API Key:
                <input
                  type="password"
                  value={settings.apiKey}
                  onChange={(e) => setSettings({ ...settings, apiKey: e.target.value })}
                  placeholder="Enter your API key"
                />
              </label>
            </section>
      
            <button onClick={handleSave}>Save Settings</button>
          </div>
        );
      }
      ```
      
      ### Content Script with React UI
      
      **entrypoints/content.ts:**
      ```typescript
      import ReactDOM from 'react-dom/client';
      import { ContentScriptApp } from './ContentScriptApp';
      
      export default defineContentScript({
        matches: ['*://*.example.com/*'],
        cssInjectionMode: 'ui',
      
        async main(ctx) {
          // Create shadow root UI
          const ui = await createShadowRootUi(ctx, {
            name: 'my-extension-overlay',
            position: 'overlay',
            anchor: 'body',
      
            onMount: (container) => {
              // Mount React app in shadow DOM
              const root = ReactDOM.createRoot(container);
              root.render(<ContentScriptApp />);
      
              return root;
            },
      
            onRemove: (root) => {
              // Cleanup
              root?.unmount();
            },
          });
      
          // Mount UI
          ui.mount();
        },
      });
      ```
      
      **entrypoints/ContentScriptApp.tsx:**
      ```typescript
      import { useState } from 'react';
      import './content.css';
      
      export function ContentScriptApp() {
        const [visible, setVisible] = useState(false);
      
        return (
          <div className="extension-overlay">
            <button
              className="toggle-button"
              onClick={() => setVisible(!visible)}
            >
              Toggle Panel
            </button>
      
            {visible && (
              <div className="extension-panel">
                <h2>Extension Panel</h2>
                <p>This is injected into the page!</p>
              </div>
            )}
          </div>
        );
      }
      ```
      
      ## React Hooks for Extensions
      
      ### useStorage Hook
      
      ```typescript
      // hooks/useStorage.ts
      import { useState, useEffect } from 'react';
      
      export function useStorage<T>(key: string, defaultValue: T) {
        const [value, setValue] = useState<T>(defaultValue);
        const [loading, setLoading] = useState(true);
      
        useEffect(() => {
          // Load initial value
          browser.storage.local.get(key).then((result) => {
            if (result[key] !== undefined) {
              setValue(result[key]);
            }
            setLoading(false);
          });
      
          // Listen for changes
          const listener = (changes: any, area: string) => {
            if (area === 'local' && changes[key]) {
              setValue(changes[key].newValue);
            }
          };
      
          browser.storage.onChanged.addListener(listener);
      
          return () => {
            browser.storage.onChanged.removeListener(listener);
          };
        }, [key]);
      
        const updateValue = async (newValue: T) => {
          await browser.storage.local.set({ [key]: newValue });
          setValue(newValue);
        };
      
        return [value, updateValue, loading] as const;
      }
      
      // Usage
      function MyComponent() {
        const [theme, setTheme, loading] = useStorage('theme', 'light');
      
        if (loading) return <div>Loading...</div>;
      
        return (
          <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
            Current theme: {theme}
          </button>
        );
      }
      ```
      
      ### useMessage Hook
      
      ```typescript
      // hooks/useMessage.ts
      import { useEffect, useCallback } from 'react';
      
      type MessageHandler<T = any> = (
        message: T,
        sender: browser.Runtime.MessageSender
      ) => any | Promise<any>;
      
      export function useMessage<T = any>(
        type: string,
        handler: MessageHandler<T>
      ) {
        useEffect(() => {
          const listener = (
            message: any,
            sender: browser.Runtime.MessageSender,
            sendResponse: (response?: any) => void
          ) => {
            if (message.type === type) {
              Promise.resolve(handler(message.payload, sender))
                .then(sendResponse)
                .catch((error) => {
                  console.error(`Error handling message ${type}:`, error);
                  sendResponse({ error: error.message });
                });
              return true; // Keep channel open
            }
          };
      
          browser.runtime.onMessage.addListener(listener);
      
          return () => {
            browser.runtime.onMessage.removeListener(listener);
          };
        }, [type, handler]);
      }
      
      // Usage
      function MyComponent() {
        useMessage('get-data', async (payload, sender) => {
          console.log('Message from:', sender.tab?.url);
          return { data: 'some data' };
        });
      
        return <div>Component listening for messages</div>;
      }
      ```
      
      ### useTabs Hook
      
      ```typescript
      // hooks/useTabs.ts
      import { useState, useEffect } from 'react';
      
      export function useTabs() {
        const [tabs, setTabs] = useState<browser.Tabs.Tab[]>([]);
      
        useEffect(() => {
          // Load initial tabs
          browser.tabs.query({}).then(setTabs);
      
          // Listen for tab changes
          const onCreated = (tab: browser.Tabs.Tab) => {
            setTabs((prev) => [...prev, tab]);
          };
      
          const onRemoved = (tabId: number) => {
            setTabs((prev) => prev.filter((t) => t.id !== tabId));
          };
      
          const onUpdated = (tabId: number, changeInfo: any, tab: browser.Tabs.Tab) => {
            setTabs((prev) =>
              prev.map((t) => (t.id === tabId ? tab : t))
            );
          };
      
          browser.tabs.onCreated.addListener(onCreated);
          browser.tabs.onRemoved.addListener(onRemoved);
          browser.tabs.onUpdated.addListener(onUpdated);
      
          return () => {
            browser.tabs.onCreated.removeListener(onCreated);
            browser.tabs.onRemoved.removeListener(onRemoved);
            browser.tabs.onUpdated.removeListener(onUpdated);
          };
        }, []);
      
        return tabs;
      }
      
      // Usage
      function TabList() {
        const tabs = useTabs();
      
        return (
          <ul>
            {tabs.map((tab) => (
              <li key={tab.id}>
                {tab.title} - {tab.url}
              </li>
            ))}
          </ul>
        );
      }
      ```
      
      ## State Management
      
      ### Context API for Extension State
      
      ```typescript
      // contexts/ExtensionContext.tsx
      import { createContext, useContext, useEffect, useState } from 'react';
      
      interface ExtensionState {
        settings: any;
        user: any;
        updateSettings: (settings: any) => Promise<void>;
      }
      
      const ExtensionContext = createContext<ExtensionState | null>(null);
      
      export function ExtensionProvider({ children }: { children: React.ReactNode }) {
        const [settings, setSettings] = useState({});
        const [user, setUser] = useState(null);
      
        useEffect(() => {
          // Load initial state
          browser.storage.local.get(['settings', 'user']).then((result) => {
            setSettings(result.settings || {});
            setUser(result.user || null);
          });
      
          // Listen for storage changes
          const listener = (changes: any) => {
            if (changes.settings) {
              setSettings(changes.settings.newValue);
            }
            if (changes.user) {
              setUser(changes.user.newValue);
            }
          };
      
          browser.storage.onChanged.addListener(listener);
      
          return () => {
            browser.storage.onChanged.removeListener(listener);
          };
        }, []);
      
        const updateSettings = async (newSettings: any) => {
          await browser.storage.local.set({ settings: newSettings });
          setSettings(newSettings);
        };
      
        return (
          <ExtensionContext.Provider value={{ settings, user, updateSettings }}>
            {children}
          </ExtensionContext.Provider>
        );
      }
      
      export function useExtension() {
        const context = useContext(ExtensionContext);
        if (!context) {
          throw new Error('useExtension must be used within ExtensionProvider');
        }
        return context;
      }
      
      // Usage in App
      function App() {
        return (
          <ExtensionProvider>
            <YourComponents />
          </ExtensionProvider>
        );
      }
      ```
      
      ### Zustand for Extension State
      
      ```typescript
      // store/useStore.ts
      import { create } from 'zustand';
      import { persist } from 'zustand/middleware';
      
      interface ExtensionStore {
        theme: 'light' | 'dark';
        notifications: boolean;
        apiKey: string;
        setTheme: (theme: 'light' | 'dark') => void;
        setNotifications: (enabled: boolean) => void;
        setApiKey: (key: string) => void;
      }
      
      export const useStore = create<ExtensionStore>()(
        persist(
          (set) => ({
            theme: 'light',
            notifications: true,
            apiKey: '',
      
            setTheme: (theme) => set({ theme }),
            setNotifications: (notifications) => set({ notifications }),
            setApiKey: (apiKey) => set({ apiKey }),
          }),
          {
            name: 'extension-storage',
            getStorage: () => ({
              getItem: async (name) => {
                const result = await browser.storage.local.get(name);
                return result[name] || null;
              },
              setItem: async (name, value) => {
                await browser.storage.local.set({ [name]: value });
              },
              removeItem: async (name) => {
                await browser.storage.local.remove(name);
              },
            }),
          }
        )
      );
      
      // Usage
      function SettingsComponent() {
        const { theme, setTheme } = useStore();
      
        return (
          <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
            Toggle Theme (current: {theme})
          </button>
        );
      }
      ```
      
      ## Styling
      
      ### Popular UI Libraries (2025)
      
      Modern Chrome extensions commonly use these UI libraries with React:
      
      #### shadcn/ui
      Most popular choice for Chrome extensions in 2025.
      
      ```bash
      npx shadcn@latest init
      npx shadcn@latest add button card dialog
      ```
      
      **Benefits:**
      - Tailwind CSS-based components
      - Full customization and ownership of code
      - Copy-paste philosophy - components live in your codebase
      - Excellent TypeScript support
      - Works seamlessly with WXT
      
      **Example Setup:**
      ```typescript
      // components/ui/button.tsx (generated by shadcn)
      import * as React from "react"
      import { cn } from "@/lib/utils"
      
      export interface ButtonProps
        extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
      
      const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
        ({ className, ...props }, ref) => {
          return (
            <button
              className={cn(
                "inline-flex items-center justify-center rounded-md px-4 py-2",
                className
              )}
              ref={ref}
              {...props}
            />
          )
        }
      )
      ```
      
      **Popular Template:** https://github.com/imtiger/wxt-react-shadcn-tailwindcss-chrome-extension
      
      #### Mantine UI
      Complete component library with 100+ components.
      
      ```bash
      npm install @mantine/core @mantine/hooks
      npm install -D postcss-preset-mantine postcss-simple-vars
      ```
      
      **Benefits:**
      - Rich component ecosystem (100+ components)
      - Built-in dark mode support
      - Comprehensive form management with @mantine/form
      - Accessible by default
      - Excellent documentation
      
      **Example Setup:**
      ```typescript
      // entrypoints/popup/main.tsx
      import { MantineProvider } from '@mantine/core';
      import '@mantine/core/styles.css';
      
      function App() {
        return (
          <MantineProvider>
            <YourApp />
          </MantineProvider>
        );
      }
      ```
      
      **Popular Template:** https://github.com/ongkay/WXT-Mantine-Tailwind-Browser-Extension
      
      #### Tailwind CSS (Utility-First)
      Most flexible option for custom designs.
      
      ```bash
      npm install -D tailwindcss postcss autoprefixer
      npx tailwindcss init -p
      ```
      
      **Benefits:**
      - Maximum design flexibility
      - Small bundle size with purging
      - No additional component library needed
      - Works with shadcn/ui for pre-built components
      
      ### Tailwind CSS Setup
      
      ```bash
      npm install -D tailwindcss postcss autoprefixer
      npx tailwindcss init -p
      ```
      
      **tailwind.config.js:**
      ```javascript
      export default {
        content: [
          './entrypoints/**/*.{html,tsx}',
          './components/**/*.tsx',
        ],
        theme: {
          extend: {},
        },
        plugins: [],
      };
      ```
      
      **entrypoints/popup/style.css:**
      ```css
      @tailwind base;
      @tailwind components;
      @tailwind utilities;
      
      /* Extension-specific styles */
      .popup-container {
        @apply w-96 h-[500px] p-4;
      }
      ```
      
      ### CSS Modules
      
      WXT supports CSS Modules automatically:
      
      **Button.module.css:**
      ```css
      .button {
        padding: 8px 16px;
        background: blue;
        color: white;
        border: none;
        border-radius: 4px;
      }
      
      .button:hover {
        background: darkblue;
      }
      ```
      
      **Button.tsx:**
      ```typescript
      import styles from './Button.module.css';
      
      export function Button({ children, onClick }: any) {
        return (
          <button className={styles.button} onClick={onClick}>
            {children}
          </button>
        );
      }
      ```
      
      ### Styled Components
      
      ```bash
      npm install styled-components
      npm install -D @types/styled-components
      ```
      
      ```typescript
      import styled from 'styled-components';
      
      const Button = styled.button`
        padding: 8px 16px;
        background: ${(props) => props.theme.primary};
        color: white;
        border: none;
        border-radius: 4px;
      
        &:hover {
          opacity: 0.8;
        }
      `;
      
      export function MyComponent() {
        return <Button>Click me</Button>;
      }
      ```
      
      ## Performance Optimization
      
      ### Code Splitting
      
      ```typescript
      // Lazy load heavy components
      import { lazy, Suspense } from 'react';
      
      const HeavyComponent = lazy(() => import('./HeavyComponent'));
      
      function App() {
        return (
          <Suspense fallback={<div>Loading...</div>}>
            <HeavyComponent />
          </Suspense>
        );
      }
      ```
      
      ### React.memo for Expensive Components
      
      ```typescript
      import { memo } from 'react';
      
      const ExpensiveComponent = memo(({ data }: { data: any }) => {
        // Expensive rendering logic
        return <div>{/* rendered content */}</div>;
      });
      ```
      
      ### useMemo and useCallback
      
      ```typescript
      import { useMemo, useCallback } from 'react';
      
      function DataTable({ data }: { data: any[] }) {
        // Memoize expensive calculations
        const sortedData = useMemo(() => {
          return data.sort((a, b) => a.value - b.value);
        }, [data]);
      
        // Memoize callbacks
        const handleClick = useCallback((id: number) => {
          console.log('Clicked:', id);
        }, []);
      
        return (
          <table>
            {sortedData.map((item) => (
              <tr key={item.id} onClick={() => handleClick(item.id)}>
                <td>{item.name}</td>
              </tr>
            ))}
          </table>
        );
      }
      ```
      
      ## Common Patterns
      
      ### Loading States
      
      ```typescript
      function DataLoader() {
        const [data, setData] = useState(null);
        const [loading, setLoading] = useState(true);
        const [error, setError] = useState<Error | null>(null);
      
        useEffect(() => {
          fetchData()
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
        }, []);
      
        if (loading) return <LoadingSpinner />;
        if (error) return <ErrorMessage error={error} />;
        if (!data) return <EmptyState />;
      
        return <DataDisplay data={data} />;
      }
      ```
      
      ### Form Handling
      
      ```typescript
      import { useState, FormEvent } from 'react';
      
      function SettingsForm() {
        const [formData, setFormData] = useState({
          name: '',
          email: '',
          notifications: true,
        });
      
        const handleSubmit = async (e: FormEvent) => {
          e.preventDefault();
      
          try {
            await browser.storage.sync.set({ settings: formData });
            console.log('Settings saved');
          } catch (error) {
            console.error('Failed to save:', error);
          }
        };
      
        return (
          <form onSubmit={handleSubmit}>
            <input
              type="text"
              value={formData.name}
              onChange={(e) => setFormData({ ...formData, name: e.target.value })}
            />
            <input
              type="email"
              value={formData.email}
              onChange={(e) => setFormData({ ...formData, email: e.target.value })}
            />
            <label>
              <input
                type="checkbox"
                checked={formData.notifications}
                onChange={(e) => setFormData({ ...formData, notifications: e.target.checked })}
              />
              Enable Notifications
            </label>
            <button type="submit">Save</button>
          </form>
        );
      }
      ```
      
      ### Modal/Dialog Patterns
      
      ```typescript
      function ConfirmDialog({ isOpen, onConfirm, onCancel }: any) {
        if (!isOpen) return null;
      
        return (
          <div className="modal-overlay" onClick={onCancel}>
            <div className="modal-content" onClick={(e) => e.stopPropagation()}>
              <h2>Are you sure?</h2>
              <div className="modal-actions">
                <button onClick={onConfirm}>Confirm</button>
                <button onClick={onCancel}>Cancel</button>
              </div>
            </div>
          </div>
        );
      }
      ```
      
      ## Testing React Extensions
      
      ### Component Testing with Vitest
      
      ```typescript
      // Button.test.tsx
      import { render, screen, fireEvent } from '@testing-library/react';
      import { describe, it, expect, vi } from 'vitest';
      import { Button } from './Button';
      
      describe('Button', () => {
        it('renders children', () => {
          render(<Button>Click me</Button>);
          expect(screen.getByText('Click me')).toBeInTheDocument();
        });
      
        it('calls onClick when clicked', () => {
          const handleClick = vi.fn();
          render(<Button onClick={handleClick}>Click me</Button>);
      
          fireEvent.click(screen.getByText('Click me'));
          expect(handleClick).toHaveBeenCalledTimes(1);
        });
      });
      ```
      
      ## Common Issues & Solutions
      
      ### Issue: React DevTools not working
      
      **Solution:** Add to manifest:
      ```typescript
      manifest: {
        content_security_policy: {
          extension_pages: "script-src 'self' 'unsafe-eval'; object-src 'self'",
        },
      }
      ```
      
      ### Issue: Hot reload breaks React state
      
      **Solution:** Use React Fast Refresh properly:
      ```typescript
      // In components, ensure proper export
      export default function MyComponent() {
        // Component logic
      }
      ```
      
      ### Issue: Storage not syncing between components
      
      **Solution:** Use storage change listeners:
      ```typescript
      useEffect(() => {
        const listener = (changes: any) => {
          if (changes.key) {
            setState(changes.key.newValue);
          }
        };
      
        browser.storage.onChanged.addListener(listener);
        return () => browser.storage.onChanged.removeListener(listener);
      }, []);
      ```
      
    • wxt-api.md 6.2 KB
      # WXT API Reference
      
      Complete API documentation for WXT framework functions and utilities.
      
      ## Core APIs
      
      ### defineBackground()
      
      Define service worker (background script) behavior.
      
      ```typescript
      export default defineBackground({
        type: 'module' | 'esm',
        persistent: boolean,
        main(ctx) {
          // Background logic
        }
      });
      ```
      
      ### defineContentScript()
      
      Define content script that runs on web pages.
      
      ```typescript
      export default defineContentScript({
        matches: string[],
        excludeMatches?: string[],
        runAt: 'document_start' | 'document_end' | 'document_idle',
        world: 'ISOLATED' | 'MAIN',
        cssInjectionMode: 'ui' | 'inline' | 'manual',
      
        main(ctx: ContentScriptContext) {
          // Content script logic
        }
      });
      ```
      
      ### createShadowRootUi()
      
      Create isolated UI components in content scripts.
      
      ```typescript
      const ui = createShadowRootUi(ctx, {
        name: string,
        position: 'inline' | 'overlay' | 'modal',
        anchor: string | HTMLElement,
      
        onMount(container: HTMLElement) {
          // Mount UI framework
        },
      
        onRemove?(container: HTMLElement) {
          // Cleanup
        }
      });
      
      ui.mount();
      ui.remove();
      ```
      
      ### storage
      
      WXT storage API with type safety.
      
      ```typescript
      import { storage } from 'wxt/storage';
      
      // Get item
      const value = await storage.getItem<T>('local:key');
      
      // Set item
      await storage.setItem('local:key', value);
      
      // Remove item
      await storage.removeItem('local:key');
      
      // Watch for changes
      const unwatch = storage.watch<T>('local:key', (newValue, oldValue) => {
        // Handle change
      });
      ```
      
      ### injectScript()
      
      Inject scripts into page context.
      
      ```typescript
      import { injectScript } from 'wxt/client';
      
      await injectScript('/script.js', {
        keepInDom: boolean,
      });
      ```
      
      ## Browser API
      
      WXT provides unified `browser` API that works across all browsers:
      
      ```typescript
      // Tabs
      await browser.tabs.query({ active: true });
      await browser.tabs.sendMessage(tabId, message);
      
      // Runtime
      await browser.runtime.sendMessage(message);
      browser.runtime.onMessage.addListener(handler);
      
      // Storage
      await browser.storage.local.get(key);
      await browser.storage.local.set({ key: value });
      await browser.storage.sync.set({ key: value });
      
      // Action (toolbar icon)
      browser.action.onClicked.addListener(handler);
      await browser.action.setBadgeText({ text: '5' });
      await browser.action.setIcon({ path: '/icon.png' });
      ```
      
      ## Configuration API
      
      ### defineConfig()
      
      ```typescript
      import { defineConfig } from 'wxt';
      
      export default defineConfig({
        // Target browser
        browser: 'chrome' | 'firefox' | 'edge' | 'safari',
      
        // Modules (framework integration)
        modules: ['@wxt-dev/module-react'],
      
        // Manifest overrides
        manifest: {
          name: string,
          description: string,
          version: string,
          permissions: string[],
          host_permissions: string[],
          content_security_policy: {
            extension_pages: string,
          },
        },
      
        // Vite configuration
        vite: () => ({
          // Vite config
        }),
      });
      ```
      
      ### defineWebExtConfig()
      
      Configure browser runner behavior during development.
      
      ```typescript
      import { defineWebExtConfig } from 'wxt';
      
      export default defineWebExtConfig({
        // Custom browser binary paths
        binaries: {
          chrome: '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
          firefox: 'firefoxdeveloperedition',
          edge: '/usr/bin/microsoft-edge-dev',
        },
      
        // Chrome/Chromium-specific arguments
        chromiumArgs: [
          '--user-data-dir=./.wxt/chrome-data',
          '--disable-features=DialMediaRouteProvider',
        ],
      
        // Firefox-specific arguments
        firefoxArgs: [
          '--profile',
          './.wxt/firefox-profile',
        ],
      
        // Keep profile data between runs (preserves logins, storage)
        keepProfileChanges: true,
      
        // Browser to launch (overrides wxt.config.ts)
        target: 'chrome-mv3',
      
        // Start URL when browser opens
        startUrl: 'https://example.com',
      
        // Additional preferences
        chromiumProfile: './.wxt/chrome-profile',
        firefoxProfile: './.wxt/firefox-profile',
      });
      ```
      
      **Common Use Cases:**
      
      #### Development with Chrome Beta/Canary
      
      ```typescript
      // web-ext.config.ts
      export default defineWebExtConfig({
        binaries: {
          chrome: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
        },
        chromiumArgs: [
          '--user-data-dir=./.wxt/chrome-canary-data',
        ],
      });
      ```
      
      #### Persistent Login Sessions
      
      ```typescript
      // web-ext.config.ts
      export default defineWebExtConfig({
        keepProfileChanges: true,
        chromiumProfile: './.wxt/dev-profile',
      
        // Start with test page open
        startUrl: 'https://app.example.com/dashboard',
      });
      ```
      
      #### Firefox Developer Edition
      
      ```typescript
      // web-ext.config.ts
      export default defineWebExtConfig({
        binaries: {
          firefox: 'firefoxdeveloperedition',
        },
        firefoxArgs: [
          '--profile',
          './.wxt/firefox-dev-profile',
        ],
        keepProfileChanges: true,
      });
      ```
      
      **Configuration File Location:** `web-ext.config.ts` in project root
      
      ## Context APIs
      
      ### ContentScriptContext
      
      Available in content scripts:
      
      ```typescript
      interface ContentScriptContext {
        addEventListener<K extends keyof WindowEventMap>(
          target: Window | Document | HTMLElement,
          type: K,
          listener: (event: WindowEventMap[K]) => void
        ): void;
      
        isValid: boolean;
      
        signal: AbortSignal;
      }
      ```
      
      ## Utility Functions
      
      ### MatchPattern
      
      Pattern matching for URLs:
      
      ```typescript
      import { MatchPattern } from 'wxt/match-pattern';
      
      const pattern = new MatchPattern('*://*.youtube.com/*');
      
      if (pattern.includes('https://www.youtube.com/watch')) {
        // Matches
      }
      ```
      
      ### Location Change Detection
      
      Detect SPA navigation:
      
      ```typescript
      ctx.addEventListener(window, 'wxt:locationchange', ({ newUrl }) => {
        console.log('Navigated to:', newUrl);
      });
      ```
      
      ## Build APIs
      
      ### Environment Variables
      
      ```typescript
      // Available at build time
      import.meta.env.MODE // 'development' | 'production'
      import.meta.env.BROWSER // 'chrome' | 'firefox' | etc.
      import.meta.env.VITE_* // Custom env variables
      ```
      
      ### Asset URLs
      
      ```typescript
      // Get public asset URL
      const iconUrl = browser.runtime.getURL('/icon/128.png');
      ```
      
      ## Hooks System
      
      WXT provides build hooks for customization:
      
      ```typescript
      export default defineConfig({
        hooks: {
          'build:manifestGenerated': (wxt, manifest) => {
            // Modify manifest before writing
          },
      
          'build:publicAssets': (wxt, assets) => {
            // Add/modify public assets
          },
        },
      });
      ```
      
  • LICENSE.txt 8.9 KB
    Apache License
    Version 2.0, January 2004
    https://www.apache.org/licenses/
    
    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    
    1. Definitions.
    
    "License" shall mean the terms and conditions for use, reproduction, and
    distribution as defined by Sections 1 through 9 of this document.
    
    "Licensor" shall mean the copyright owner or entity authorized by the
    copyright owner that is granting the License.
    
    "Legal Entity" shall mean the union of the acting entity and all other
    entities that control, are controlled by, or are under common control with
    that entity. For the purposes of this definition, "control" means (i) the
    power, direct or indirect, to cause the direction or management of such
    entity, whether by contract or otherwise, or (ii) ownership of fifty percent
    (50%) or more of the outstanding shares, or (iii) beneficial ownership of
    such entity.
    
    "You" (or "Your") shall mean an individual or Legal Entity exercising
    permissions granted by this License.
    
    "Source" form shall mean the preferred form for making modifications,
    including but not limited to software source code, documentation source, and
    configuration files.
    
    "Object" form shall mean any form resulting from mechanical transformation or
    translation of a Source form, including but not limited to compiled object
    code, generated documentation, and conversions to other media types.
    
    "Work" shall mean the work of authorship, whether in Source or Object form,
    made available under the License, as indicated by a copyright notice that is
    included in or attached to the work (an example is provided in the Appendix
    below).
    
    "Derivative Works" shall mean any work, whether in Source or Object form,
    that is based on (or derived from) the Work and for which the editorial
    revisions, annotations, elaborations, or other modifications represent, as a
    whole, an original work of authorship. For the purposes of this License,
    Derivative Works shall not include works that remain separable from, or
    merely link (or bind by name) to the interfaces of, the Work and Derivative
    Works thereof.
    
    "Contribution" shall mean any work of authorship, including the original
    version of the Work and any modifications or additions to that Work or
    Derivative Works thereof, that is intentionally submitted to Licensor for
    inclusion in the Work by the copyright owner or by an individual or Legal
    Entity authorized to submit on behalf of the copyright owner. For the
    purposes of this definition, "submitted" means any form of electronic, verbal,
    or written communication sent to the Licensor or its representatives,
    including but not limited to communication on electronic mailing lists, source
    code control systems, and issue tracking systems that are managed by, or on
    behalf of, the Licensor for the purpose of discussing and improving the Work,
    but excluding communication that is conspicuously marked or otherwise
    designated in writing by the copyright owner as "Not a Contribution."
    
    "Contributor" shall mean Licensor and any individual or Legal Entity on
    behalf of whom a Contribution has been received by Licensor and subsequently
    incorporated within the Work.
    
    2. Grant of Copyright License. Subject to the terms and conditions of this
    License, each Contributor hereby grants to You a perpetual, worldwide,
    non-exclusive, no-charge, royalty-free, irrevocable copyright license to
    reproduce, prepare Derivative Works of, publicly display, publicly perform,
    sublicense, and distribute the Work and such Derivative Works in Source or
    Object form.
    
    3. Grant of Patent License. Subject to the terms and conditions of this
    License, each Contributor hereby grants to You a perpetual, worldwide,
    non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
    section) patent license to make, have made, use, offer to sell, sell, import,
    and otherwise transfer the Work, where such license applies only to those
    patent claims licensable by such Contributor that are necessarily infringed by
    their Contribution(s) alone or by combination of their Contribution(s) with
    the Work to which such Contribution(s) was submitted. If You institute patent
    litigation against any entity (including a cross-claim or counterclaim in a
    lawsuit) alleging that the Work or a Contribution incorporated within the Work
    constitutes direct or contributory patent infringement, then any patent
    licenses granted to You under this License for that Work shall terminate as of
    the date such litigation is filed.
    
    4. Redistribution. You may reproduce and distribute copies of the Work or
    Derivative Works thereof in any medium, with or without modifications, and in
    Source or Object form, provided that You meet the following conditions:
    
    (a) You must give any other recipients of the Work or Derivative Works a copy
    of this License; and
    
    (b) You must cause any modified files to carry prominent notices stating that
    You changed the files; and
    
    (c) You must retain, in the Source form of any Derivative Works that You
    distribute, all copyright, patent, trademark, and attribution notices from
    the Source form of the Work, excluding those notices that do not pertain to
    any part of the Derivative Works; and
    
    (d) If the Work includes a "NOTICE" text file as part of its distribution,
    then any Derivative Works that You distribute must include a readable copy of
    the attribution notices contained within such NOTICE file, excluding those
    notices that do not pertain to any part of the Derivative Works, in at least
    one of the following places: within a NOTICE text file distributed as part of
    the Derivative Works; within the Source form or documentation, if provided
    along with the Derivative Works; or, within a display generated by the
    Derivative Works, if and wherever such third-party notices normally appear.
    The contents of the NOTICE file are for informational purposes only and do not
    modify the License. You may add Your own attribution notices within Derivative
    Works that You distribute, alongside or as an addendum to the NOTICE text from
    the Work, provided that such additional attribution notices cannot be
    construed as modifying the License.
    
    You may add Your own copyright statement to Your modifications and may provide
    additional or different license terms and conditions for use, reproduction, or
    distribution of Your modifications, or for any such Derivative Works as a
    whole, provided Your use, reproduction, and distribution of the Work otherwise
    complies with the conditions stated in this License.
    
    5. Submission of Contributions. Unless You explicitly state otherwise, any
    Contribution intentionally submitted for inclusion in the Work by You to the
    Licensor shall be under the terms and conditions of this License, without any
    additional terms or conditions. Notwithstanding the above, nothing herein
    shall supersede or modify the terms of any separate license agreement you may
    have executed with Licensor regarding such Contributions.
    
    6. Trademarks. This License does not grant permission to use the trade names,
    trademarks, service marks, or product names of the Licensor, except as
    required for reasonable and customary use in describing the origin of the Work
    and reproducing the content of the NOTICE file.
    
    7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
    writing, Licensor provides the Work (and each Contributor provides its
    Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied, including, without limitation, any warranties
    or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    PARTICULAR PURPOSE. You are solely responsible for determining the
    appropriateness of using or redistributing the Work and assume any risks
    associated with Your exercise of permissions under this License.
    
    8. Limitation of Liability. In no event and under no legal theory, whether in
    tort (including negligence), contract, or otherwise, unless required by
    applicable law (such as deliberate and grossly negligent acts) or agreed to in
    writing, shall any Contributor be liable to You for damages, including any
    direct, indirect, special, incidental, or consequential damages of any
    character arising as a result of this License or out of the use or inability to
    use the Work (including but not limited to damages for loss of goodwill, work
    stoppage, computer failure or malfunction, or any and all other commercial
    damages or losses), even if such Contributor has been advised of the
    possibility of such damages.
    
    9. Accepting Warranty or Additional Liability. While redistributing the Work
    or Derivative Works thereof, You may choose to offer, and charge a fee for,
    acceptance of support, warranty, indemnity, or other liability obligations
    and/or rights consistent with this License. However, in accepting such
    obligations, You may act only on Your own behalf and on Your sole
    responsibility, not on behalf of any other Contributor, and only if You agree
    to indemnify, defend, and hold each Contributor harmless for any liability
    incurred by, or claims asserted against, such Contributor by reason of your
    accepting any such warranty or additional liability.
    
    END OF TERMS AND CONDITIONS
    
  • SKILL.md 8.3 KB
    ---
    name: chrome-extension-wxt
    description: Build Chrome extensions with the WXT framework and TypeScript, React, Vue, or Svelte. Use when creating browser extensions or cross-browser add-ons. Triggers on "chrome extension", "browser extension", WXT, manifest v3, or wxt.config.ts.
    metadata:
      version: "1.1.5"
      categories: "development"
      topics: "chrome-extension, wxt, browser-extension, typescript, react"
      openclaw:
        homepage: https://github.com/tenequm/skills/tree/main/skills/chrome-extension-wxt
        emoji: "🧩"
    ---
    
    # Chrome Extension Development with WXT
    
    Build modern, cross-browser extensions using WXT - the next-generation framework that supports Chrome, Firefox, Edge, Safari, and all Chromium browsers with a single codebase.
    
    ## When to Use This Skill
    
    Use this skill when:
    - Creating a new Chrome/browser extension
    - Setting up WXT development environment
    - Building extension features (popup, content scripts, background scripts)
    - Implementing cross-browser compatibility
    - Working with Manifest V3 (mandatory standard as of 2025, V2 deprecated)
    - Integrating React 19, Vue, Svelte, or Solid with extensions
    
    ## Quick Start Workflow
    
    ### 1. Initialize WXT Project
    
    ```bash
    # Create new project with framework of choice
    npm create wxt@latest
    
    # Or with specific template
    npm create wxt@latest -- --template react-ts
    npm create wxt@latest -- --template vue-ts
    npm create wxt@latest -- --template svelte-ts
    ```
    
    ### 2. Project Structure
    
    WXT uses file-based conventions:
    
    ```
    project/
    ├── entrypoints/              # Auto-discovered entry points
    │   ├── background.ts         # Service worker
    │   ├── content.ts           # Content script
    │   ├── popup.html           # Popup UI
    │   └── options.html         # Options page
    ├── components/              # Auto-imported UI components
    ├── utils/                   # Auto-imported utilities
    ├── public/                  # Static assets
    │   └── icon/               # Extension icons
    ├── wxt.config.ts           # Configuration
    └── package.json
    ```
    
    ### 3. Development Commands
    
    ```bash
    npm run dev              # Start dev server with HMR
    npm run build           # Production build
    npm run zip             # Package for store submission
    ```
    
    ## Core Entry Points
    
    WXT recognizes entry points by filename in `entrypoints/` directory:
    
    ### Background Script (Service Worker)
    
    ```typescript
    // entrypoints/background.ts
    export default defineBackground({
      type: 'module',
      persistent: false,
    
      main() {
        // Listen for extension events
        browser.action.onClicked.addListener((tab) => {
          console.log('Extension clicked', tab);
        });
    
        // Handle messages
        browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
          // Handle message
          sendResponse({ success: true });
          return true; // Keep channel open for async
        });
      },
    });
    ```
    
    ### Content Script
    
    ```typescript
    // entrypoints/content.ts
    export default defineContentScript({
      matches: ['*://*.example.com/*'],
      runAt: 'document_end',
    
      main(ctx) {
        // Content script logic
        console.log('Content script loaded');
    
        // Create UI
        const ui = createShadowRootUi(ctx, {
          name: 'my-extension-ui',
          position: 'inline',
          anchor: 'body',
    
          onMount(container) {
            // Mount React/Vue component
            const root = ReactDOM.createRoot(container);
            root.render(<App />);
          },
        });
    
        ui.mount();
      },
    });
    ```
    
    ### Popup UI
    
    ```typescript
    // entrypoints/popup/main.tsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    
    ReactDOM.createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    ```
    
    ```html
    <!-- entrypoints/popup/index.html -->
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>Extension Popup</title>
    </head>
    <body>
      <div id="root"></div>
      <script type="module" src="./main.tsx"></script>
    </body>
    </html>
    ```
    
    ## Configuration
    
    ### Basic wxt.config.ts
    
    ```typescript
    import { defineConfig } from 'wxt';
    
    export default defineConfig({
      // Framework integration
      modules: ['@wxt-dev/module-react'],
    
      // Manifest configuration
      manifest: {
        name: 'My Extension',
        description: 'Extension description',
        permissions: ['storage', 'activeTab'],
        host_permissions: ['*://example.com/*'],
      },
    
      // Browser target
      browser: 'chrome', // or 'firefox', 'edge', 'safari'
    });
    ```
    
    ## Common Patterns
    
    ### Type-Safe Storage
    
    ```typescript
    // utils/storage.ts
    import { storage } from 'wxt/storage';
    
    export const storageHelper = {
      async get<T>(key: string): Promise<T | null> {
        return await storage.getItem<T>(`local:${key}`);
      },
    
      async set<T>(key: string, value: T): Promise<void> {
        await storage.setItem(`local:${key}`, value);
      },
    
      watch<T>(key: string, callback: (newValue: T | null) => void) {
        return storage.watch<T>(`local:${key}`, callback);
      },
    };
    ```
    
    ### Type-Safe Messaging
    
    ```typescript
    // utils/messaging.ts
    interface Messages {
      'get-data': {
        request: { key: string };
        response: { value: any };
      };
    }
    
    export async function sendMessage<K extends keyof Messages>(
      type: K,
      payload: Messages[K]['request']
    ): Promise<Messages[K]['response']> {
      return await browser.runtime.sendMessage({ type, payload });
    }
    ```
    
    ### Script Injection
    
    ```typescript
    // Inject script into page context
    import { injectScript } from 'wxt/client';
    
    await injectScript('/injected.js', {
      keepInDom: false,
    });
    ```
    
    ## Building & Deployment
    
    ### Production Build
    
    ```bash
    # Build for specific browser
    npm run build -- --browser=chrome
    npm run build -- --browser=firefox
    
    # Create store-ready ZIP
    npm run zip
    npm run zip -- --browser=firefox
    ```
    
    ### Multi-Browser Build
    
    ```bash
    # Build for all browsers
    npm run zip:all
    ```
    
    Output: `.output/my-extension-{version}-{browser}.zip`
    
    ## Modern Stacks (2025)
    
    Popular technology combinations for building Chrome extensions:
    
    ### WXT + React + Tailwind + shadcn/ui
    Most popular stack in 2025. Combines utility-first styling with pre-built accessible components.
    
    ```bash
    npm create wxt@latest -- --template react-ts
    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init -p
    npx shadcn@latest init
    ```
    
    **Best for:** Modern UIs with consistent design system
    **Example:** https://github.com/imtiger/wxt-react-shadcn-tailwindcss-chrome-extension
    
    ### WXT + React + Mantine UI
    Complete component library with 100+ components and built-in dark mode.
    
    ```bash
    npm create wxt@latest -- --template react-ts
    npm install @mantine/core @mantine/hooks
    ```
    
    **Best for:** Feature-rich extensions needing complex components
    **Example:** https://github.com/ongkay/WXT-Mantine-Tailwind-Browser-Extension
    
    ### WXT + React + TypeScript (Minimal)
    Clean setup for custom designs without UI library dependencies.
    
    ```bash
    npm create wxt@latest -- --template react-ts
    ```
    
    **Best for:** Simple extensions or highly custom designs
    
    ## Advanced Topics
    
    For detailed information on advanced topics, see the reference files:
    
    - **React Integration**: See `references/react-integration.md` for complete React setup, hooks, state management, and popular UI libraries
    - **Chrome APIs**: See `references/chrome-api.md` for comprehensive Chrome Extension API reference with examples
    - **Chrome 140+ Features**: See `references/chrome-140-features.md` for latest Chrome Extension APIs (sidePanel.getLayout(), etc.)
    - **WXT API**: See `references/wxt-api.md` for complete WXT framework API documentation
    - **Best Practices**: See `references/best-practices.md` for security, performance, and architecture patterns
    
    ## Troubleshooting
    
    Common issues and solutions:
    
    1. **Module not found errors**: Ensure modules are installed and properly imported
    2. **CSP violations**: Update `content_security_policy` in manifest
    3. **Hot reload not working**: Check browser console for errors
    4. **Storage not persisting**: Use `storage.local` or `storage.sync` correctly
    
    For deeper guidance on avoiding these issues, see [references/best-practices.md](references/best-practices.md).
    
    ## Resources
    
    ### Official Documentation
    - WXT Docs: https://wxt.dev
    - Chrome Extension Docs: https://developer.chrome.com/docs/extensions
    - Firefox Extension Docs: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons
    
    ### Bundled Resources
    
    - **references/**: Detailed documentation for advanced features
    
    Use these resources as needed when building your extension.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related