chrome-extension
samber/cc-skills
Manifest V3를 사용하여 Chrome 확장 프로그램을 개발하기 위한 종합 가이드. 사용자가 Chrome 확장 프로그램, 브라우저 확장 프로그램, manifest.json, 콘텐츠 스크립트, 서비스 워커(확장 프로그램 컨텍스트 내), 팝업, 사이드 패널, chrome.runtime, chrome.tabs, chrome.storage, chrome.scripting, 백그라운드 스크립트, MV3, Manifest V3 또는 기타 Chrome 확장 프로그램 API를 언급할 때마다 이 스킬을 사용하세요. 또한 사용자가 웹 페이지에 스크립트를 삽입하거나, 페이지와 백그라운드 간에 통신하거나, 콘텐츠에서 CSP를 우회하고자 할 때도 이 스킬을 실행하세요.
...모든 것을 확장하십시오소개 chrome-extension
Manifest V3를 사용하여 Chrome 확장 프로그램을 구축, 디버깅 및 게시하는 방법에 대한 포괄적인 가이드로, 라우팅 문서 형태로 구성되어 있습니다. 먼저 메인 파일을 읽어 아키텍처와 결정 지점을 파악한 후, 구현 세부 사항에 대해서는 관련성이 높고 독립적으로 구성된 참조 파일만 로드합니다. 참조 파일에는 manifest.json 설정 및 버전 관리, 서비스 워커의 라이프사이클과 상태 지속성, 콘텐츠 스크립트 및 격리된 환경과 메인 환경 간의 주입, 메시징 및 RPC 계층, UI 요소(팝업, 옵션 페이지, 사이드 패널, 컨텍스트 메뉴, 명령어, 알림, 옴니박스, 개발자 도구 패널), chrome.storage 사용 및 할당량, 네트워크 및 CSP 처리, 권한, 웹에서 접근 가능한 리소스, TypeScript 빌드 설정, Chrome 웹 스토어 게시, 실행 컨텍스트 흐름도, 그리고 흔한 디버깅 실수 등을 다룹니다. 이 튜토리얼은 AI 코딩 에이전트를 대상으로 하며, git과 node가 필요하며, 파일 편집부터 git, gh, npm 명령어에 이르는 도구들이 명시되어 있습니다.
아키텍처 개요에 따르면, 확장 프로그램은 메시지 전달을 통해 통신하는 최대 5개의 실행 컨텍스트를 갖습니다: 서비스 워커(백그라운드, DOM 없음, 일시적, 모든 chrome.* API)에 대한 액세스 권한이 있음), 팝업, 옵션 페이지 및 사이드 패널(모두 전체 DOM 및 API 사용 가능), 그리고 웹 페이지 자체에 있는 격리된 환경의 콘텐츠 스크립트와 페이지 컨텍스트의 메인 월드 스크립트입니다. 도표에 따르면, 콘텐츠 스크립트는 DOM을 공유하지만 자체 JS 범위와 chrome.runtime 및 chrome.storage에 대한 접근 권한을 가지며 네트워크에 대해서만 CSP의 적용을 받는 반면, 메인 월드 스크립트는 페이지에 대한 전체 접근 권한을 가지지만 chrome.* API는 사용할 수 없으며 CSP의 완전한 적용을 받습니다. 이 둘은 공유된 DOM을 통해 window.postMessage를 이용해 통신합니다.
통신 패턴은 표로 요약되어 있습니다: 모든 확장 프로그램 컨텍스트에서 서비스 워커로 단회성 요청/응답을 보내는 chrome.runtime.sendMessage(가장 일반적인 경우), chrome.tabs.sendMessage는 서비스 워커에서 tabId를 통해 특정 탭으로 데이터를 전송하는 데 사용되며, chrome.runtime.connect 포트는 양방향 스트리밍 및 진행 상황 전달(서비스 워커에서 팝업으로의 전송 포함)에 사용되고, window.postMessage는 동일한 페이지 내의 서로 다른 영역 간 통신에 사용됩니다. 서비스 워커는 일시적이며 확장 프로그램 페이지로 직접 데이터를 전송할 수 없기 때문에, 이 가이드는 독자들에게 포트를 사용하거나 모든 컨텍스트에서 동시에 발동되는 chrome.storage.onChanged를 활용하도록 안내하며, 보다 상세한 흐름도 및 컨텍스트별 기능과 제한 사항에 대한 분석은 execution-contexts 참조 문서를 참고하도록 안내합니다. 이 가이드는 프레임워크별 질문에는 사용해서는 안 된다고 명시하고 있습니다.
FAQ
이 문서는 어떤 Chrome 확장 프로그램 매니페스트 버전을 다루나요?
매니페스트 V3(MV3)입니다. 이 스킬은 manifest.json의 설정 및 수정, 아이콘 구성, 버전 관리뿐만 아니라 더 광범위한 MV3 아키텍처, 메시징 및 게시 프로세스를 다룹니다.
스킬은 어떻게 구성되어 있나요?
라우팅 문서 형태로 구성되어 있습니다. 먼저 메인 SKILL.md를 읽어 아키텍처와 결정 지점을 파악한 다음, 구현 세부 사항에 대해서는 관련성이 높고 독립적인 참조 파일(예: service-worker.md, content-scripts.md 또는 messaging-rpc.md)만 불러오면 됩니다.
이 가이드에 따르면 Chrome 확장 프로그램에는 어떤 실행 컨텍스트가 있나요?
메시지 전달을 통해 통신하는 최대 5개의 컨텍스트가 있습니다. 확장 프로그램 프로세스 내의 서비스 워커(백그라운드), 팝업, 옵션 페이지, 사이드 패널과 더불어, 웹 페이지상의 콘텐츠 스크립트(격리된 환경) 및 메인 월드 스크립트가 있습니다.
일반적인 요청/응답에는 어떤 메시징 방법을 사용해야 하나요?
어떤 확장 프로그램 컨텍스트에서든 서비스 워커로 chrome.runtime.sendMessage를 사용하면 됩니다. 가이드에 따르면 이 방법이 약 90%의 경우를 처리한다고 합니다. 특정 탭으로 메시지를 전송하려면 chrome.tabs.sendMessage를 사용하고, 양방향 스트리밍을 위해서는 chrome.runtime.connect 포트를 사용하십시오.
이 스킬을 사용하기 위한 전제 조건은 무엇인가요?
이 스킬은 Claude Code 또는 이와 유사한 AI 코딩 에이전트를 위해 설계되었으며, git과 node가 필요합니다(메타데이터에는 git, node, npm이 나열되어 있습니다). 프레임워크별 질문에 대한 용도로는 의도되지 않았습니다.
모든 파일
14개파일references/content-scripts.md12.5KB보기references/execution-contexts.md18.3KB보기references/messaging-rpc.md19.4KB보기references/permissions.md8.0KB보기references/service-worker.md10.9KB보기references/typescript-build.md7.9KB보기references/debugging-mistakes.md9.2KB보기references/manifest-v3.md7.2KB보기references/network-csp.md9.9KB보기references/publishing.md6.4KB보기참조/storage.md9.0KB보기참조/ui-surfaces.md9.7KB보기참조/web-accessible-resources.md3.1KB보기SKILL.md16.0 KB보기This skill covers everything needed to build, debug, and publish Chrome extensions with MV3. It is organized as a routing document: read this file first to understand the architecture and decision points, then load the relevant reference file for implementation details.
Reference files
Read only the reference files relevant to the current task. Each file is self-contained.
| File | When to read |
|---|---|
references/manifest-v3.md | Setting up or modifying manifest.json, configuring icons, versioning |
references/service-worker.md | Background logic, lifecycle, state persistence, alarms, events |
references/content-scripts.md | Injecting code into pages, isolated/main world, dynamic injection, SPA handling, orphaning |
references/messaging-rpc.md | Communication between any contexts, typed protocols, RPC layer, async handler patterns |
references/ui-surfaces.md | Popup, options page, side panel, context menus, commands, notifications, omnibox, devtools panel |
references/storage.md | chrome.storage (local/sync/session), quotas, reactive patterns, framework hooks |
references/network-csp.md | HTTP requests from content scripts, CSP bypass relay, declarativeNetRequest, offscreen docs, CORS |
references/permissions.md | Required/optional permissions, host permissions, activeTab, runtime request flow |
references/web-accessible-resources.md | Exposing extension files to web pages, security implications |
references/typescript-build.md | TypeScript setup, project structure, build tools comparison, bundling |
references/publishing.md | Chrome Web Store submission, review process, rejection reasons, updates, privacy policy |
references/execution-contexts.md | Communication flow diagrams, per-context capabilities/limits, choosing the right messaging method |
references/debugging-mistakes.md | DevTools for extensions, testing SW termination, common gotchas, error patterns |
Architecture overview
A Chrome extension has up to 5 execution contexts that communicate via message passing:
┌──────────────────────────────────────────────────────────┐│ Extension Process ││ ┌─────────────────┐ ┌───────┐ ┌─────────┐ ┌──────┐ ││ │ Service Worker │ │ Popup │ │ Options │ │ Side │ ││ │ (background) │ │ │ │ Page │ │Panel │ ││ │ - No DOM │ │ Full │ │ Full │ │ Full │ ││ │ - Ephemeral │ │ DOM │ │ DOM │ │ DOM │ ││ │ - All chrome.* │ │ All │ │ All │ │ All │ ││ │ APIs │ │ APIs │ │ APIs │ │ APIs │ ││ └────────┬─────────┘ └───┬───┘ └────┬────┘ └──┬───┘ ││ │ chrome.runtime.sendMessage / connect │ │└───────────┼────────────────┼───────────┼──────────┼──────┘ │ │ │ │ chrome.tabs.sendMessage │ │ │ │ │ │ │┌───────────┼────────────────┼───────────┼──────────┼──────┐│ Web Page ▼ ││ ┌──────────────────┐ ┌──────────────────┐ ││ │ Content Script │ │ Main World Script │ ││ │ (isolated world) │◄──►│ (page context) │ ││ │ - Shared DOM │ │ - Shared DOM │ ││ │ - Own JS scope │ │ - Page JS scope │ ││ │ - chrome.runtime │ │ - No chrome.* API │ ││ │ - chrome.storage │ │ - Full page access│ ││ │ - Subject to CSP │ │ - Subject to CSP │ ││ │ (network only) │ │ (fully) │ ││ └──────────────────┘ └──────────────────┘ ││ ▲ window.postMessage ││ │ (through shared DOM) │└──────────────────────────────────────────────────────────┘Communication flows (labeled channels)
┌───────────────────────────────────────────────────────────────────────────┐│ Extension Process ││ ││ ┌─────────────────┐ chrome.runtime ┌───────┐ ┌─────────┐ ┌──────┐ ││ │ Service Worker │◄─.sendMessage()──│ Popup │ │ Options │ │ Side │ ││ │ (background) │◄─.connect()──────│ │ │ Page │ │Panel │ ││ │ │ └───────┘ └─────────┘ └──────┘ ││ │ - No DOM │ ┌────────────────────────────────────────────┐ ││ │ - Ephemeral 30s │ │ SW cannot push to these pages. │ ││ │ - All chrome.* │ │ Use: ports (.connect) or storage.onChanged │ ││ └────────┬─────────┘ └────────────────────────────────────────────┘ ││ │ ││ chrome.storage.onChanged ◄── fires across ALL contexts simultaneously ││ │└───────────┼──────────────────────────────────────────────────────────────┘ │ chrome.tabs.sendMessage(tabId, ...) [SW must know tabId] │┌───────────┼──────────────────────────────────────────────────────────────┐│ Web Page ▼ ││ ┌──────────────────┐ window.postMessage ┌──────────────────┐ ││ │ Content Script │◄───────────────────►│ Main World Script │ ││ │ (isolated world) │ Custom DOM events │ (page context) │ ││ │ │ │ │ ││ │ chrome.runtime ───┼── to/from SW │ No chrome.* APIs │ ││ │ chrome.storage │ │ Full page JS │ ││ │ Shared DOM │ │ Shared DOM │ ││ │ Page CSP (network)│ │ Page CSP (full) │ ││ └──────────────────┘ └──────────────────┘ │└──────────────────────────────────────────────────────────────────────────┘For detailed flow diagrams (three-layer bridge, cross-extension, storage broadcast) and a per-context breakdown of permissions, limits, and workarounds: → Read references/execution-contexts.md
Communication methods at a glance
| Method | Direction | Best for |
|---|---|---|
chrome.runtime.sendMessage | Any ext context → SW | One-shot request/response (90% of cases) |
chrome.tabs.sendMessage | SW → content script (by tabId) | Pushing data to a specific tab |
chrome.runtime.connect (Port) | Bidirectional | Streaming, progress, SW ↔ popup |
window.postMessage | Between worlds on same page | Page JS ↔ content script bridge |
chrome.storage.onChanged | Broadcast to all contexts | Settings sync, no messaging needed |
→ Full matrix with limits and edge cases: references/execution-contexts.md → Implementation patterns, typed protocols, RPC layer: references/messaging-rpc.md
Key architectural rules
Service worker is ephemeral. It terminates after 30s of inactivity. All state must be persisted to chrome.storage. All event listeners must be registered synchronously at the top level. Never use setTimeout/setInterval for anything beyond a few seconds. → Read
references/service-worker.mdContent scripts run in the page's origin. Network requests from content scripts are subject to the page's CSP and CORS. To bypass, relay through the service worker. → Read
references/network-csp.mdMessaging is the backbone. Every cross-context interaction uses chrome.runtime messaging. The #1 bug: forgetting to
return truefrom async message listeners. → Readreferences/messaging-rpc.mdPermissions determine CWS review speed. Broad host_permissions trigger manual review (weeks). activeTab + optional permissions = fast automated review. → Read
references/permissions.mdPopup is destroyed on blur. Side panel persists. Choose based on interaction duration. → Read
references/ui-surfaces.md
Decision tree: which context handles what?
"I need to run code when the user visits a page"
→ Content script. Static (manifest) for known URL patterns, dynamic (chrome.scripting) for user-triggered injection. Default to isolated world unless you need page JS access. → Read references/content-scripts.md
"I need to make an HTTP request to my API"
- From popup/options/side panel: direct fetch() works (extension origin, no CSP issues)
- From content script on a page with restrictive CSP: relay through service worker
- From service worker: direct fetch() works (requires host_permissions for the target domain) → Read
references/network-csp.md
"I need to store user settings"
- Settings that sync across devices: chrome.storage.sync (100KB limit)
- Large data or caches: chrome.storage.local (10MB, or unlimited with permission)
- Ephemeral state surviving SW restarts: chrome.storage.session → Read
references/storage.md
"I need to modify HTTP headers or block requests"
→ declarativeNetRequest (NOT webRequest, which lost blocking in MV3) → Read references/network-csp.md
"I need the page's JavaScript to talk to my extension"
→ Three-layer bridge: page (window.postMessage) → content script → service worker → Read references/messaging-rpc.md
"I need to understand what each context can and cannot do"
→ Read references/execution-contexts.md — per-context cards listing chrome.* access, DOM, network, storage, lifetime, hard limits, and practical workarounds.
"I need periodic background tasks"
→ chrome.alarms (minimum 30s interval). NOT setTimeout. → Read references/service-worker.md
"I need DOM APIs in the background" (DOMParser, Canvas, Audio)
→ Offscreen document. One per extension, only chrome.runtime available. → Read references/network-csp.md
"I need to authenticate with OAuth"
→ chrome.identity.launchWebAuthFlow() or chrome.identity.getAuthToken() (Google only) → Read references/service-worker.md (identity section)
Workflow: new extension from scratch
Define the manifest with minimum permissions. Start with
activeTab+scripting. → Readreferences/manifest-v3.mdSet up TypeScript and build tooling (or use CRXJS for Vite-based dev). → Read
references/typescript-build.mdImplement the service worker with all event listeners at the top level. → Read
references/service-worker.mdAdd content scripts if you need page interaction. → Read
references/content-scripts.mdBuild UI surfaces (popup, options, side panel) as needed. → Read
references/ui-surfaces.mdWire up messaging between all contexts. → Read
references/messaging-rpc.mdTest with DevTools, specifically test service worker termination. → Read
references/debugging-mistakes.mdPublish to Chrome Web Store. → Read
references/publishing.md
Workflow: adding a feature to an existing extension
- Identify which context the feature belongs to (see decision tree above).
- Read the relevant reference file(s) for that context.
- Check if new permissions are needed. Prefer optional_permissions for new capabilities. → Read
references/permissions.md - Update the manifest if adding new content scripts, UI surfaces, or permissions.
- Handle extension updates gracefully (content script orphaning). → Read
references/content-scripts.md(orphaning section)
Minimal manifest.json template
{ "manifest_version": 3, "name": "My Extension", "version": "1.0.0", "description": "What it does in one sentence", "permissions": ["storage", "activeTab", "scripting"], "action": { "default_popup": "popup.html", "default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } }, "background": { "service_worker": "background.js", "type": "module" }, "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }}→ For the full manifest reference with all fields: references/manifest-v3.md
Code patterns quick reference
Async message handler (the safe pattern)
// Wrap async handlers to avoid the return-true trapfunction asyncHandler( fn: (msg: any, sender: chrome.runtime.MessageSender) => Promise<any>,) { return ( message: any, sender: chrome.runtime.MessageSender, sendResponse: (r: any) => void, ) => { fn(message, sender) .then(sendResponse) .catch((e) => sendResponse({ __error: true, message: e.message })); return true; // literal true, not Promise<true> };}chrome.runtime.onMessage.addListener( asyncHandler(async (msg, sender) => { if (msg.type === "FETCH") { const res = await fetch(msg.url); return { ok: res.ok, data: await res.text() }; } }),);
CSP bypass relay (content script → service worker → API)
// content-script.tsasync function apiCall(endpoint: string, options?: RequestInit) { return chrome.runtime.sendMessage({ type: "API_RELAY", endpoint, options });}// background.tsconst ALLOWED_ENDPOINTS = ["https://api.example.com"];chrome.runtime.onMessage.addListener( asyncHandler(async (msg) => { if (msg.type !== "API_RELAY") return; if (!ALLOWED_ENDPOINTS.some((e) => msg.endpoint.startsWith(e))) { throw new Error("Blocked endpoint"); } const res = await fetch(msg.endpoint, msg.options); return { ok: res.ok, status: res.status, data: await res.text() }; }),);
Persist state across SW restarts
// Use chrome.storage.session for ephemeral statechrome.storage.session.setAccessLevel({ accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS",});async function getState<T>(key: string, fallback: T): Promise<T> { const result = await chrome.storage.session.get(key); return result[key] ?? fallback;}async function setState<T>(key: string, value: T): Promise<void> { await chrome.storage.session.set({ [key]: value });}
Orphaned content script detection
function isExtensionContextValid(): boolean { try { return !!chrome.runtime?.id; } catch { return false; }}// Before any chrome.runtime callif (!isExtensionContextValid()) { showRefreshBanner(); return;}
What NOT to do
- Do NOT use
eval(),new Function(), or load remote scripts. MV3 forbids it. - Do NOT use
setTimeout/setIntervalfor anything > 5s in service workers. - Do NOT register event listeners inside callbacks or async functions.
- Do NOT use
<all_urls>host permission unless absolutely necessary. - Do NOT rely on DevTools keeping the service worker alive during testing.
- Do NOT forget
return truein async message listeners. - Do NOT use
localStorageorsessionStoragein service workers (they don't exist there). - Do NOT assume content scripts survive extension updates.
- Do NOT use
webRequestblocking (removed in MV3). UsedeclarativeNetRequest. - Do NOT use
chrome.extension.getBackgroundPage()(removed in MV3).
모든 파일
14개 파일chrome-extension 설치
스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어주세요.
ZIP 다운로드저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.
git clone https://github.com/samber/cc-skills/blob/main/skills/chrome-extension/SKILL.md # Copy SKILL.md to your .claude/skills/ directory
복사





집
