General
extension-toolchain - Claude MCP Skill
Apply modern browser extension toolchain patterns: WXT (default), Plasmo, CRXJS for Chrome/Firefox/Safari extensions. Use when building browser extensions, choosing extension frameworks, or discussing manifest v3 patterns.
SEO Guide: Enhance your AI agent with the extension-toolchain tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to apply modern browser extension toolchain patterns: wxt (default), plasmo, crxjs for chrome/firefox/s... Download and configure this skill to unlock new capabilities for your AI workflow.
Documentation
SKILL.md# Extension Toolchain
Modern browser extension development with Manifest V3, focusing on framework-agnostic solutions.
## Recommended Stack: WXT (Default)
**Why WXT (2025):**
- Framework-agnostic (React, Vue, Svelte, SolidJS, Vanilla)
- Vite-powered (fast HMR, optimized builds)
- Auto-reload on code changes (content scripts too!)
- TypeScript-first with excellent type generation
- Automated publishing to stores
- Manifest V3 by default
```bash
# Create new extension
npm create wxt@latest
# Choose your framework
? Select a template:
> vanilla
react
vue
svelte
solid
# Start development
cd my-extension
npm run dev # Chrome (default)
npm run dev:firefox # Firefox
npm run dev:edge # Edge
npm run dev:safari # Safari (experimental)
```
### When to Use WXT
ā
Multi-framework teams (framework-agnostic)
ā
Need cross-browser compatibility
ā
Want modern DX (HMR, TypeScript, auto-reload)
ā
Publishing to multiple stores
ā
Complex extensions with multiple entry points
## Alternative: Plasmo
**Best for React developers:**
- Next.js-like file-based routing
- Automatic code splitting
- Built-in remote code bundling
- Very opinionated (React-centric)
```bash
# Create Plasmo extension
npm create plasmo
# Start development
npm run dev
```
### When to Use Plasmo
ā
React-only team
ā
Want Next.js-like DX
ā
Need remote code bundling
ā
Prefer opinionated frameworks
## Alternative: CRXJS (Vite Plugin)
**Minimal, unopinionated:**
- Just a Vite plugin (you control everything)
- Best-in-class HMR (especially for content scripts)
- Lightweight, minimal overhead
- Requires more manual setup
```bash
# Add to existing Vite project
npm install @crxjs/vite-plugin -D
```
### When to Use CRXJS
ā
Want maximum control
ā
Already using Vite
ā
Minimal tooling preference
ā
Expert developer team
## Toolchain Comparison
| | WXT | Plasmo | CRXJS |
|---|---|---|---|
| **Frameworks** | All | React-focused | All |
| **Setup** | Batteries-included | Opinionated | Manual |
| **DX** | Excellent | Excellent | Great |
| **HMR** | Yes | Yes | Best |
| **Auto-publish** | Yes | Yes | No |
| **Learning Curve** | Low | Low | Medium |
| **Flexibility** | High | Medium | Highest |
## Project Structure (WXT)
```
my-extension/
āāā entrypoints/
ā āāā background.ts # Service worker
ā āāā content.ts # Content script
ā āāā popup/ # Extension popup
ā ā āāā index.html
ā ā āāā main.tsx
ā āāā options/ # Options page
ā āāā index.html
ā āāā main.tsx
āāā components/ # Shared UI components
āāā utils/ # Shared utilities
āāā public/ # Static assets
ā āāā icon.png # Extension icon
āāā wxt.config.ts # WXT configuration
āāā package.json
```
## Manifest V3 Essentials
```typescript
// wxt.config.ts
import { defineConfig } from 'wxt'
export default defineConfig({
manifest: {
name: 'My Extension',
version: '1.0.0',
permissions: ['storage', 'tabs'],
host_permissions: ['https://*.example.com/*'],
action: {
default_title: 'My Extension',
},
},
})
```
### Key Manifest V3 Changes
- **Service Workers** replace background pages (no DOM access)
- **`host_permissions`** separate from `permissions`
- **`scripting` API** for dynamic content script injection
- **No remotely hosted code** (bundle everything)
- **Limited `executeScript`** capabilities
## Communication Patterns
### Popup ā Background
```typescript
// popup/main.tsx
import browser from 'webextension-polyfill'
const response = await browser.runtime.sendMessage({
type: 'GET_DATA',
payload: { key: 'value' },
})
// background.ts
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_DATA') {
// Process and respond
sendResponse({ data: 'result' })
}
return true // Keep channel open for async response
})
```
### Content Script ā Background
```typescript
// content.ts
import browser from 'webextension-polyfill'
// Send message to background
const result = await browser.runtime.sendMessage({
type: 'ANALYZE_PAGE',
url: window.location.href,
})
// background.ts
browser.runtime.onMessage.addListener(async (message) => {
if (message.type === 'ANALYZE_PAGE') {
const analysis = await analyzePage(message.url)
return { analysis }
}
})
```
### Content Script ā Page (Web Page)
```typescript
// content.ts - inject into page context
const script = document.createElement('script')
script.src = browser.runtime.getURL('injected.js')
document.head.appendChild(script)
// Listen for messages from page
window.addEventListener('message', (event) => {
if (event.source !== window) return
if (event.data.type === 'FROM_PAGE') {
// Handle message from page
}
})
// injected.js (runs in page context, has access to page's window/DOM)
window.postMessage({ type: 'FROM_PAGE', data: 'value' }, '*')
```
## Storage Patterns
```typescript
// Using chrome.storage.sync (syncs across devices)
import browser from 'webextension-polyfill'
// Save
await browser.storage.sync.set({ key: 'value' })
// Load
const { key } = await browser.storage.sync.get('key')
// Listen for changes
browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'sync' && changes.key) {
console.log('Value changed:', changes.key.newValue)
}
})
```
## Essential Libraries
```bash
# Cross-browser compatibility
npm install webextension-polyfill
# State Management
npm install zustand
# Forms
npm install react-hook-form zod
# UI Components (if using React)
npm install @radix-ui/react-* # Headless components
```
## Testing Strategy
```bash
# Install testing libraries
npm install --save-dev vitest @testing-library/react @testing-library/user-event
npm install --save-dev @wxt-dev/testing
```
**Example test:**
```typescript
// popup/main.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Popup from './main'
describe('Popup', () => {
it('renders heading', () => {
render(<Popup />)
expect(screen.getByRole('heading')).toBeInTheDocument()
})
})
```
## Quality Gates Integration
```yaml
# .github/workflows/extension-ci.yml
name: Extension CI
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: extension-build
path: .output/
```
## Publishing Automation
```bash
# Build for all browsers
npm run build # Chrome
npm run build:firefox # Firefox
npm run build:safari # Safari
# Zip for submission
npm run zip # All stores
# Or use WXT's publish command (requires API keys)
wxt publish --chrome --firefox
```
**Store submission setup:**
```typescript
// wxt.config.ts
export default defineConfig({
zip: {
artifactTemplate: '{{name}}-{{version}}-{{browser}}.zip',
},
manifest: {
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
},
})
```
## Performance Best Practices
- **Lazy load content scripts**: Only inject when needed
- **Use storage efficiently**: Minimize sync storage writes
- **Debounce frequent operations**: Especially in content scripts
- **Minimize background script work**: Use alarms/events, not intervals
- **Optimize bundle size**: Code splitting, tree shaking
## Security Considerations
```typescript
// Content Security Policy
manifest: {
content_security_policy: {
extension_pages: "script-src 'self'; object-src 'self'"
}
}
// Validate messages
browser.runtime.onMessage.addListener((message) => {
// Always validate message structure
if (typeof message !== 'object' || !message.type) {
return
}
// Type guard
if (message.type === 'EXPECTED_TYPE') {
// Process
}
})
// Never inject user content directly into DOM
// Use textContent, not innerHTML
element.textContent = userInput // Safe
element.innerHTML = userInput // XSS vulnerability!
```
## Common Gotchas
**Service Worker Lifecycle:**
- Service workers can be terminated anytime
- Use `chrome.storage` for persistence, not in-memory state
- Set up event listeners at top level (not inside async functions)
**Content Script Isolation:**
- Content scripts run in isolated world
- No direct access to page's JavaScript
- Must use `postMessage` to communicate with page
**Manifest V3 Restrictions:**
- No `eval()` or `new Function()`
- No inline scripts in HTML
- All code must be bundled
- Limited service worker APIs
## Recommendation Flow
```
New browser extension:
āā Multi-framework team ā WXT ā
āā React-only team ā Plasmo
āā Want maximum control ā CRXJS
Existing extension (Manifest V2):
āā Migrate to WXT (handles V2āV3 migration)
```
When agents design browser extensions, they should:
- Default to WXT for new projects (framework-agnostic, best DX)
- Use Manifest V3 (V2 deprecated in 2024)
- Apply quality-gates skill for testing/CI setup
- Use webextension-polyfill for cross-browser compatibility
- Follow Content Security Policy strictly
- Plan for service worker lifecycle (no persistent background page)
- Use chrome.storage for state persistence
- Validate all messages between componentsSignals
Information
- Repository
- phrazzld/claude-config
- Author
- phrazzld
- Last Sync
- 3/2/2026
- Repo Updated
- 3/1/2026
- Created
- 1/13/2026
Reviews (0)
No reviews yet. Be the first to review this skill!
Related Skills
CLAUDE
Claude-Mem: AI Development Instructions
cursorrules
CrewAI Development Rules
firecrawl-build-onboarding
Get Firecrawl credentials and SDK setup into a project. Use when an application needs `FIRECRAWL_API_KEY`, when an agent should add Firecrawl to `.env`, when the user wants to authenticate Firecrawl for app code, or when choosing the first SDK and docs for a new Firecrawl integration. This skill includes its own browser auth flow, so it does not depend on the website onboarding skill.
firecrawl-build
Integrate Firecrawl into application code whenever a product, agent, or workflow needs web data inside the app ā web search, live search results, page scraping, structured extraction, or browser interaction. Use when building any feature that needs data from the web in code, even if the user does not mention Firecrawl explicitly and only describes wanting web data, website content, search, scraping, or interaction in an application. Trigger for Firecrawl requests, "fire girl" shorthand, and generic app-level web-data needs that should map to `/scrape`, `/search`, or `/interact`. Do not use this skill for one-off terminal-only web tasks during the current session; use `firecrawl/cli` for those.
Related Guides
Bear Notes Claude Skill: Your AI-Powered Note-Taking Assistant
Learn how to use the bear-notes Claude skill. Complete guide with installation instructions and examples.
Mastering tmux with Claude: A Complete Guide to the tmux Claude Skill
Learn how to use the tmux Claude skill. Complete guide with installation instructions and examples.
OpenAI Whisper API Claude Skill: Complete Guide to AI-Powered Audio Transcription
Learn how to use the openai-whisper-api Claude skill. Complete guide with installation instructions and examples.