| name | browser-extension-builder |
| description | Scaffolds Manifest V3 Chrome, Firefox, and cross-browser extensions: content scripts, service workers, popups, chrome.storage, monetization, and Web Store/AMO publishing. Trigger on browser extension, chrome extension, firefox addon, MV3, or extension popup work. Do not use for Electron apps, PWAs, or ordinary websites unless the deliverable is an unpacked or store extension. |
| version | 1.0.1 |
| risk | unknown |
| source | vibeship-spawner-skills (Apache 2.0) |
| date_added | 2026-02-27T00:00:00.000Z |
Browser Extension Builder
Expert in building browser extensions that solve real problems — Chrome, Firefox, and cross-browser. Covers extension architecture, Manifest V3, content scripts, popup UIs, monetization strategies, and Chrome Web Store publishing.
Role: Browser Extension Architect
You extend the browser to give users superpowers. You understand the unique constraints of extension development — permissions, security, store policies. You build extensions that people install and actually use daily. You know the difference between a toy and a tool.
When to Use
Trigger this skill when the user mentions or implies any of:
- "browser extension"
- "chrome extension"
- "firefox addon"
- "manifest v3" / "MV3"
- "content script"
- "service worker" in an extension context
- "chrome web store" / "addons.mozilla.org"
- Extension monetization, popup UI, or extension publishing
Do not use this skill for general web apps, Electron apps, or PWA-only tasks unless the user explicitly wants a browser extension wrapper.
Prerequisites
- Node.js 18+ and npm (for bundling if using a framework)
- A Chromium-based browser (Chrome, Edge, Brave) for local testing
- Firefox Developer Edition or Nightly for cross-browser testing (optional)
- A text editor or Cursor
- For publishing: a Chrome Web Store developer account ($5 one-time fee) and/or an AMO (addons.mozilla.org) account
- Icons in 16×16, 48×48, and 128×128 PNG (required for store listing)
Procedure
1. Scaffold the Extension Project
Create the standard MV3 directory structure:
mkdir my-extension; cd my-extension
New-Item -ItemType Directory -Force popup, content, background, options, icons
New-Item popup\popup.html, popup\popup.css, popup\popup.js
New-Item content\content.js
New-Item background\service-worker.js
New-Item options\options.html, options\options.js
New-Item manifest.json
Resulting structure:
extension/
├── manifest.json
├── popup/
│ ├── popup.html
│ ├── popup.css
│ └── popup.js
├── content/
│ └── content.js
├── background/
│ └── service-worker.js
├── options/
│ ├── options.html
│ └── options.js
└── icons/
├── icon16.png
├── icon48.png
└── icon128.png
2. Write the Manifest V3
manifest.json:
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"description": "What it does",
"permissions": ["storage", "activeTab"],
"action": {
"default_popup": "popup/popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"
Hard rule: Chrome requires Manifest V3 for all new extensions. Do not use "manifest_version": 2 — it will be rejected by the Web Store.
3. Implement the Communication Pattern
Popup ←→ Background (Service Worker) ←→ Content Script
↓
chrome.storage
- Popup sends messages to the background service worker and reads
chrome.storage.
- Background service worker coordinates logic, handles events, and relays messages to content scripts.
- Content script runs on matched pages, reads/modifies the DOM, and responds to messages.
4. Write the Content Script
content/content.js:
document.addEventListener('DOMContentLoaded', () => {
const element = document.querySelector('.target');
if (element) {
element.style.backgroundColor = 'yellow';
}
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'getData') {
const data = document.querySelector('.data')?.textContent;
sendResponse({ data });
}
return true;
});
To inject a floating UI on the page:
function injectUI() {
const container = document.createElement('div');
container.id = 'my-extension-ui';
container.innerHTML = `
<div style="position: fixed; bottom: 20px; right: 20px;
background: white; padding: 16px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000;">
<h3>My Extension</h3>
<button id="my-extension-btn">Click me</button>
</div>
`;
document.body.appendChild(container);
document.getElementById('my-extension-btn').addEventListener('click', () => {
});
}
injectUI();
Scope content scripts to specific sites when possible:
{
"content_scripts": [
{
"matches": ["https://specific-site.com/*"],
"js": ["content.js"],
"run_at": "document_end"
}
]
}
5. Implement Storage and State
chrome.storage is the primary persistence layer.
chrome.storage.local.set({ key: 'value' }, () => {
console.log('Saved');
});
chrome.storage.local.get(['key'], (result) => {
console.log(result.key);
});
chrome.storage.sync.set({ setting: true });
chrome.storage.onChanged.addListener((changes, area) => {
if (changes.key) {
console.log('key changed:', changes.key.newValue);
}
});
Storage limits:
| Type | Limit |
|---|
| local | 5 MB |
| sync | 100 KB total, 8 KB per item |
Async/await wrapper:
async function getStorage(keys) {
return new Promise((resolve) => {
chrome.storage.local.get(keys, resolve);
});
}
async function setStorage(data) {
return new Promise((resolve) => {
chrome.storage.local.set(data, resolve);
});
}
const { settings } = await getStorage(['settings']);
await setStorage({ settings: { ...settings, theme: 'dark' } });
6. Load the Extension for Local Testing (Chrome)
- Open
chrome://extensions/
- Enable Developer mode (top-right toggle)
- Click Load unpacked
- Select the
my-extension folder
- The extension appears in the toolbar; click the puzzle piece icon to pin it
To reload after code changes: click the reload arrow on the extension card in chrome://extensions/.
7. Load for Local Testing (Firefox)
- Open
about:debugging#/runtime/this-firefox
- Click Load Temporary Add-on
- Select
manifest.json in the project folder
- Temporary add-ons are removed when Firefox closes
8. Monetization (Optional)
Revenue models:
| Model | How It Works |
|---|
| Freemium | Free basic, paid features |
| One-time | Pay once, use forever |
| Subscription | Monthly/yearly access |
| Donations | Tip jar / Buy me a coffee |
| Affiliate | Recommend products |
Chrome discontinued built-in payments. Use your own backend and redirect to an external checkout page:
chrome.tabs.create({
url: `https://your-site.com/upgrade?user=${userId}`
});
async function checkPremium() {
const { userId } = await getStorage(['userId']);
const response = await fetch(`https://your-api.com/premium/${userId}`);
const { isPremium } = await response.json();
await setStorage({ isPremium });
return isPremium;
}
Feature gating:
async function usePremiumFeature() {
const { isPremium } = await getStorage(['isPremium']);
if (!isPremium) {
showUpgradeModal();
return;
}
}
9. Publish to the Chrome Web Store
- Zip the extension folder contents (not the parent folder):
Compress-Archive -Path .\* -DestinationPath my-extension.zip
- Go to the Chrome Web Store Developer Dashboard.
- Pay the one-time $5 developer fee if not already paid.
- Click New Item, upload
my-extension.zip.
- Fill in the listing: name, description, screenshots, category, privacy practices.
- Submit for review. Review typically takes 1–3 business days.
10. Publish to Firefox Add-ons (AMO)
- Zip the extension (same as above).
- Go to addons.mozilla.org/developers.
- Submit a new add-on, upload the zip.
- AMO runs automated and manual review. Firefox requires
browser_specific_settings in the manifest for signing.
Pitfalls
Using Deprecated Manifest V2 — HIGH
Problem: Chrome requires V3 for new extensions. MV2 submissions are rejected.
Fix: Migrate to Manifest V3. Replace background pages with service workers. Use "manifest_version": 3.
Excessive Permissions Requested — HIGH
Problem: Broad permissions (<all_urls>, *://*/*) trigger store rejection or scary warnings.
Fix: Use specific host_permissions and optional_permissions. Request permissions at runtime with chrome.permissions.request() when possible.
No Error Handling in Extension — MEDIUM
Problem: Not checking chrome.runtime.lastError causes silent failures.
Fix: Always check chrome.runtime.lastError after API calls:
chrome.storage.local.get(['key'], (result) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
return;
}
});
Hardcoded URLs in Extension — MEDIUM
Problem: Hardcoded URLs make it hard to update endpoints without re-publishing.
Fix: Store configuration in chrome.storage or fetch from a remote config endpoint.
Missing Extension Icons — LOW
Problem: Missing icons affect store listing and toolbar display.
Fix: Add icons in 16, 48, and 128 pixel sizes. Reference them in action.default_icon.
Service Worker Lifecycle
Problem: Service workers are terminated when idle and restarted on events. Do not rely on global state persisting.
Fix: Persist all state in chrome.storage. Use chrome.alarms for scheduled tasks, not setInterval.
Content Script Isolation
Problem: Content scripts run in an isolated world and cannot directly access page JavaScript variables or functions.
Fix: To interact with page context, inject a <script> tag or use chrome.scripting.executeScript with world: 'MAIN'.
Cross-Origin Restrictions
Problem: Fetching from arbitrary origins fails without host permissions.
Fix: Add the target origin to host_permissions in the manifest.
Verification
Manifest Validity
Check the manifest parses correctly:
Get-Content manifest.json | ConvertFrom-Json | Select-Object manifest_version, name, version
Expected output:
manifest_version name version
--------------- ---- -------
3 My Extension 1.0.0
Extension Loads in Chrome
- Navigate to
chrome://extensions/
- Confirm the extension card appears with no error badge
- If errors exist, click Errors to view console output
Content Script Runs
- Open a matched page (e.g.,
https://specific-site.com/)
- Open DevTools (F12) → Console
- Confirm content script logs or DOM modifications appear
- Verify the injected UI element exists:
document.getElementById('my-extension-ui')
Storage Works
- Open the popup
- Trigger a storage write
- In
chrome://extensions, click Service worker to open the background console
- Run:
chrome.storage.local.get(null, console.log)
- Confirm the saved key-value pair is present
Message Passing
- From the popup console, send a message:
chrome.runtime.sendMessage({ action: 'getData' }, console.log)
- Confirm the content script responds with the expected data
Store Readiness Checklist
Collaboration
Delegation Triggers
react|vue|svelte → frontend (Extension popup framework)
monetization|payment|subscription → micro-saas-launcher (Extension business model)
personal tool|just for me → personal-tool-builder (Personal extension)
AI|LLM|GPT → ai-wrapper-product (AI-powered extension)
Productivity Extension
Skills: browser-extension-builder, frontend, micro-saas-launcher
Workflow:
- Define extension functionality
- Build popup UI with React
- Implement content scripts
- Add premium features
- Publish to Chrome Web Store
- Market and iterate
AI Browser Assistant
Skills: browser-extension-builder, ai-wrapper-product, frontend
Workflow:
- Design AI features for browser
- Build extension architecture
- Integrate AI API
- Create popup interface
- Handle usage limits/payments
- Publish and grow
Related Skills
Works well with: frontend, micro-saas-launcher, personal-tool-builder, ai-wrapper-product
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.