Wxt Browser Extensions
vantezzen/skip-silence
WXT browser extension performance optimization guidelines. An agent skill from vantezzen/skip-silence.
Build and publish Chrome Extensions using Manifest V3 best practices.
$ npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install GoogleChrome/modern-web-guidance-src chrome-extensions --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/GoogleChrome/modern-web-guidance-src.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills-src/chrome-extensions .claude/skills/chrome-extensions && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "chrome-extensions" agent skill from https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensions into .claude/skills/chrome-extensions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "chrome-extensions", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensionsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install GoogleChrome/modern-web-guidance-src chrome-extensions --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/GoogleChrome/modern-web-guidance-src.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills-src/chrome-extensions .agents/skills/chrome-extensions && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "chrome-extensions" agent skill from https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensions into .agents/skills/chrome-extensions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "chrome-extensions", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install GoogleChrome/modern-web-guidance-src chrome-extensions --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/GoogleChrome/modern-web-guidance-src.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills-src/chrome-extensions .cursor/skills/chrome-extensions && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "chrome-extensions" agent skill from https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensions into .cursor/skills/chrome-extensions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "chrome-extensions", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/GoogleChrome/modern-web-guidance-src.git --path skills-src/chrome-extensions--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install GoogleChrome/modern-web-guidance-src chrome-extensions --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/GoogleChrome/modern-web-guidance-src.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills-src/chrome-extensions .gemini/skills/chrome-extensions && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "chrome-extensions" agent skill from https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensions into .gemini/skills/chrome-extensions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "chrome-extensions", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install GoogleChrome/modern-web-guidance-src chrome-extensionsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/GoogleChrome/modern-web-guidance-src.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills-src/chrome-extensions .github/skills/chrome-extensions && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "chrome-extensions" agent skill from https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensions into .github/skills/chrome-extensions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "chrome-extensions", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install GoogleChrome/modern-web-guidance-src chrome-extensions --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/GoogleChrome/modern-web-guidance-src.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills-src/chrome-extensions .opencode/skills/chrome-extensions && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "chrome-extensions" agent skill from https://github.com/GoogleChrome/modern-web-guidance-src/tree/main/skills-src/chrome-extensions into .opencode/skills/chrome-extensions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "chrome-extensions", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
chrome-extensionsBuild and publish Chrome Extensions using Manifest V3 best practices.
Chrome Extensions is an agent skill from GoogleChrome/modern-web-guidance-src. Build and publish Chrome Extensions using Manifest V3 best practices. Use this skill whenever the user asks to create, modify, debug, or understand Chrome browser extensions, add-ons, or anything involving the Chrome Extensions API. Trigger on mentions of: 'Chrome extension', 'browser extension', 'manifest.json', 'content script', 'service worker' (in browser context), 'popup' (in browser extension context), 'side panel', 'chrome. API', 'declarativeNetRequest', 'omnibox', 'context menu' (in extension context)…
Its SKILL.md is about 6.6k tokens, which your agent loads only when the skill is triggered. The skill folder holds 26 other files, including reference files (for example `references/extensions/api-calling.md`, `references/extensions/auth-identity.md` and `references/extensions/content-scripts.md`).
It sits in Development, covering Browser extensions. It works with Chrome Extensions. The licence is Apache-2.0.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit c312847. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
justFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Chrome Extensions loads about 6.6k tokens when it runs, and up to ~31k if it reads all its reference files. Until then it costs about 219 tokens; SKILL.md has 2,118 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
must exclude `.git/`, `node_modules/`, `.env`, `CHROMEWEBSTORE.md`Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from GoogleChrome/modern-web-guidance-src at commit c312847, republished under its Apache-2.0 licence (© GoogleChrome). 2,118 words, ~6,569 tokens.
.claude/skills/chrome-extensions/SKILL.md (or your agent's skills folder). This skill also uses 24 other files; get the full folder from GitHub.Build production-quality Chrome extensions using Manifest V3 and publish them to the Chrome Web Store.
These address the most common causes of broken extensions. Violating any produces a non-functional build.
❌ BROKEN — referencing files that don't exist or reusing one file for all sizes:
"icons": { "16": "icon.png", "48": "icon.png", "128": "icon.png" }
✅ CORRECT — each size is a separate file at the correct pixel dimensions:
"icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" }
(where icon-16.png is 16×16px, icon-48.png is 48×48px, icon-128.png is 128×128px)
✅ ALSO CORRECT — omit icons from manifest if you cannot generate real PNG files:
(just remove the "icons" and "default_icon" fields — Chrome uses a default icon)If you include icon references, you MUST create the actual image files. Generate them with a script (see references/extensions/icons.md) or leave them out. Never reference non-existent files.
Defining "side_panel": {"default_path": "..."} does NOT make it openable. Add a trigger:
// In service-worker.js — open side panel on extension icon click
// IMPORTANT: chrome.action.onClicked ONLY fires when there is NO default_popup
chrome.action.onClicked.addListener(async (tab) => {
await chrome.sidePanel.open({ windowId: tab.windowId });
});If the extension has both a popup AND side panel, add a button in the popup that calls chrome.sidePanel.open(). Alternatively, use chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }) — but the property is openPanelOnActionClick, NOT openPanelOnActionIconClick; the "Icon" variant causes a synchronous TypeError that silently aborts the service worker. Do NOT also define default_popup when using setPanelBehavior. See references/extensions/side-panel.md.
Extension CSP blocks eval(), new Function(), inline <script> in all extension pages.
// ❌ BROKEN — direct iframe DOM access throws SecurityError
iframe.contentDocument.write(html);
// ❌ BROKEN — eval in extension page
eval(userCode); // CSP blocks this
// ✅ OPTION A: Sandbox in manifest + postMessage
// manifest.json: { "sandbox": { "pages": ["sandbox.html"] } }
iframe.contentWindow.postMessage({ html, css, js }, '*');
// sandbox.html receives and runs:
window.addEventListener('message', (e) => { eval(e.data.js); /* allowed in sandbox */ });
// ✅ OPTION B: Blob URL (creates separate origin, bypasses extension CSP)
iframe.src = URL.createObjectURL(new Blob([doc], { type: 'text/html' }));
// ✅ OPTION C: srcdoc
iframe.srcdoc = `<style>${css}</style>${html}<script>${js}<\/script>`;See references/extensions/csp-sandbox.md for full details.
tab.url requires the tabs permissionWithout it, tab.url silently returns undefined — no error thrown. See
references/extensions/permissions.md.
.then() chains// ❌ BAD
chrome.tabs.query({active: true, currentWindow: true}).then(tabs => {
chrome.scripting.executeScript({target: {tabId: tabs[0].id}, files: ['content.js']}).then(() => {});
});
// ✅ GOOD
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] });For runtime.onMessage listeners that do async work:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
(async () => {
const data = await chrome.storage.local.get('key');
sendResponse({ data });
})();
return true; // keeps channel open
});When modifying many DOM elements, batch with requestAnimationFrame and yield between batches:
async function highlightAll(elements) {
const BATCH = 20;
for (let i = 0; i < elements.length; i += BATCH) {
await new Promise(r => requestAnimationFrame(() => {
elements.slice(i, i + BATCH).forEach(el => el.style.backgroundColor = 'yellow');
r();
}));
if (globalThis.scheduler?.yield) await scheduler.yield();
}
}See references/extensions/content-scripts.md.
// ❌ BROKEN — state lost when SW terminates (~30s of inactivity)
let count = 0;
chrome.tabs.onUpdated.addListener(() => { count++; });
// ✅ CORRECT — persist in chrome.storage, read on every event
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
if (changeInfo.status !== 'complete') return;
const { count = 0 } = await chrome.storage.local.get('count');
await chrome.storage.local.set({ count: count + 1 });
await chrome.action.setBadgeText({ text: String(count + 1) });
});Use chrome.alarms instead of setTimeout/setInterval. See references/extensions/service-worker.md.
When using Google sign-in, the OAuth client_id is tied to a specific extension ID. The ID changes between unpacked development and the Chrome Web Store.
To stabilize the ID during development, add a "key" field to manifest.json:
"key": "MIIBIjANBgkqh..." to manifest.jsonAlways document: "After publishing to the Chrome Web Store, update the OAuth client with the store-assigned extension ID." See references/extensions/auth-identity.md.
When a context menu item performs an action (save, copy, etc.), confirm it to the user. Use a notification, badge flash, or injected toast — don't let actions happen silently. See references/extensions/context-menus.md for a complete toast implementation.
The LanguageModel API works in all extension contexts — service worker, popup, and side panel — with no additional manifest permissions required. Extensions also get LanguageModel.params(), which is unavailable on the web:
const params = await LanguageModel.params();
// { defaultTopK: 3, maxTopK: 128, defaultTemperature: 1, maxTemperature: 2 }For general Prompt API patterns (availability checks, session creation, streaming), use the modern-web-guidance skill. See references/extensions/prompt-api.md for the extension-specific wiring example.
chrome.action API requires action in manifestUsing chrome.action.setBadgeText, chrome.action.setIcon, or chrome.action.onClicked requires
an "action" key in manifest.json — even if it's empty. Without it, chrome.action is undefined.
// ❌ BROKEN — manifest has no "action" key
await chrome.action.setBadgeText({ text: '5' });
// TypeError: Cannot read properties of undefined (reading 'setBadgeText')
// ✅ FIX — add "action" to manifest.json (at minimum an empty object)
{ "action": {} }
// or with a popup:
{ "action": { "default_popup": "popup/popup.html" } }activeTab only works on direct user gestures — not from side panelsactiveTab grants temporary access to the current tab ONLY on a direct user gesture (action
icon click, context menu item, keyboard shortcut, omnibox suggestion) — NOT from a button click
inside a side panel or popup. Use tabs + host_permissions instead. See
references/extensions/permissions.md and references/extensions/side-panel.md.
When creating a DevTools panel, the panel HTML path is relative to the extension root, NOT
relative to the devtools page that calls chrome.devtools.panels.create().
// ❌ BROKEN — path relative to devtools/ directory
chrome.devtools.panels.create("My Panel", "", "panel/panel.html");
// ✅ CORRECT — full path from extension root
chrome.devtools.panels.create("My Panel", "", "devtools/panel/panel.html");See references/extensions/devtools.md.
Offscreen documents (chrome.offscreen) are severely restricted. Most chrome.* APIs
are unavailable, including chrome.downloads, chrome.tabs, chrome.action, and others.
// ❌ BROKEN — chrome.downloads is undefined in offscreen documents
chrome.downloads.download({ url, filename: 'recording.webm' }); // TypeError
// ❌ BROKEN — chrome.action is undefined in offscreen documents
chrome.action.setBadgeText({ text: 'REC' }); // TypeErrorThe only APIs available in offscreen documents are:
chrome.runtime.sendMessage / chrome.runtime.onMessagechrome.runtime.getURLRule of thumb: Offscreen documents do the Web API work (recording, parsing, audio). The service worker does all chrome.* API work (downloads, badge updates, notifications). Use chrome.runtime.sendMessage to bridge between them. See references/extensions/message-passing.md.
chrome.notifications.create() requires a valid iconUrl pointing to an actual image file.
If the file doesn't exist or the path is wrong, the call fails with "Unable to download all specified images."
// ❌ BROKEN — icon file doesn't exist
chrome.notifications.create('reminder', {
type: 'basic',
iconUrl: 'icons/icon-128.png', // File not in extension!
title: 'Reminder',
message: 'Time is up!'
});
// ✅ Generate a data URL at runtime via OffscreenCanvas — no file needed.
// See `references/extensions/icons.md` for a reusable implementation.
const iconUrl = await getIconDataUrl();
chrome.notifications.create('reminder', { type: 'basic', iconUrl, title: 'Reminder', message: 'Time is up!' });This applies to ALL image references in chrome.* APIs — notifications, chrome.action.setIcon,
context menu icons, etc. If you reference a file, it must exist.
chrome.tabCapture.getMediaStreamId() fails with "Cannot capture a tab with an active stream"
if called while a previous capture is still active. Fast double-clicks on the extension icon
easily trigger this. Use explicit state locking:
// ❌ BROKEN — no guard against rapid clicks
let isRecording = false;
chrome.action.onClicked.addListener(async (tab) => {
if (isRecording) { stopRecording(); isRecording = false; }
else { isRecording = true; startRecording(tab); } // Second click = "active stream" error
});
// ✅ CORRECT — use transitional states to lock out concurrent operations
// State machine: 'idle' → 'starting' → 'recording' → 'stopping' → 'idle'
// Store state in chrome.storage.session (survives SW restart, cleared on browser close)
chrome.action.onClicked.addListener(async (tab) => {
const { recordingState = 'idle' } = await chrome.storage.session.get('recordingState');
if (recordingState === 'starting' || recordingState === 'stopping') return;
if (recordingState === 'idle') {
await chrome.storage.session.set({ recordingState: 'starting' });
try {
await startRecording(tab);
await chrome.storage.session.set({ recordingState: 'recording' });
await chrome.action.setBadgeText({ text: 'REC' });
await chrome.action.setBadgeBackgroundColor({ color: '#FF0000' });
} catch (err) {
console.error('Failed to start recording:', err);
await chrome.storage.session.set({ recordingState: 'idle' });
}
} else if (recordingState === 'recording') {
await chrome.storage.session.set({ recordingState: 'stopping' });
try { await stopRecording(); }
finally {
await chrome.storage.session.set({ recordingState: 'idle' });
await chrome.action.setBadgeText({ text: '' });
}
}
});This pattern applies to any chrome API that manages exclusive resources:
chrome.tabCapture, chrome.desktopCapture, chrome.offscreen.createDocument (only one
offscreen document allowed at a time). See references/extensions/media-capture.md.
chrome.desktopCapture requires a target tab with URL accessWhen calling chrome.desktopCapture.chooseDesktopMedia() from a service worker, you must pass
the active tab as the targetTab parameter. The tab object must have its url field populated,
which requires the "tabs" permission.
// ❌ BROKEN — called without targetTab from service worker
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], (streamId) => { ... });
// Error: "A target tab is required when called from a service worker context."
// ❌ BROKEN — tab doesn't have url field (missing "tabs" permission)
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], tab, (streamId) => { ... });
// Error: "targetTab doesn't have URL field set."
// ✅ CORRECT — "tabs" permission in manifest + pass tab object
// manifest.json: { "permissions": ["tabs", "desktopCapture"] }
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], tab, (streamId) => {
if (!streamId) return; // User cancelled
});Note: Prefer chrome.tabCapture.getMediaStreamId() for tab-only recording. Use chrome.desktopCapture only when the user should choose which screen/window to capture. See references/extensions/media-capture.md.
chrome.userScripts runs user-provided code at runtime. Use it for script managers and
user automation — not for extension-bundled scripts.
isUserScriptsAvailable() before any chrome.userScripts.* call and show an error UI when it returns false.chrome.storage; re-register them in runtime.onInstalled for the "update" reason.configureWorld({ messaging: true }) first; listen on runtime.onUserScriptMessage, not runtime.onMessage.ScriptSource constraint: each js entry must have exactly one of code or file. id constraint: cannot start with _.See references/extensions/user-scripts.md.
chrome.windows has NO .query() method — use getAll, getLastFocused, or getCurrentUnlike chrome.tabs.query(), the chrome.windows API does NOT have a .query() method.
// ❌ BROKEN — chrome.windows.query does not exist
const windows = await chrome.windows.query({ focused: true });
// TypeError: chrome.windows.query is not a function
// ✅ CORRECT — use the right method for your need
const focused = await chrome.windows.getLastFocused({ populate: true });
const current = await chrome.windows.getCurrent({ populate: true });
const all = await chrome.windows.getAll({ populate: true });chrome.windows methods: getAll, getLastFocused, getCurrent, get(windowId), create, update, remove. See references/extensions/tab-management.md.
chrome.permissions.request() in the service worker must be called with no await before it in the message listenerA user gesture from a UI context (side panel, popup) does propagate across chrome.runtime.sendMessage
to the service worker's onMessage listener — but only for that one synchronous turn. If the
listener does an await (even a short delay) before calling chrome.permissions.request(), the
gesture is gone and the call throws "This function must be called during a user gesture". Call
it as the first thing in the listener, with nothing awaited before it — see
references/extensions/permissions.md.
Never generate Manifest V2 code.
background.service_worker not background.scriptschrome.action not chrome.browserActionchrome.scripting.executeScript not chrome.tabs.executeScripthost_permissions is separate from permissions<script src="file.js">addEventListenerManage CHROMEWEBSTORE.md — the single source of truth for all Chrome Web Store listing
metadata, permissions justifications, privacy disclosures, version history, and publishing
readiness for a Chrome extension project.
Every time you touch a Chrome extension project in a way that affects its store presence,
update (or create) CHROMEWEBSTORE.md in the project root. The file tracks everything the
developer needs to fill out in the Chrome Developer Dashboard, so they can copy-paste from
a single doc instead of scrambling at publish time.
Create it the moment any of these happen:
Use the template in references/webstore/chromewebstore-template.md as your starting point. Read it
before generating the file.
Update it whenever:
For each section, pull information from the actual project files:
manifest.json to extract name, version, description, permissions, host_permissionsWrite store-facing copy in a tone that is specific, honest, and benefit-oriented. The Chrome Web Store review team rejects vague descriptions. "Makes your life easier" will be rejected. "Highlights search results on any webpage and lets you save highlights to a local list" will pass.
Never mention implementation details. Users care what the extension does for them, not how it was built. Strip any mention of APIs, libraries, frameworks, or code patterns:
| ❌ Implementation detail (cut it) | ✅ User benefit (keep it) |
|---|---|
| "Uses a MutationObserver to detect page changes" | "Automatically detects new content as you browse" |
| "Built with custom elements and Shadow DOM" | "Works seamlessly without affecting page styles" |
| "Powered by a service worker for background processing" | "Runs quietly in the background without slowing your browser" |
| "Leverages the chrome.storage.sync API" | "Your settings sync across all your devices" |
| "Implements declarativeNetRequest for filtering" | "Blocks ads and trackers without reading your page content" |
Read references/webstore/chromewebstore-template.md before generating the file — it defines
what each section covers and how to fill it out. The highest-risk section is Permissions
Justification: write a specific plain-English reason per permission and per host_permission.
"Needed for the extension to work" will be rejected. Read references/webstore/privacy-policy.md
for guidance on generating a privacy policy.
Before submission, run through references/webstore/review-checklist.md. The most common
first-submission failures:
.git/, node_modules/, .env, CHROMEWEBSTORE.mdFor copy guidelines and common rejection reasons, see references/webstore/store-listing.md.
Key rule: lead with function ("Highlights search terms on any webpage"), not feeling ("Enjoy
searching again").
For detailed API patterns and publishing guidance, read the relevant file BEFORE writing code or content:
| Topic | Reference |
|---|---|
| Permissions | references/extensions/permissions.md |
| Side panels | references/extensions/side-panel.md |
| Content scripts & DOM | references/extensions/content-scripts.md |
| Popups | references/extensions/popup-ui.md |
| Service worker lifetime | references/extensions/service-worker.md |
| Code execution & CSP | references/extensions/csp-sandbox.md |
| API calls | references/extensions/api-calling.md |
| Declarative Net Request | references/extensions/declarative-net-request.md |
| Chrome Prompt API | references/extensions/prompt-api.md |
| DevTools panels | references/extensions/devtools.md |
| Authentication | references/extensions/auth-identity.md |
| Context menus | references/extensions/context-menus.md |
| Omnibox | references/extensions/omnibox.md |
| Storage | references/extensions/storage.md |
| Tab & window management | references/extensions/tab-management.md |
| Tab/desktop capture | references/extensions/media-capture.md |
| User scripts | references/extensions/user-scripts.md |
| Message passing | references/extensions/message-passing.md |
| Icons | references/extensions/icons.md |
| CHROMEWEBSTORE.md template | references/webstore/chromewebstore-template.md |
| Privacy policy guidance | references/webstore/privacy-policy.md |
| Pre-publish review checklist | references/webstore/review-checklist.md |
| Store listing tips & rejections | references/webstore/store-listing.md |
Verify EVERY item before delivering:
manifest_version: 3 — no V2 APIs anywhereeval() in extension pagestabs permission declared if tab.url or tab.title is accessedasync/await — no .then() chainsrequestAnimationFramechrome.storage"action": {} (or more) present in manifest if using chrome.action.* APIstabs + host_permissions (NOT activeTab)chrome.devtools.panels.create() are relative to extension rootchrome.runtime messaging — no chrome.downloads, chrome.action, etc.chrome.notifications, chrome.action.setIcon, etc. point to real files (or use data URLs)chrome.desktopCapture.chooseDesktopMedia passes targetTab with tabs permissionchrome.windows calls use getAll/getLastFocused/getCurrent — NOT .query() (it doesn't exist)chrome.permissions.request() in a service worker onMessage listener is called with no await before it (gesture is lost after the first async gap)chrome.userScripts availability checked before use (API throws if user hasn't enabled it)chrome.storage and restored on runtime.onInstalled "update" reasonconfigureWorld({ messaging: true }) called before user scripts send messages; listening on onUserScriptMessage not onMessageScriptSource entries each have exactly one of code or file (not both, not neither)id values do not start with underscoresidePanel.setPanelBehavior uses openPanelOnActionClick — NOT openPanelOnActionIconClickhost_permissions scoped to specific domains (not <all_urls> unless needed)return true in onMessage listeners with async responses"tab" in chrome.contextMenus contexts requires Chrome M150+© GoogleChrome, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 24 other files (references) in skills-src/chrome-extensions of GoogleChrome/modern-web-guidance-src.
Open the folder on GitHubat commit c312847
Chrome Extensions next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Chrome Extensions this skillGoogleChrome/modern-web-guidance-src | 1.1k | — | ~6.6k | Automated safety check: Notes | Apache-2.0 | |
| Wxt Browser Extensionsvantezzen/skip-silence | 468 | 2 repos | ~1.9k | Automated safety check: Pass | MIT | |
| Kimi WebbridgeMoonshotAI/kimi-code | 7.8k | — | ~3.6k | Automated safety check: Pass | MIT | |
| Repomix Browser Extension Developeryamadashy/repomix | 29k | 1 repos | ~288 | Automated safety check: Pass | MIT | |
| Deepseek Automationzhu1090093659/deepseek-pp | 1.9k | — | ~2.1k | Automated safety check: Notes | Apache-2.0 | |
| Live Extension UI Automationqixing-jk/all-api-hub | 4.9k | — | ~2.6k | Automated safety check: Pass | AGPL-3.0 |
vantezzen/skip-silence
WXT browser extension performance optimization guidelines. An agent skill from vantezzen/skip-silence.
MoonshotAI/kimi-code
Kimi Browser Extension(Kimi 浏览器扩展,原 Kimi WebBridge)lets AI control the user's real browser — navigate, click, type, read, screenshot, and interact with any website using the user's actual login…
yamadashy/repomix
Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content…
zhu1090093659/deepseek-pp
A skill your agent uses when implementing, resuming, reviewing, or verifying the DeepSeek++ Codex-style automation feature in this repository.
qixing-jk/all-api-hub
Control, debug, and test the live dev browser extension UI (Options, Popup, Sidepanel) via CDP with persistent login states and accounts.
Shpigford/chops
Cuts a new Knockoff release by bumping the version, rolling release notes, tagging and shipping to the Chrome, Firefox and Apple stores with a per-store report.
GoogleChrome/modern-web-guidance-src
Downloads and analyzes the latest three distinct nightly evaluation runs (Claude Code, Codex CLI, and Jetski CLI) from the GCS remote dashboard to identify and flag unhealthy or low-performing tasks…
GoogleChrome/modern-web-guidance-src
Run a document coherence, link integrity, and git repository status audit across repository markdown files using a dedicated subagent.
GoogleChrome/modern-web-guidance-src
Action-oriented guidelines for privacy by design, data minimization, third-party audits, and modern browser privacy APIs.
GoogleChrome/modern-web-guidance-src
Coding style, architectural conventions, and PR review standards for the modern-web-guidance-src (guidance) repository.
GoogleChrome/modern-web-guidance-src
Workflow for refactoring discipline-level guides (e.g., JavaScript, CSS) to remove "Common Knowledge" by generating and comparing against model-specific "Knowledge Mirrors".
GoogleChrome/modern-web-guidance-src
Best practices for creating expectations and grader files to evaluate guidance quality.
Works with
Categories
Build and publish Chrome Extensions using Manifest V3 best practices. Chrome Extensions is an agent skill from GoogleChrome/modern-web-guidance-src. Build and publish Chrome Extensions using Manifest V3 best practices.
Chrome Extensions fits situations like: the user asks to create; understand Chrome browser extensions; anything involving the Chrome Extensions API; mentions of: Chrome extension.
Run `npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a claude-code`. Or copy the skill folder (skills-src/chrome-extensions in GoogleChrome/modern-web-guidance-src) into .claude/skills/chrome-extensions in your project. Claude Code loads it when a task matches its description.
Run `npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a codex`. Or copy the skill folder (skills-src/chrome-extensions in GoogleChrome/modern-web-guidance-src) into .agents/skills/chrome-extensions in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add GoogleChrome/modern-web-guidance-src --skill chrome-extensions -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/chrome-extensions, .gemini/skills/chrome-extensions, .github/skills/chrome-extensions and .opencode/skills/chrome-extensions in your project.
Going by SKILL.md and its folder, Chrome Extensions needs the command-line tools its instructions call (just).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Chrome Extensions is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.6k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 24k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Chrome Extensions: Wxt Browser Extensions (vantezzen/skip-silence, 468 stars), Kimi Webbridge (MoonshotAI/kimi-code, 7.8k stars), Repomix Browser Extension Developer (yamadashy/repomix, 29k stars) and Deepseek Automation (zhu1090093659/deepseek-pp, 1.9k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
GoogleChrome (a GitHub organization) maintains it in GoogleChrome/modern-web-guidance-src, which has 1,134 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on October 7, 2026.
Source: GoogleChrome/modern-web-guidance-src on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.