Claude Media
Claude CodeでChrome拡張をManifest V3で開発する手順

Claude CodeでChrome拡張をManifest V3で開発する手順

manifest.jsonの必須キー、service workerとcontent scriptの分離、chrome://extensionsでのデバッグ手順までをClaude Codeでの開発フローとして扱います。

Manifest V3(MV3)はChrome拡張機能プラットフォームの最新仕様です。MV2にあった常駐型のバックグラウンドページはservice workerに置き換わり、ページ上で動くcontent scriptとは実行環境を分けて書く必要があります。この記事では、manifest.jsonの必須キーからservice workerとcontent scriptの分離までを扱います。chrome://extensionsでの読み込みとデバッグも含め、Claude Codeで拡張機能を開発するときの具体的な手順を示します。

Chrome拡張(Manifest V3)の全体像

拡張機能は大きく3つの要素で構成されます。設定を書くmanifest.json、バックグラウンドのイベント処理を担うservice worker、ページ上で動くcontent scriptです。この3層構成を前提にコードを組み立てます。Manifest V3やChrome Web Storeの公開まわりのガイドをスキルとして渡す方法は、modern-web-guidanceプラグインの記事にあります。

MV3が目指すのは、プライバシー・セキュリティ・パフォーマンスの改善です。この移行は数年がかりの取り組みで、実務で押さえるべき変更点は次の3つです。

変更点

MV2から変わる3点

  • service workerへの移行

    MV2の常駐バックグラウンドページは、必要なときだけ起動するservice workerに置き換わりました。

  • リモートコードの禁止

    拡張機能パッケージに含まれないJavaScriptを実行時に読み込むことができません。CDN経由のライブラリ読み込みは通りません。

  • webRequestの非推奨化

    通信をすべて拡張機能経由でプロキシしていたwebRequestのblocking版は非推奨で、declarativeNetRequestが代替として案内されています。

この発想はElectronアプリのmain process/renderer processの分離と近いものがあります。Claude CodeでElectronアプリを開発する手順ではメイン/レンダラー構成を扱っています。プロセスを分離して書くという考え方は、そちらも参考になります。

Claude Codeにコードを書かせるときは、この3点を制約条件として先に伝えておくと、MV2時代の実装パターン(常駐前提のロジックやCDNからのライブラリ読み込み)を提案されにくくなります。

manifest.jsonに必要なキー

すべての拡張機能はルートディレクトリにmanifest.jsonを置きます。公式が示す最小構成は次の5キーです。このうちmanifest_version・name・versionは拡張機能プラットフォームの必須キーです。descriptionとiconsは、Chromeウェブストアで必須になるキーです。ローカルで読み込むだけなら、後者の2つが無くても動きます。

{
  "manifest_version": 3,
  "name": "Minimal Manifest",
  "version": "1.0.0",
  "description": "A basic example extension with only required keys",
  "icons": {
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  }
}

manifest_versionは3で固定します。ここにbackground・content_scripts・permissions・host_permissionsを足していく形で挙動を組み立てます。

キー役割必須/任意
manifest_version役割バージョン指定。3で固定必須/任意必須
name / version役割拡張機能の名前とバージョン必須/任意必須(プラットフォーム)
description / icons役割拡張機能の説明とアイコン必須/任意Chromeウェブストアで必須(公開時)
background.service_worker役割バックグラウンド処理を書くファイルを指定必須/任意service worker利用時に必須
content_scripts役割ページに挿入するスクリプトをmatchesパターンで指定必須/任意content script利用時に必須
permissions / host_permissions役割使うAPIとアクセス先ドメインの許可必須/任意使うAPIに応じて必須

content_scriptsではmatchesが必須で、対象パターンを指定しないと自動実行されません。css・jsはどちらか、または両方を指定できます。

service workerとcontent scriptを分離して書く

MV3のservice workerは、必要なときにロードされ、休止すればアンロードされます。イベントを受け取っている間は動き続けますが、シャットダウンすることもあります。DOMへのアクセスはできず、必要ならoffscreen documentと組み合わせます。Firefoxはservice workerを使わないため、同じ拡張を持ち込むときの指摘はClaude CodeでFirefoxアドオンを作る手順で確認できます。

content scriptはページの実行コンテキストで動きますが、拡張機能本体とは分離された世界(isolated world)で実行されます。ページ側のJavaScript変数を直接読み書きすることはできません。content scriptから直接呼べる拡張機能APIは限られています。

content scriptから直接呼べるのは次のAPIです。

  • dom / i18n / storage
  • runtime.connect() / runtime.getManifest() / runtime.getURL()
  • runtime.id / runtime.onConnect / runtime.onMessage / runtime.sendMessage()

tabs・scripting・declarativeNetRequestなど、それ以外の大半のAPIはcontent scriptから直接呼べません。「content scriptがタブを操作する」ような処理は避けます。content scriptからservice workerへメッセージを送り、service worker側でchrome.tabsやchrome.scriptingを呼ぶ設計にします。外部サーバーのデータをCORS制約を受けずに取得したい場合は、対象URLをhost_permissionsに宣言したうえで、service worker側でfetch()します。

Claude Codeでブラウザ・OS向けの拡張機能を作る手順は他にも扱っています。Claude CodeでFigmaプラグインを開発する手順やClaude CodeでRaycast拡張を作る手順も、本体とUI/ページを分離して書く設計は共通しています。

content scriptをどの方法でページに入れるか

manifest.jsonのcontent_scriptsに書くのは、注入方法の1つにすぎません。content scriptには静的宣言・動的宣言・プログラムによる注入の3つがあります。対象ページが決まっているかどうかで選びます。

くらべる

注入方法の使い分け

対象ページが決まっている

静的宣言

manifest.jsonのcontent_scriptsにmatchesとjs・cssを書きます。既知のページ群で自動実行したいときの方法です。

実行時に決める

動的宣言・プログラム注入

動的宣言はservice workerから実行時に登録します。プログラム注入は、タブのクリックなどをきっかけにscripting.executeScript()で入れます。

静的宣言には細かい指定があります。run_atの既定値はdocument_idleで、DOMの構築が終わったあと、画像やフレームの読み込みが終わる前後に注入されます。all_framesをtrueにすると、最上位フレームだけでなく一致するすべてのフレームに入ります。除外にはexclude_matchesを使います。

プログラム注入では、funcに渡した関数がコピーとして対象ページに送られます。関数の外側の変数を参照するとReferenceErrorになるので、必要な値は引数で渡します。

content scriptとservice workerの間でメッセージをやり取りする

1回限りのメッセージにはruntime.sendMessage()(拡張機能内向け)とtabs.sendMessage()(content script向け)を使います。例えば次のような形になります。

content-script.js:

document.addEventListener("mouseup", () => {
  const text = window.getSelection()?.toString().trim();
  if (!text) return;
  chrome.runtime.sendMessage({ type: "SAVE_SELECTION", text });
});

service-worker.js:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type !== "SAVE_SELECTION") return;
  chrome.storage.local.get({ notes: [] }, ({ notes }) => {
    notes.push({ text: message.text, url: sender.tab?.url, savedAt: Date.now() });
    chrome.storage.local.set({ notes }, () => sendResponse({ ok: true }));
  });
  return true;
});

sendResponseは既定では同期呼び出しが前提です。chrome.storageのような非同期処理をはさんで後から応答したい場合は、リスナーの戻り値としてリテラルのtrueを返し、メッセージチャネルを開いたままにします。Chrome 148からはリスナーがPromiseを返す形でも非同期応答ができるようになっていますが、公式もこの対応は段階的な展開中だとしています。当面はreturn trueのパターンを使うのが無難です。

Claude Codeで開発ループを回す

Claude Codeにこの3層構成のコードを書かせるとき、CLAUDE.mdへMV3固有の制約を明文化しておくと、MV2的な実装への逆戻りを防げます。例えば次のような断片です。

## Chrome拡張(Manifest V3)の規約
- manifest_version は3固定。MV2のbackground pageパターンに戻さない
- backgroundはservice_workerのみ。常駐前提の状態をグローバル変数に溜めない
- content scriptとservice workerの状態共有はchrome.storage経由で行う
- 外部CDNからスクリプトを読み込まない(パッケージ外のコード実行は不可)
- 新しいchrome.*APIを使うときはmanifest.jsonのpermissions/host_permissionsを同時に更新する

manifest.jsonの書き間違いはchrome://extensionsで読み込むまで気づきにくいため、編集のたびに機械的に検証するループを組んでおくと手戻りが減ります。最小構成なら次のようなチェックスクリプトで十分です。descriptionとiconsの有無も見るのは、ウェブストアへの公開を見据えているためです。

scripts/check-manifest.js:

const fs = require("fs");
const manifest = JSON.parse(fs.readFileSync("manifest.json", "utf8"));
const errors = [];
if (manifest.manifest_version !== 3) errors.push("manifest_version must be 3");
// description と icons はウェブストア公開時に必須(ローカル読み込みだけなら不要)
for (const key of ["name", "version", "description", "icons"]) {
  if (!(key in manifest)) errors.push(`missing key: ${key}`);
}
const sw = manifest.background?.service_worker;
if (sw && !fs.existsSync(sw)) errors.push(`service_worker not found: ${sw}`);
for (const cs of manifest.content_scripts ?? []) {
  if (!cs.matches?.length) errors.push("content_scripts entry has no matches");
  for (const f of [...(cs.js ?? []), ...(cs.css ?? [])]) {
    if (!fs.existsSync(f)) errors.push(`content script file not found: ${f}`);
  }
}
if (errors.length) {
  console.error(errors.join("\n"));
  process.exit(1);
}
console.log("manifest.json ok");

このスクリプトはエラーを1件で止めず、まとめて出力します。MV2のまま、versionのタイプミス、matchesなし、ファイル未作成のmanifest.jsonを渡すと、Node.js 20で次のように出ます。

$ node scripts/check-manifest.js
manifest_version must be 3
missing key: version
service_worker not found: service-worker.js
content_scripts entry has no matches
content script file not found: content-script.js

manifest_versionとversionを直し、service-worker.jsとcontent-script.jsを作り、matchesを足すとmanifest.json okで終了コード0になります。ファイルの有無までここで見ておけば、chrome://extensionsの読み込みダイアログまで待たずに済みます。JSONの括弧が欠けた場合はJSON.parseがSyntaxErrorで落ちるので、そのときはエラー位置を見て直すようClaude Codeに伝えます。

これをhooksに登録すると、Claude Codeがmanifest.jsonをEdit/Writeするたびに自動で実行されます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(manifest.json)",
            "command": "node scripts/check-manifest.js"
          }
        ]
      }
    ]
  }
}

permissions.allowにビルド・確認コマンドを登録しておけば、毎回の確認プロンプトを減らせます。

{
  "permissions": {
    "allow": ["Bash(node scripts/*)", "Bash(npm run *)"]
  }
}

Claude Codeにこのスクリプトの出力を読ませます。missing keyのようなエラーが出たら該当キーを追記させるループを回すと、chrome://extensionsで読み込む前の段階で構造的なミスを潰せます。

chrome://extensionsで読み込みとデバッグをする

コードを書いたら、パッケージ化せずにローカルで動作確認できます。手順は次のとおりです。

手順

パッケージ化せずに読み込む

  1. 1

    拡張機能の管理ページを開く

    chrome://extensionsを新しいタブで開きます。

  2. 2

    デベロッパーモードを有効にする

    右上のスイッチをオンにします。

  3. 3

    ディレクトリを選ぶ

    「パッケージ化されていない拡張機能を読み込む」をクリックし、manifest.jsonがあるディレクトリを選びます。

manifest.jsonにエラーがあると、この時点で「拡張機能を読み込めませんでした」というダイアログが表示されます。例えばversionキーをversionsのように書き間違えると、「Required value version is missing or invalid」というエラーが出ます。permissionsの値をactiveTabではなくactivetabと小文字で書いた場合、読み込みは通ります。ただし拡張機能の管理ページの「エラー」ボタンに「Permission 'activetab' is unknown or URL pattern is malformed」という警告が記録されます。

コードを編集したら、拡張機能カードの「更新」アイコン(オン/オフの切り替えスイッチの隣にある円形の矢印アイコン)をクリックして反映します。どこまで再読み込みが必要かはコンポーネントによって変わります。

コンポーネント再読み込みの要否
manifest.json再読み込みの要否必要
service worker再読み込みの要否必要
content script再読み込みの要否必要(対象ページの再読み込みも)
popup再読み込みの要否不要
オプションページ再読み込みの要否不要

service workerのログを見るには、拡張機能カードの「Inspect views」の横にあるリンクをクリックしてDevToolsを開きます。ここで注意が必要です。公式ドキュメントは、service workerをインスペクトしている間はservice workerがアクティブなまま保たれると説明しています。service workerが終了したときに正しく動くかを確認したい場合は、DevToolsを閉じてから動作を試す必要があります。

service workerが実際にいつ起動・終了するかを確認したいときは、拡張機能IDを控えます。chrome-extension://<拡張機能ID>/manifest.jsonを開き、DevToolsのApplicationパネルからService Workersペインを開きます。ここのstart/stopリンクで、service workerを手動で起動・終了させて挙動を確認できます。

content scriptのエラーは、拡張機能の管理ページの「エラー」ボタンには出ません。実行時エラー・console.warn・console.errorだけが管理ページに記録される仕組みで、content scriptはページの中で動くため、対象ページ自体のDevToolsコンソールを開いて確認します。コンソール上部のコンテキスト切り替えドロップダウン(既定は「top」)から該当する拡張機能を選ぶと、その拡張機能のcontent scriptが出したエラーだけを絞り込めます。

よくあるつまずき

  • return trueをreturn 1のような値で済ませる: 非同期で応答するには、リスナーから返すのはtrueというリテラルです。truthyな値では足りず、sendResponseを引数なしで呼ぶと、受け取る側にはnullが届きます
  • devtoolsページがある拡張でPromise応答が効かない: devtoolsページを備えた拡張では、Chrome 148のPromise応答が有効でも使えません。return trueは有効かどうかにかかわらず動くので、DevToolsを拡張する場合はこちらで書きます
  • 動的登録したcontent scriptの実行順を取り違える: 同じ読み込み段階では、静的宣言のcontent scriptが先に注入されます。動的登録などで入れたものはそのあとです。順序に依存する処理は、静的宣言のほうに置きます

Chrome拡張機能を自分で作る話ではなく、Claude公式のChrome拡張の使い方を知りたい場合は別記事があります。Claude Chrome拡張機能でできることを参照してください。ここで扱っているのは、自分で拡張機能を開発する側の手順です。

拡張機能を読み込んだブラウザでのコンソールログ取得やスクリーンショット取得を自動化したい場合は、ブラウザ操作の自動化が参考になります。詳しくはChrome DevTools MCPでClaude Codeのパフォーマンス計測を自動化するで扱っています。Excel・Word向けのアドインなら、manifest検証とサイドロードの手順をClaude CodeでOfficeアドインを開発する手順にまとめています。

まとめ

Manifest V3の拡張機能は、manifest.jsonのmanifest_version・name・versionと、service workerとcontent scriptの役割分担が決まれば形になります。Claude Codeには、この分担とCDN不使用をCLAUDE.mdで先に伝え、manifest.jsonの検証をhooksに載せておくと、読み込み前に誤りを見つけられます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. developer.chrome.com/docs/extensions/get-started
  2. developer.chrome.com/docs/extensions/develop/concepts/service-workers
  3. developer.chrome.com/docs/extensions/develop/concepts/content-scripts
  4. developer.chrome.com/docs/extensions/develop/concepts/messaging
  5. developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3
  6. developer.chrome.com/docs/extensions/reference/manifest
  7. developer.chrome.com/docs/extensions/get-started/tutorial/hello-world
  8. developer.chrome.com/docs/extensions/get-started/tutorial/debug
この記事を共有:XはてブLinkedIn