この記事でわかること
- Claude Code を相棒にして Chrome拡張(Manifest V3)をゼロから動かすまでの全手順
- manifest.json / content script / service worker / popup の役割分担と Claude Code への指示の出し方
- permission不足・CSP・service worker寿命など MV3 特有の詰まりポイントとその解決パターン
対象読者:自分用ツールや社内業務効率化のために Chrome拡張を作りたい開発者・実務者。Chrome拡張のコードを書いたことがなくても OK。
「特定ページのテキストをワンクリックでコピーしたい」「社内業務ツールに専用ボタンを追加したい」——そんな小さな要求を叶えるのが Chrome拡張です。ただ Manifest V3 への移行で仕様が大幅に変わり、「昔のチュートリアルどおりに書いたら動かない」という経験をした人も多いはず。Claude Code があれば、manifest.json の権限設計からデバッグ、審査向け整備まで、対話しながら一気に通せます。この記事では 2026年6月時点の MV3 仕様をベースに、実コードと Claude Code への指示例をセットで紹介します。

Chrome拡張(MV3)の構成要素を整理する
コードを書く前に、Chrome拡張がどんなパーツで成り立っているかを頭に入れておきましょう。MV3 の主要コンポーネントは次の 5 つです。
manifest.json
拡張全体の設定ファイル。name / version / manifest_version(値は必ず 3)が必須で、ここで使う API の権限や動かすスクリプトをすべて宣言します。後から権限を追加するとユーザーに再承認を求める場合があるため、設計初期に必要な権限を洗い出すのが重要です。
content script
指定した URL で読み込まれ、ページの DOM を直接操作できるスクリプトです。ただし実行コンテキストは「隔離された世界(isolated world)」に置かれるため、ページ側のグローバル変数や他の拡張のスクリプトとは干渉しません。直接呼べる拡張 API は chrome.storage / chrome.runtime.sendMessage など一部に限られ、それ以外は service worker 経由で間接アクセスします。
background service worker
MV2 の「background page」に相当しますが、常駐しないのが最大の違いです。イベント発火後 30 秒無操作でブラウザが終了させます。グローバル変数は揮発するため、状態を保持したい場合は必ず chrome.storage や IndexedDB を使います。
popup(action UI)
ツールバーアイコンをクリックしたときに表示される HTML ファイルです。独立したウィンドウなので CSS / JS を自由に書けます。ただし popup が閉じると JS コンテキストも消えるため、保持したい状態はここでも chrome.storage に逃がす必要があります。
options ページ
ユーザー設定 UI を提供するオプションの画面です。chrome://extensions の「拡張機能のオプション」リンクから開けます。設定値は chrome.storage.sync に保存しておくと、複数端末で同期されます。
Claude Code でプロジェクトを初期化する
構成が把握できたら、早速 Claude Code に仕事を振りましょう。ポイントは「何を作りたいか」「どの URL で動かすか」「どんな権限が要るか」を最初にまとめて伝えることです。
プロジェクト初期化の指示例①
Chrome拡張を作りたい。要件は次のとおり。
- 目的:GitHub の Issue ページを開いたとき、
Issue タイトルと URL を「#123 タイトル https://...」形式でクリップボードにコピーするボタンを
ページ右下に追加する
- 対象 URL: https://github.com/*/issues/*
- MV3 で実装すること
- 必要なファイル一式(manifest.json / content_script.js / popup.html / popup.js / icon.png 代替は SVG でも可)を
./chrome-ext-github-copy/ ディレクトリに生成してください
- manifest.json の permissions と host_permissions を最小限に設計し、
なぜその権限が必要かコメントで説明してください
Claude Code はこの指示から全ファイルを生成してくれます。生成後、manifest.json を開いて権限設計を確認するのが最初のチェックポイントです。
生成される manifest.json の例
{
"manifest_version": 3,
"name": "GitHub Issue Copier",
"version": "1.0.0",
"description": "GitHub Issue のタイトルと URL をワンクリックでコピーします。",
"permissions": [
"clipboardWrite"
],
"host_permissions": [
"https://github.com/*"
],
"content_scripts": [
{
"matches": ["https://github.com/*/issues/*"],
"js": ["content_script.js"]
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
}
clipboardWrite は Clipboard API に書き込む際に必要です。host_permissions は対象サイトだけに絞ることで、審査でのリジェクトリスクを下げられます。
権限設計を Claude Code に相談する
「どの権限が要るか分からない」という場面は頻繁にあります。そんなときはこう聞きましょう。
content script から fetch で外部 API を叩きたい(例: https://api.example.com/v1/)。
サービスワーカーには何もさせず、content script だけで完結させる場合、
manifest.json に必要な設定を教えてください。
審査で問題になりやすいポイントがあれば一緒に指摘してください。
Claude Code は host_permissions に対象ドメインを追加する方法と、connect-src CSP 設定が不要な理由(MV3 では manifest の host_permissions が優先)まで説明してくれます。
content script でページを操作する実例
content script の王道ユースケースは「DOM 操作」と「ページ情報の取得」です。実際のコードで流れをつかみましょう。
DOM を取得してボタンを挿入する
// content_script.js
(function () {
// 既に挿入済みなら何もしない
if (document.getElementById('gh-issue-copier-btn')) return;
const btn = document.createElement('button');
btn.id = 'gh-issue-copier-btn';
btn.textContent = '📋 Issue をコピー';
btn.style.cssText = [
'position:fixed',
'bottom:24px',
'right:24px',
'z-index:9999',
'padding:10px 16px',
'background:#0969da',
'color:#fff',
'border:none',
'border-radius:6px',
'cursor:pointer',
'font-size:14px',
].join(';');
btn.addEventListener('click', () => {
// ページタイトルから Issue 番号とタイトルを取得
const titleEl = document.querySelector('h1 bdi');
const issueNumEl = document.querySelector('.gh-header-number');
if (!titleEl || !issueNumEl) {
alert('Issue タイトルが見つかりませんでした');
return;
}
const text = `${issueNumEl.textContent.trim()} ${titleEl.textContent.trim()} ${location.href}`;
navigator.clipboard.writeText(text).then(() => {
btn.textContent = '✅ コピー済み';
setTimeout(() => { btn.textContent = '📋 Issue をコピー'; }, 2000);
});
});
document.body.appendChild(btn);
})();
このコードは IIFE(即時実行関数)で囲んでいます。content script は同じタブのページ遷移ごとに再読み込みされますが、SPA ではページが変わっても再実行されないことがあるため、複数回挿入されないよう ID チェックを入れています。
content script → service worker のメッセージング
content script から直接呼べない API(例:chrome.tabs)は service worker に処理を委譲します。
// content_script.js 側(送信)
chrome.runtime.sendMessage(
{ type: 'FETCH_EXTERNAL', url: 'https://api.example.com/v1/data' },
(response) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
return;
}
console.log('受け取ったデータ:', response.data);
}
);
// service_worker.js 側(受信)
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'FETCH_EXTERNAL') {
fetch(message.url)
.then((res) => res.json())
.then((data) => sendResponse({ data }))
.catch((err) => sendResponse({ error: err.message }));
// 非同期レスポンスには true を返す(必須)
return true;
}
});
非同期でレスポンスを返す場合は onMessage のコールバックで return true を書かないと、sendResponse が無効になります。これは MV3 でも変わらない MV3 初学者がよくハマるポイントです。Claude Code に「なぜ return true が必要か」を聞くと、コールバックのライフサイクルから丁寧に説明してくれます。
service worker と chrome.storage を使う
service worker はイベント駆動です。グローバル変数に状態を持たせると、ブラウザが service worker を終了させた瞬間に消えてしまいます。
chrome.storage.local で状態を永続化する
// service_worker.js
// 拡張インストール時の初期化
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
chrome.storage.local.set({ copyCount: 0, lastCopied: '' });
console.log('GitHub Issue Copier がインストールされました');
}
});
// content script からのメッセージを処理
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.type === 'INCREMENT_COPY_COUNT') {
chrome.storage.local.get(['copyCount'], (result) => {
const newCount = (result.copyCount || 0) + 1;
chrome.storage.local.set({ copyCount: newCount, lastCopied: message.text });
sendResponse({ newCount });
});
return true; // 非同期レスポンス用
}
});
chrome.storage.sync と local の使い分け
Chrome には 2 種類の storage API があります。
- chrome.storage.local:端末ローカルに保存。容量上限は 10 MB。センシティブなデータや大量データに向きます。
- chrome.storage.sync:Google アカウントと同期(要ログイン)。容量は 100 KB と小さいですが、複数端末で設定を共有したいときに便利。
Claude Code への指示例:
ユーザーが「コピー形式」を設定画面で選べるようにしたい。
設定値はブラウザ間で同期したいので chrome.storage.sync に保存する。
options ページ(options.html + options.js)を追加して、
現在の形式を取得・保存できる実装を作ってください。
popup UI(HTML / CSS / JS)を作る
popup は独立した HTML ページです。ツールバーアイコンをクリックすると開き、閉じると JS コンテキストが破棄されます。
シンプルな popup の実装例
<!-- popup.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { width: 260px; padding: 16px; font-family: system-ui, sans-serif; }
h1 { font-size: 14px; margin: 0 0 12px; }
.stat { font-size: 24px; font-weight: bold; color: #0969da; }
.label { font-size: 12px; color: #57606a; }
.last { margin-top: 12px; font-size: 12px; word-break: break-all; }
</style>
</head>
<body>
<h1>GitHub Issue Copier</h1>
<div class="stat" id="count">--</div>
<div class="label">今日のコピー回数</div>
<div class="last" id="last"></div>
<script src="popup.js"></script>
</body>
</html>
// popup.js
document.addEventListener('DOMContentLoaded', () => {
chrome.storage.local.get(['copyCount', 'lastCopied'], (result) => {
document.getElementById('count').textContent = result.copyCount ?? 0;
if (result.lastCopied) {
document.getElementById('last').textContent = `最後: ${result.lastCopied}`;
}
});
});
popup.html に <script> インラインで JS を書くと CSP エラーになります(MV3 のデフォルト CSP がインラインスクリプトを禁止)。必ず外部 JS ファイルに分離してください。これも Claude Code に「popup.html にインラインスクリプトを書いてください」と頼むと自動的に外部ファイルに分けてくれます。
ローカル読み込みとデバッグの手順
コードが揃ったら実機で動かしましょう。Chrome拡張の開発は「ローカル読み込み → 動作確認 → コード修正 → 再読み込み」のサイクルです。
chrome://extensions での読み込み
- Chrome で
chrome://extensionsを開く - 右上の「デベロッパーモード」トグルを ON にする
- 「パッケージ化されていない拡張機能を読み込む」をクリック
manifest.jsonが置いてあるディレクトリを選択
読み込みに成功するとカードが表示されます。エラーがあれば赤いバナーで内容が出るので、その文言を Claude Code にそのまま貼り付けて聞きましょう。
コンポーネントごとのリロード方法
- manifest.json を変更した場合:
chrome://extensionsの更新ボタンを押す - service_worker.js を変更した場合:同上
- content_script.js を変更した場合:拡張のリロード+対象タブのリロード(ページの再読み込み)が必要
- popup.html / popup.js を変更した場合:popup を閉じて再度開くだけで最新版が反映される
各コンポーネントのデバッグ方法
- content script:対象ページで DevTools を開く(F12)→ Console タブ。content script のログはここに出ます。
- service worker:
chrome://extensionsのカードにある「Service Worker」リンクをクリック → DevTools が開く。 - popup:popup を右クリック →「検証」で DevTools を開く。
よくあるエラーと Claude Code での解決パターン
MV3 への移行で新たに生まれたエラーは多く、エラーメッセージをそのまま Claude Code に貼り付けると高い精度で原因と解決策を返してくれます。代表的な 4 つを紹介します。
エラー① 権限不足(Permission denied)
manifest.json に権限を書き忘れると次のようなエラーが出ます:
Uncaught TypeError: Cannot read properties of undefined (reading 'tabs')
// または
chrome.tabs is not available: Permissions are missing.
Claude Code への聞き方:
content script から現在のタブの URL を取得しようとしたら
「chrome.tabs is not available」と出ました。
manifest.json のどこを修正すればよいか教えてください。
また、content script から直接 location.href で URL を取る方法と
service worker 経由で tabs API を叩く方法、どちらが適切ですか?
多くの場合、content script 内では location.href を使えばよく、tabs 権限は不要です。Claude Code は「最小権限で実現できる方法」を優先して提案してくれます。
エラー② CSP(Content Security Policy)違反
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"
原因は popup.html や options.html にインラインスクリプトを書いたケースがほとんどです。MV3 のデフォルト CSP はインラインスクリプトを禁止しています。解決策はすべての JS を外部ファイルに分離すること。
エラー③ service worker の予期せぬ終了
非同期処理の途中で service worker が停止し、コールバックが呼ばれなくなる問題です。chrome.runtime の idle timeout(無操作 30 秒で終了)が原因です。
service worker のタイムアウト対策として、
fetch を使う処理が 30 秒を超える可能性があります。
処理を分割するか、chrome.alarms API でウォームアップする方法を
コード例つきで教えてください。
Claude Code は chrome.alarms を使って定期的に service worker を起こし続けるパターンや、処理を細かいチャンクに分割する方法を提案します。ただし「常駐させ続ける」実装は審査で弾かれる可能性があるため、本当に必要な場合に限定します。
エラー④ XMLHttpRequest / fetch が content script から失敗する
Access to fetch at 'https://api.example.com/v1/' from origin 'https://github.com'
has been blocked by CORS policy
content script は対象ページのオリジンで動くため、CORS 制限を受けます。解決策は service worker 経由で fetch させること。manifest.json の host_permissions に対象 API のドメインを追加すれば、service worker からは CORS なしで叩けます。
content script から https://api.example.com/v1/search に fetch すると
CORS エラーが出ます。
service worker を経由する形にリファクタリングしてください。
manifest.json の変更点も含めて教えてください。
Chrome ウェブストアへの公開と審査のポイント
自分だけが使うツールなら公開不要ですが、社内配布や外部公開を考えるなら Chrome ウェブストアへの申請が必要です。2026年6月時点の審査フローと注意点をまとめます。
公開の基本手順
- Chrome デベロッパーダッシュボードで開発者アカウントを登録(初回登録料 5 ドル)
- 拡張のファイル一式を ZIP に圧縮(最大 2 GB)してアップロード
- ストアの説明・プライバシーポリシー・スクリーンショットを入力
- 「審査用の提出」をクリックして審査待ち
- 審査通過後、最大 30 日以内に手動公開するか自動公開を選択
審査で弾かれやすいポイント
- 過剰な権限:使っていない権限を manifest.json に書いていると確実に指摘されます。Claude Code に「この実装で本当に必要な最小権限は何か」と聞いて整理しましょう。
- プライバシーポリシーの欠如:ユーザーデータを収集する場合(
storage.syncを使う場合も含む)、プライバシーポリシーページの URL が必要です。 - リモートコードの実行:外部から JS をダウンロードして
eval()等で実行するパターンはポリシー違反です。 - 説明と機能の乖離:ストア説明に書いていない動作(例:特定 URL でのみ動く)がある場合、詳細な説明を求められます。
Claude Code に審査対策を依頼する
この Chrome拡張を Chrome ウェブストアに申請する予定です。
現在の manifest.json と全ファイルを確認して、
審査で問題になりそうな点をすべてリストアップしてください。
特に permissions / host_permissions の過剰指定、
リモートコード実行の疑いがある箇所、CSP 設定の問題を重点的に確認してください。
Claude Code はファイルを読み込んだ上で権限の過剰指定や CSP のミスを指摘し、プライバシーポリシー文書の叩き台まで生成してくれます。審査用のチェックリストを出力させておくと、再申請のコストを大幅に減らせます。
React / CLI ツールとの違い——Chrome拡張ならではの注意点
フロントエンド開発の経験がある場合、Claude Code × React 開発のノウハウはそのまま活かせます。ただし Chrome拡張には固有の制約があるので整理しておきましょう。
- バンドラー不要でも動く:シンプルな拡張なら webpack / Vite なしの Vanilla JS で十分です。ただし React を使って popup を作ることも可能で、その場合はバンドラーを組み合わせます。
- グローバル状態の管理が独特:Redux を popup 内で使っても、popup が閉じると状態は消えます。永続状態は必ず
chrome.storageに移す設計にします。 - TypeScript との相性は良い:
@types/chromeパッケージを入れると chrome.* API の型補完が効きます。Claude Code × TypeScript 型設計の記事も参考にしてください。 - CLI ツールと違って GUI が必須:popup や content script のビジュアルを実機で確認する手順が増えます。CLI ツール開発とは異なるデバッグサイクルに慣れることが最初のハードルです。
まとめ——Claude Code で Chrome拡張開発を加速する
Chrome拡張(Manifest V3)開発を Claude Code と進めるポイントをまとめます。
- 最初に全体設計を渡す:「何を作るか・どの URL で動かすか・どんな権限が要るか」を一度にまとめて伝えると、manifest.json の設計から始めて整合性のあるファイル群を生成できます。
- エラーメッセージはそのまま貼る:CSP エラーも permission エラーも、コンソールのテキストをそのまま Claude Code に渡すのが最速の解決法です。
- 「最小権限で実現する方法」を常に意識させる:権限を絞ることはセキュリティとストア審査の両方に効きます。Claude Code に審査視点での指摘を求めるのが効果的です。
- service worker の寿命を前提に設計する:グローバル変数ではなく
chrome.storageに状態を逃がす習慣を最初から持つと、謎のバグを大幅に減らせます。
小さな自分用ツールから社内配布ツール、ウェブストア公開まで、Chrome拡張の開発スコープは幅広いです。Claude Code を相棒にすれば、MV3 の仕様変更に振り回されることなく「作りたいもの」に集中できます。
Claude Code の使い方を体系的に学びたい方へ
- Uravation では Claude Code 個別指導・法人向け導入支援を提供しています
- Chrome拡張・社内ツール開発から CI/CD 連携まで、実務ユースケースに特化した伴走支援です
- お問い合わせ・無料相談はこちら
よくある質問(FAQ)
Manifest V2 の拡張は今後どうなりますか?
Google は Manifest V2 のサポートを段階的に終了する方針を示しています。2026年時点では新規の MV2 拡張はウェブストアへの登録が拒否され、既存の MV2 拡張も順次無効化される予定です。既存の MV2 拡張を持っている場合は MV3 への移行が必要です。
service worker が 30 秒で終了する問題を回避できますか?
完全に回避することはできません。設計として「終了してもよい」状態を保つのが基本方針です。状態は chrome.storage や IndexedDB に逃がし、service worker が再起動しても問題ない構造にします。どうしても長時間処理が必要な場合は chrome.alarms API で定期的に起こすか、処理を細かいチャンクに分割します。
React や TypeScript で Chrome拡張を作れますか?
どちらも使えます。Vite の「chrome-extension」テンプレートや crxjs プラグインを使うと HMR(ホットリロード)が効くので開発体験が大幅に上がります。Claude Code に「Vite + React + TypeScript で Chrome拡張のボイラープレートを作って」と頼むと、@types/chrome の設定まで含めたセットアップを生成してくれます。
Chrome ウェブストアの審査はどのくらいかかりますか?
内容によって異なりますが、数日から数週間かかる場合があります。権限が少ない・シンプルな拡張ほど早い傾向があります。審査通過後は最大 30 日以内に手動で公開するか、自動公開を設定することができます。
社内限定で配布する場合はウェブストアへの申請は必要ですか?
不要です。Google Workspace の管理コンソールから「グループポリシー」で特定の拡張を一括インストールさせることができます。または従業員に chrome://extensions のデベロッパーモードで手動読み込みしてもらう方法もあります。ただし --load-extension フラグを使った自動化は端末管理ポリシー次第です。