Best use case
Plasmo — Browser Extension Framework is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## Overview
Teams using Plasmo — Browser Extension Framework should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/plasmo/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Plasmo — Browser Extension Framework Compares
| Feature / Agent | Plasmo — Browser Extension Framework | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## Overview
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Plasmo — Browser Extension Framework
## Overview
Plasmo, the framework for building browser extensions with React, TypeScript, and modern tooling. Helps developers create Chrome and Firefox extensions with content scripts, background workers, popup UIs, and messaging — all with hot reload and zero webpack config.
## Instructions
### Project Setup
Create a new extension with Plasmo CLI:
```bash
# Create a new extension project
pnpm create plasmo my-extension
# Or with a specific template
pnpm create plasmo --with-tailwindcss my-extension
# Development with hot reload
pnpm dev
# Build for production
pnpm build
# Package for Chrome Web Store
pnpm package
```
### Popup UI
Build the extension popup as a React component:
```tsx
// src/popup.tsx — Main popup UI (click the extension icon)
import { useState, useEffect } from "react";
import { Storage } from "@plasmohq/storage";
const storage = new Storage();
function IndexPopup() {
const [savedCount, setSavedCount] = useState(0);
const [isEnabled, setIsEnabled] = useState(true);
useEffect(() => {
// Load state from extension storage (persists across sessions)
storage.get<number>("savedCount").then((count) => setSavedCount(count ?? 0));
storage.get<boolean>("isEnabled").then((enabled) => setIsEnabled(enabled ?? true));
}, []);
const toggleExtension = async () => {
const newState = !isEnabled;
setIsEnabled(newState);
await storage.set("isEnabled", newState);
// Notify content scripts about the state change
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
if (tabs[0]?.id) {
chrome.tabs.sendMessage(tabs[0].id, { type: "TOGGLE", enabled: newState });
}
};
return (
<div style={{ padding: 16, width: 320 }}>
<h2>My Extension</h2>
<p>Saved items: {savedCount}</p>
<button onClick={toggleExtension}>
{isEnabled ? "🟢 Enabled" : "🔴 Disabled"}
</button>
</div>
);
}
export default IndexPopup;
```
### Content Scripts
Inject UI and logic into web pages:
```tsx
// src/contents/overlay.tsx — Content script that renders a React component on any page
import type { PlasmoCSConfig, PlasmoGetOverlayAnchor } from "plasmo";
import { useState } from "react";
// Configure which pages this content script runs on
export const config: PlasmoCSConfig = {
matches: ["https://github.com/*"], // Only on GitHub
css: ["contents/overlay.css"], // Optional custom CSS
};
// Anchor the overlay to a specific DOM element (optional)
export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => {
return document.querySelector(".repository-content");
};
// The React component renders as an overlay on the page
function GitHubOverlay() {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="plasmo-overlay">
<button onClick={() => setIsOpen(!isOpen)}>
📋 Quick Actions
</button>
{isOpen && (
<div className="plasmo-panel">
<button onClick={() => copyRepoInfo()}>Copy repo info</button>
<button onClick={() => analyzeReadme()}>Analyze README</button>
</div>
)}
</div>
);
}
async function copyRepoInfo() {
const title = document.querySelector("[itemprop='name'] a")?.textContent?.trim();
const desc = document.querySelector("[itemprop='about']")?.textContent?.trim();
const stars = document.querySelector("#repo-stars-counter-star")?.textContent?.trim();
await navigator.clipboard.writeText(`${title}: ${desc} (⭐ ${stars})`);
}
export default GitHubOverlay;
```
### Background Service Worker
Handle long-running tasks, alarms, and cross-tab communication:
```typescript
// src/background.ts — Background service worker (Manifest V3)
import { Storage } from "@plasmohq/storage";
const storage = new Storage();
// Listen for messages from content scripts and popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.type) {
case "SAVE_ITEM":
handleSaveItem(message.data).then(sendResponse);
return true; // Keep the message channel open for async response
case "GET_STATS":
getStats().then(sendResponse);
return true;
}
});
async function handleSaveItem(data: { url: string; title: string; content: string }) {
const items = (await storage.get<any[]>("savedItems")) ?? [];
items.push({ ...data, savedAt: Date.now() });
await storage.set("savedItems", items);
// Update badge count
const count = items.length;
chrome.action.setBadgeText({ text: count > 0 ? String(count) : "" });
chrome.action.setBadgeBackgroundColor({ color: "#6366f1" });
return { success: true, count };
}
// Set up periodic tasks with alarms
chrome.alarms.create("sync-data", { periodInMinutes: 30 });
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === "sync-data") {
const items = (await storage.get<any[]>("savedItems")) ?? [];
if (items.length > 0) {
// Sync to your backend API
await fetch("https://api.example.com/sync", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
});
}
}
});
// Context menu (right-click menu)
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "save-selection",
title: "Save selection to My Extension",
contexts: ["selection"],
});
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "save-selection" && info.selectionText) {
handleSaveItem({
url: tab?.url ?? "",
title: tab?.title ?? "",
content: info.selectionText,
});
}
});
```
### Messaging Between Components
Type-safe communication between popup, content scripts, and background:
```typescript
// src/messaging.ts — Type-safe messaging with Plasmo messaging API
import { sendToBackground, sendToContentScript } from "@plasmohq/messaging";
// Define message types
interface SaveRequest { url: string; title: string; }
interface SaveResponse { success: boolean; id: string; }
// From content script → background
async function saveFromContentScript(data: SaveRequest): Promise<SaveResponse> {
return sendToBackground({
name: "save-item", // Maps to src/background/messages/save-item.ts
body: data,
});
}
// From popup → content script
async function highlightElements() {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
return sendToContentScript({
name: "highlight",
tabId: tabs[0].id!,
body: { selector: "h1, h2, h3" },
});
}
```
```typescript
// src/background/messages/save-item.ts — Background message handler
import type { PlasmoMessaging } from "@plasmohq/messaging";
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
const { url, title } = req.body;
// Process the save request
const id = crypto.randomUUID();
await chrome.storage.local.set({ [id]: { url, title, savedAt: Date.now() } });
res.send({ success: true, id });
};
export default handler;
```
### Storage with React Hooks
Reactive storage that syncs across extension components:
```tsx
// src/popup.tsx — Using storage hooks for reactive state
import { useStorage } from "@plasmohq/storage/hook";
function SettingsPopup() {
// useStorage provides React state that persists and syncs
// Changes in popup instantly reflect in content scripts and vice versa
const [theme, setTheme] = useStorage<string>("theme", "light");
const [apiKey, setApiKey] = useStorage<string>("apiKey", "");
const [savedItems, setSavedItems] = useStorage<any[]>("savedItems", []);
return (
<div style={{ padding: 16, width: 350 }}>
<h3>Settings</h3>
<label>Theme</label>
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<label>API Key</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="Enter your API key"
/>
<h3>Saved Items ({savedItems.length})</h3>
<ul>
{savedItems.slice(-5).map((item, i) => (
<li key={i}>{item.title}</li>
))}
</ul>
</div>
);
}
```
### Options Page
Full-page settings interface:
```tsx
// src/options.tsx — Extension options page (chrome-extension://id/options.html)
function OptionsPage() {
const [settings, setSettings] = useStorage("settings", {
autoSave: true,
notifications: true,
syncInterval: 30,
excludedSites: [] as string[],
});
const updateSetting = (key: string, value: any) => {
setSettings({ ...settings, [key]: value });
};
return (
<div style={{ maxWidth: 600, margin: "40px auto", padding: 20 }}>
<h1>Extension Settings</h1>
<div>
<label>
<input type="checkbox" checked={settings.autoSave}
onChange={(e) => updateSetting("autoSave", e.target.checked)} />
Auto-save highlighted text
</label>
</div>
<div>
<label>Sync interval (minutes)</label>
<input type="number" value={settings.syncInterval}
onChange={(e) => updateSetting("syncInterval", parseInt(e.target.value))} />
</div>
</div>
);
}
export default OptionsPage;
```
## Installation
```bash
pnpm create plasmo my-extension
cd my-extension
pnpm dev # Start dev server with hot reload
```
## Examples
### Example 1: Setting up Plasmo with a custom configuration
**User request:**
```
I just installed Plasmo. Help me configure it for my TypeScript + React workflow with my preferred keybindings.
```
The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.
### Example 2: Extending Plasmo with custom functionality
**User request:**
```
I want to add a custom popup ui to Plasmo. How do I build one?
```
The agent scaffolds the extension/plugin project, implements the core functionality following Plasmo's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.
## Guidelines
1. **Manifest V3 by default** — Plasmo generates MV3 manifests; service workers replace persistent background pages
2. **Use Plasmo storage over chrome.storage** — `@plasmohq/storage` provides React hooks and cross-component sync
3. **Content script isolation** — Plasmo uses Shadow DOM for content script UIs; your CSS won't leak into the page
4. **Minimize permissions** — Request only the permissions you need in `package.json` under `manifest.permissions`
5. **Hot reload in dev** — `pnpm dev` auto-reloads the extension on file changes; no manual refresh needed
6. **Type your messages** — Use Plasmo's messaging API for type-safe communication between components
7. **Test across browsers** — Build for both Chrome (`pnpm build --target=chrome-mv3`) and Firefox (`--target=firefox-mv3`)
8. **Bundle size matters** — Extensions load on every page; keep content scripts small, lazy-load heavy logicRelated Skills
cursor-extension-integration
Integrate VS Code extensions with Cursor IDE: compatibility, Open VSX registry, VSIX installation, conflict resolution, and essential extensions. Triggers on "cursor extensions", "cursor vscode extensions", "cursor plugins", "cursor marketplace", "open vsx", "vsix install".
conducting-browser-compatibility-tests
This skill enables cross-browser compatibility testing for web applications using BrowserStack, Selenium Grid, or Playwright. It tests across Chrome, Firefox, Safari, and Edge, identifying browser-specific bugs and ensuring consistent functionality. It is used when a user requests to "test browser compatibility", "run cross-browser tests", or uses the `/browser-test` or `/bt` command to assess web application behavior across different browsers and devices. The skill generates a report detailing compatibility issues and screenshots for visual verification. Activates when you request "conducting browser compatibility tests" functionality.
defold-native-extension-editing
Defold native extension development. Use when creating or editing C/C++ (.c, .cpp, .h, .hpp), JavaScript (.js), or manifest files in native extension directories (src/, include/, lib/, api/).
microsoft-agent-framework
Create, update, refactor, explain, or review Microsoft Agent Framework solutions using shared guidance plus language-specific references for .NET and Python.
containerize-aspnet-framework
Containerize an ASP.NET .NET Framework project by creating Dockerfile and .dockerfile files customized for the project.
../../../engineering-team/playwright-pro/skills/browserstack/SKILL.md
No description provided.
browser-extension-developer
Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.
use-my-browser
Use when the user wants browser automation, page inspection, or web research and you need to choose between public-web tools, the live browser session, or a separate browser context, especially for signed-in, dynamic, social, or DevTools-driven pages.
steel-browser
Use this skill by default for browser or web tasks that can run in the cloud: site navigation, scraping, structured extraction, screenshots/PDFs, form flows, and anti-bot-sensitive automation. Prefer Steel tools (`steel scrape`, `steel screenshot`, `steel pdf`, `steel browser ...`) over generic fetch/search approaches when reliability matters. Trigger even if the user does not mention Steel. Skip only when the task must run against local-only apps (for example localhost QA) or private network targets unavailable from Steel cloud sessions.
startup-metrics-framework
This skill should be used when the user asks about "key startup metrics", "SaaS metrics", "CAC and LTV", "unit economics", "burn multiple", "rule of 40", "marketplace metrics", or requests guidance on tracking and optimizing business performance metrics.
microsoft-azure-webjobs-extensions-authentication-events-dotnet
Microsoft Entra Authentication Events SDK for .NET. Azure Functions triggers for custom authentication extensions. Use for token enrichment, custom claims, attribute collection, and OTP customization in Entra ID. Triggers: "Authentication Events", "WebJobsAuthenticationEventsTrigger", "OnTokenIssuanceStart", "OnAttributeCollectionStart", "custom claims", "token enrichment", "Entra custom extension", "authentication extension".
framework-migration-legacy-modernize
Orchestrate a comprehensive legacy system modernization using the strangler fig pattern, enabling gradual replacement of outdated components while maintaining continuous business operations through ex