필사 모드: ブラウザ拡張機能開発 2026 — Manifest V3 / Plasmo / WXT / Side Panel API / Safari Web Extensions 徹底ガイド
日本語プロローグ — MV3 はついに本物になった
ブラウザ拡張機能の開発者は、5 年間「Manifest V3 はもうすぐ強制されますよ」と言われ続けた。毎回延期された。2022 年 1 月。2023 年 1 月。2024 年 1 月。そして 2024 年 6 月、Chrome はついにその約束を守った。
2024 年 6 月以降、Chrome 安定版で MV2 拡張機能は無効化された。Chrome Web Store への MV2 新規登録は、その 1 年前にすでに塞がれていた。2025 年にはエンタープライズ向けの逃げ道(`ExtensionManifestV2Availability` ポリシー)も段階的に廃止され、2026 年 5 月時点で MV2 は事実上、博物館行きの過去のものだ。
この変化の重みは、単に「マニフェストのバージョンが上がった」だけではない。
- **バックグラウンドページはサービスワーカーになった。** 常駐していたページが、イベントに応じて眠ったり目を覚ましたりするワーカーになった。
- **blocking webRequest は declarativeNetRequest に置き換わった。** uBlock Origin は Chrome から去り、代わりに uBlock Origin Lite が登場した。
- **ホスト権限はランタイムオプトインがデフォルトになった。** 「すべてのサイトへのアクセス」はもはや、インストール時に自動承認されない。
- **Side Panel API・Offscreen Documents API・Scripting API・declarativeNetRequest** — これらの新 API は、バックグラウンドページ時代には不可能だったことを可能にする。
同時に、拡張機能の開発ツールも進化した。**Plasmo** は React を一等市民として迎え入れ、**WXT** は Vite ベースで軽くて速い代替を持ち込んだ。**vite-plugin-web-extension** はもっとミニマルな道を提供する。本稿はその全体像を見ていく。
第 1 章 · 2026 年のブラウザ拡張機能 — MV3 時代の風景
まずは全体像から。
Browser Extension API
|
+-------------------+-------------------+
| | |
Chrome (Blink) Firefox (Gecko) Safari (WebKit)
| | |
manifest_version: 3 manifest_version: 3 manifest_version: 3
service_worker background.scripts background.scripts
(event page) (persistent: false)
| | |
declarativeNetRequest webRequest blocking declarativeNetRequest
(MV2 互換を維持) (限定的な webRequest)
| | |
chrome.* namespace browser.* + chrome.* browser.* (Promise)
Side Panel API sidebarAction 一部未サポート
Offscreen Documents backgroundScripts 一部未サポート
この 1 枚の図が、2026 年の拡張機能開発の本質を捉えている。
- **Chrome(および Edge・Brave・Opera のような Chromium 派生)**: MV3 のみ。blocking webRequest なし。
- **Firefox**: MV3 をサポートしつつ、blocking webRequest を一部維持(特に広告ブロッカーのため)。バックグラウンドはイベントページモデル(休眠/起動)。
- **Safari**: WebKit ベース。iOS/iPadOS でも拡張機能が動く(Safari 15+、iOS 15+)。一部 API は未サポート。
3 ブラウザは **WebExtensions** 標準(W3C Browser Extensions Community Group)を共有するが、細部で分岐する。「一度書いてどこでも動く」のスローガンは半分しか真実ではない。
拡張機能の 4 大コンポーネント
+----------------------------------------------------+
| 拡張機能パッケージ (.crx / .xpi / .pkg) |
+----------------------------------------------------+
| manifest.json (メタデータ + 権限) |
| |
| +-----------------+ +-----------------+ |
| | Service Worker | | Content Script | |
| | (background) |<-->| (ページに注入) | |
| +-----------------+ +-----------------+ |
| ^ ^ |
| | | |
| v v |
| +-----------------+ +-----------------+ |
| | Popup / UI | | Side Panel | |
| | (browser_action)| | (Chrome 114+) | |
| +-----------------+ +-----------------+ |
| |
| Offscreen Documents (DOM が必要な作業に) |
+----------------------------------------------------+
各コンポーネントは別々の実行コンテキスト。通信はメッセージパッシング(`chrome.runtime.sendMessage` 等)で行う。
第 2 章 · Manifest V3 — 2024 年 6 月の強制適用以降
最も基本的な manifest.json(Chrome MV3)
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"description": "A simple MV3 extension",
"icons": {
"16": "icons/16.png",
"48": "icons/48.png",
"128": "icons/128.png"
},
"action": {
"default_popup": "popup.html",
"default_icon": "icons/16.png"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"js": ["content.js"]
}
],
"permissions": ["storage", "tabs", "sidePanel"],
"host_permissions": ["https://api.example.com/*"],
"side_panel": {
"default_path": "sidepanel.html"
}
}
MV2 から MV3 への主要な差分
| 項目 | MV2 | MV3 |
| --- | --- | --- |
| Background | 常駐ページ / イベントページ | Service Worker(Firefox はイベントページ) |
| webRequest blocking | 可能 | declarativeNetRequest に置換(Firefox は一部維持) |
| ホスト権限 | インストール時に自動付与 | ランタイムオプトイン可能(`activeTab`/`optional_host_permissions`) |
| リモートコード | 許可 | 禁止(すべての JS をパッケージに同梱) |
| Content Security Policy | オブジェクト形式 | より厳格なオブジェクト形式 |
| Action API | `browser_action` + `page_action` | 統合された `action` |
| Promise API | 一部のみ | すべての chrome.* API が Promise 対応 |
リモートコード禁止は大きな変化だ。広告ブロッカーがリモートのフィルタリストを動的に評価できなくなり、分析・実験ツールがクラウドから JS を取得して実行するパターンはすべて書き直しを迫られた。
CRX フォーマット — 拡張機能はどう梱包されるか
Chrome 拡張機能は `.crx` ファイルとして配布される。これは本質的に **ヘッダ付きの ZIP ファイル**だ。
[CRX3 Magic: "Cr24"] [Version: 3] [Header Length] [Header (ProtoBuf)]
+- RSA 署名(開発者鍵)
+- ECDSA 署名(Google またはセルフ)
[拡張機能ファイルの ZIP アーカイブ]
署名鍵は拡張機能の **ID を決定する**。同じ鍵で署名すれば同じ ID、別の鍵なら別の ID。Web Store に登録すれば、Google がその鍵を管理する。
第 3 章 · Service Worker — バックグラウンドページの後継
MV3 の核心は、バックグラウンドコンテキストのモデルが変わったことだ。
Service Worker のライフサイクル
[イベント発生] -> [ワーカー起動] -> [ハンドラ実行] -> [アイドル検出]
|
v
[30 秒後に終了]
Service Worker は **休眠できる**。約 30 秒のアイドルでブラウザが終了させる。次のイベントで再び起きる。
これが意味すること:
- **グローバル変数に状態を持てない。** ワーカーが死ぬと消える。`chrome.storage.session` や `chrome.storage.local` を使う。
- **タイマーは生き残らない。** `setTimeout`/`setInterval` の代わりに `chrome.alarms` を使う。
- **WebSocket を永続的に保てない。** 切断される。必要なら Offscreen Document で保持する。
- **DOM がない。** ワーカーコンテキストでは DOM API が使えない。パースが必要なら Offscreen Document。
典型的な Service Worker パターン
// background.ts
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed')
chrome.storage.local.set({ installedAt: Date.now() })
})
chrome.action.onClicked.addListener(async (tab) => {
if (!tab.id) return
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => alert('Hello from extension!'),
})
})
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'PING') {
sendResponse({ type: 'PONG', timestamp: Date.now() })
}
return true // 非同期応答のため
})
// 定期作業は alarms で
chrome.alarms.create('hourly-sync', { periodInMinutes: 60 })
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'hourly-sync') {
syncData()
}
})
async function syncData() {
const res = await fetch('https://api.example.com/sync')
const data = await res.json()
await chrome.storage.local.set({ lastSync: data })
}
keepAlive アンチパターン
初期の頃、ワーカー終了に戸惑った開発者は `setInterval(() => fetch('/ping'), 20000)` でワーカーを強制的に生かそうとした。2026 年には明らかなアンチパターンだ。Chrome はそうしたワーカーも結局終了させるし、ポリシー上も推奨されない。正攻法は **状態を storage に保存し、イベントごとに起きて処理する**ことだ。
第 4 章 · declarativeNetRequest — 広告ブロックへの影響
MV3 で最も議論を呼んだ変更。blocking webRequest を declarativeNetRequest(DNR)に置き換え。
なぜ変えたのか
Chrome チームの公式見解: **性能とプライバシー**。blocking webRequest はすべてのネットワークリクエストを拡張機能ワーカーに同期評価のためルーティングしていた。それが遅く、拡張機能が全トラフィックを覗ける権限が広すぎた。
反対派の見解: その権力を拡張機能から奪い **宣言的ルール**に閉じ込めたということは、複雑な広告ブロックロジック(特にコスメティックフィルタや動的ブロック)が弱体化したという意味だ。
DNR の基本
{
"manifest_version": 3,
"name": "Simple Ad Blocker",
"version": "1.0.0",
"permissions": ["declarativeNetRequest"],
"host_permissions": ["<all_urls>"],
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}
]
}
}
[
{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "||doubleclick.net^",
"resourceTypes": ["script", "image", "sub_frame"]
}
},
{
"id": 2,
"priority": 1,
"action": { "type": "redirect", "redirect": { "url": "https://example.com/blocked" } },
"condition": {
"urlFilter": "||tracker.com^",
"resourceTypes": ["main_frame"]
}
}
]
ルールは **宣言的**。拡張機能はリクエストごとに判断せず、ブラウザがルールセットを参照して決める。
制約
- **静的ルールの上限**: 単一拡張機能で 30,000 個。有効化されたすべての拡張機能の合計で 330,000 個。uBlock Origin が使っていた 100 万件超の EasyList ルールは全部入らない。
- **動的ルール**: 5,000 個。ユーザーが追加できるルール。
- **セッションルール**: 5,000 個。ブラウザ終了時に消える。
- **正規表現ルール**: マッチ時間制限が厳格。
uBlock Origin の作者 Raymond Hill は、Chrome では **uBlock Origin Lite** という MV3 互換版を別途運用しているが、「MV2 版 uBO ほどの性能は出せない」と明記している。Firefox では今もフル機能の uBO が動く(Mozilla が blocking webRequest を維持する選択をした)。
第 5 章 · Side Panel API(Chrome 114+)
2023 年 6 月の Chrome 114 で導入された Side Panel API は、拡張機能の UX を根本的に変えた。ポップアップがクリックで閉じる揮発性 UI だとすると、Side Panel は **ブラウザ横に永続する UI** だ。
基本
{
"manifest_version": 3,
"name": "Side Panel Demo",
"version": "1.0.0",
"permissions": ["sidePanel"],
"side_panel": {
"default_path": "sidepanel.html"
},
"action": {
"default_title": "Open Side Panel"
}
}
// background.ts
chrome.runtime.onInstalled.addListener(() => {
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((error) => console.error(error))
})
// 特定のタブだけサイドパネルを有効化
chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
if (!tab.url) return
const url = new URL(tab.url)
if (url.origin === 'https://www.example.com') {
await chrome.sidePanel.setOptions({
tabId,
path: 'sidepanel-example.html',
enabled: true,
})
} else {
await chrome.sidePanel.setOptions({
tabId,
enabled: false,
})
}
})
Side Panel の強み
- **永続性**: タブを切り替えても同じパネルが残る(タブ別にすることも可能)。
- **広さ**: ポップアップは 800x600 程度が限界だが、サイドパネルはもっと広い。
- **対話性**: ユーザーが作業を続ける間、パネルが見え続ける。
- **AI アシスタントとの相性**: チャット UI がサイドパネルに自然に収まる。
Edge もサイドパネルをサポート(Edge の Copilot がまさにこの構造)。Firefox は MV2 時代から `sidebarAction` という似た API を持っていて、MV3 でも維持。Safari は一部バージョンでサポート。
第 6 章 · Offscreen Documents + Scripting API
Offscreen Documents — DOM が必要なとき
Service Worker には DOM がない。だが拡張機能はときどき本当に DOM が必要だ — HTML パース、クリップボードアクセス、`<audio>` 再生、`DOMParser`、WebRTC など。
解決策: **Offscreen Document**。ユーザーに見えない HTML ページをバックグラウンドで立ち上げられる。
{
"permissions": ["offscreen"]
}
// background.ts
async function ensureOffscreenDocument() {
const existing = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [chrome.runtime.getURL('offscreen.html')],
})
if (existing.length > 0) return
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DOM_PARSER'],
justification: 'Parse HTML for content extraction',
})
}
chrome.action.onClicked.addListener(async () => {
await ensureOffscreenDocument()
const response = await chrome.runtime.sendMessage({
target: 'offscreen',
type: 'PARSE_HTML',
data: '<html>...</html>',
})
console.log('parsed:', response)
})
<!-- offscreen.html -->
<!doctype html>
// offscreen.ts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.target !== 'offscreen') return
if (message.type === 'PARSE_HTML') {
const parser = new DOMParser()
const doc = parser.parseFromString(message.data, 'text/html')
const title = doc.querySelector('title')?.textContent ?? ''
sendResponse({ title })
}
})
`reasons` は固定 enum から選ぶ: `AUDIO_PLAYBACK`、`CLIPBOARD`、`DOM_PARSER`、`DOM_SCRAPING`、`IFRAME_SCRIPTING`、`LOCAL_STORAGE`、`TESTING`、`USER_MEDIA`、`WEB_RTC`、`BLOBS`、`WORKERS`。
Scripting API — コンテンツスクリプトの未来
MV2 の `chrome.tabs.executeScript` は MV3 の `chrome.scripting.executeScript` に置き換わった。
// 関数の注入
await chrome.scripting.executeScript({
target: { tabId: 123 },
func: (color) => {
document.body.style.backgroundColor = color
},
args: ['lightblue'],
})
// ファイルの注入
await chrome.scripting.executeScript({
target: { tabId: 123, allFrames: true },
files: ['content.js'],
})
// CSS の挿入
await chrome.scripting.insertCSS({
target: { tabId: 123 },
css: 'body { filter: invert(1); }',
})
// コンテンツスクリプトの動的登録
await chrome.scripting.registerContentScripts([
{
id: 'dynamic-script',
matches: ['https://*.example.com/*'],
js: ['dynamic.js'],
runAt: 'document_idle',
},
])
`scripting` 権限 + ホスト権限(または `activeTab`)の組み合わせが必要。
第 7 章 · Plasmo — React ベースのフレームワーク
伝統的な拡張機能開発は、manifest を手書きし、webpack/rollup を設定し、ホットリロードを自分で組み上げる… 退屈だった。Plasmo はそれら全部を **Next.js のように** ほどく。
30 秒セットアップ
pnpm create plasmo my-extension
cd my-extension
pnpm dev
my-extension/
├── popup.tsx # 自動でポップアップになる
├── options.tsx # 自動でオプションページ
├── background.ts # 自動でサービスワーカー
├── contents/
│ └── plasmo.ts # コンテンツスクリプト
├── sidepanel.tsx # サイドパネル
└── package.json # マニフェストの一部
manifest.json を直接書く必要はない。ファイル名とディレクトリの規約から決まる。メタデータは package.json に書く。
{
"name": "my-extension",
"displayName": "My Extension",
"version": "1.0.0",
"description": "...",
"manifest": {
"permissions": ["storage", "tabs"],
"host_permissions": ["https://*.example.com/*"]
}
}
Plasmo の強み
- **React 一等市民**: `popup.tsx` がそのまま React コンポーネント。
- **HMR**: 拡張機能のコードを編集すると自動リロード。
- **Content Script UI**: `contents/` に React コンポーネントを置けばページに注入される UI を React で書ける。
- **Storage Hook**: `@plasmohq/storage` の `useStorage` フック。
- **Messaging**: 型安全なメッセージング。
- **クロスブラウザ**: Chrome / Firefox / Edge ビルドを分離。
Content Script UI の例
// contents/inline.tsx
export const config: PlasmoCSConfig = {
matches: ['https://*.example.com/*'],
}
export default function ContentUI() {
const [count, setCount] = useState(0)
return (
)
}
これが自動でページに注入されて動く。Shadow DOM 隔離もオプションで可能。
Plasmo の弱み
- 抽象が厚いので、manifest を細かく制御したいときに不自由なときがある。
- ビルド成果物が重くなる傾向(React ランタイム同梱)。
- かつて活発だった BrowserStack(親会社)のサポートが減ったとの観測。とはいえ OSS コミュニティは健在。
第 8 章 · WXT — Vite ベースの新鋭
Plasmo が「React/Next.js」アナロジーなら、**WXT** は「Vite/Nuxt」のそれだ。より軽く、より速く、より自由。2023 年の登場以降、2025-2026 年に急成長。
基本構造
pnpm dlx wxt@latest init my-extension
cd my-extension
pnpm dev
my-extension/
├── entrypoints/
│ ├── background.ts
│ ├── content.ts
│ ├── popup/
│ │ └── index.html
│ └── options/
├── wxt.config.ts
└── package.json
// wxt.config.ts
export default defineConfig({
manifest: {
permissions: ['storage', 'tabs'],
host_permissions: ['https://*.example.com/*'],
},
modules: ['@wxt-dev/module-react'],
})
// entrypoints/background.ts
export default defineBackground(() => {
console.log('Hello from background!')
browser.runtime.onMessage.addListener((msg) => {
return Promise.resolve({ pong: true })
})
})
WXT の強み
- **Vite ベース**: ビルドが速く、ESM フレンドリー。
- **フレームワーク非依存**: React モジュール、Vue モジュール、Svelte もある。
- **自動 import**: Nuxt スタイルの自動 imports — `browser`、`defineBackground`、`defineContentScript` がグローバルとして自動で入ってくる。
- **クロスブラウザ**: 一度書いて Chrome/Firefox/Safari ビルドを分離。
- **Web-ext 統合**: 開発モードでブラウザを自動起動しホットリロード。
WXT vs Plasmo 一行比較
| 項目 | Plasmo | WXT |
| --- | --- | --- |
| 基盤 | Parcel/Next.js 風 | Vite/Nuxt 風 |
| フレームワーク | React 中心(Vue/Svelte も可) | モジュールで対等 |
| Manifest 制御 | 規約優先 | 直接記述可能 |
| 学習曲線 | 低(規約に従えばよい) | 中(設定は明示的) |
| コミュニティ | 2022-2024 活発 → 停滞 | 2024-2026 急成長 |
| 推奨シナリオ | React 中心の素早いプロトタイプ | 長期運用・多フレームワーク・精密制御 |
2026 年時点で新規スタートなら **WXT が優勢**というのが多数意見。Plasmo もまだ良いが、勢いは WXT に移った。
第 9 章 · vite-plugin-web-extension — ミニマル選択肢
フレームワークの重さが負担なら、**vite-plugin-web-extension** が答えかもしれない。Vite プラグイン 1 つで拡張機能のビルドを解決する。
pnpm add -D vite vite-plugin-web-extension
// vite.config.ts
export default defineConfig({
plugins: [
webExtension({
manifest: path.resolve('manifest.json'),
additionalInputs: {
html: ['sidepanel.html'],
},
}),
],
})
`manifest.json` を直接書き、Vite がエントリを自動発見してビルドする。規約による自動化はないが、**Vite のすべてをそのまま使える。**
選択基準
- 「フレームワーク依存なしで軽く」→ vite-plugin-web-extension
- 「Vite エコシステム + ある程度の自動化」→ WXT
- 「React 中心で素早く」→ Plasmo
- 「フレームワーク不使用の素のまま」→ webpack/Rollup 直接(現在は非推奨)
第 10 章 · Firefox MV3 — 一部 MV2 を維持
Mozilla は Chrome の MV3 推進に微妙な反対の立場だった。結果として Firefox MV3 は Chrome MV3 に似ているが、部分的に異なる。
Firefox MV3 の差別点
- **Background**: Service Worker ではなく **イベントページ**モデル。休眠可能だが DOM を持てる。
- **blocking webRequest**: 維持。Firefox で uBlock Origin がフル機能で動く理由。
- **ホスト権限**: Chrome 同様、ランタイムオプトイン可能。
- **API 名前空間**: `browser.*`(Promise ベース)が標準。`chrome.*` も互換のため動く。
同じコードを両方で動かす
// 共通ヘルパー
const api = (typeof browser !== 'undefined' ? browser : chrome) as typeof browser
async function getCookie(url: string, name: string) {
return api.cookies.get({ url, name })
}
`webextension-polyfill` ライブラリを使えば `chrome.*` も Promise を返すようにできる。
await browser.storage.local.set({ key: 'value' })
Plasmo と WXT はこの polyfill または同等の抽象を内蔵している。
manifest の差分
Firefox は `manifest.json` に `browser_specific_settings` を要求することがある。
{
"manifest_version": 3,
"name": "Cross-Browser Ext",
"version": "1.0.0",
"background": {
"scripts": ["background.js"],
"type": "module"
},
"browser_specific_settings": {
"gecko": {
"id": "myext@example.com",
"strict_min_version": "115.0"
}
}
}
WXT はこの分岐を自動で処理してくれる。
第 11 章 · Safari Web Extensions — Mac/iOS
Apple は 2020 年の Safari 14 で Web Extensions を受け入れた。2021 年の iOS 15 から **iPhone/iPad でも** 拡張機能が可能になった。これは小さな出来事ではない — モバイル拡張機能エコシステムの事実上初の例だ。
Safari Web Extension の特徴
- **Xcode プロジェクトでラップ**: Web 拡張機能コードを Xcode アプリコンテナに入れてビルド。App Store 経由で配布。
- **`browser.*` API を使用**: Mozilla 互換の名前空間。
- **一部 API は未サポート**: `chrome.identity`、`chrome.sidePanel`、`chrome.declarativeNetRequest` の一部、`chrome.offscreen` などが未対応/制限。
- **iOS 制約**: モバイルでは権限モデルがより厳格。ユーザーの明示的な有効化が必要。
変換ツール — safari-web-extension-converter
既存の Chrome/Firefox 拡張機能を Safari に移植するとき:
xcrun safari-web-extension-converter /path/to/extension
このコマンドが Xcode プロジェクトの足場を作る。manifest を読み、互換のない API を警告で知らせる。
配布
- **macOS**: Safari Extensions Gallery は 2022 年に閉鎖。今はすべての Safari 拡張機能が **App Store** 経由で配布。
- **iOS**: 同じく App Store。
- **Apple Developer Program** メンバーシップが必要(年 $99)。
この参入障壁が、Safari 拡張機能のエコシステムが Chrome ほど豊かでない理由。だが iOS で拡張機能が可能な唯一の道なので、ますます重要になる。
第 12 章 · Edge Add-ons — Microsoft
Edge は Chromium ベースなので Chrome 拡張機能がほぼそのまま動く。だが Microsoft は **Edge Add-ons** という独自ストアを運営している。
Edge に拡張機能を載せる
1. Microsoft Partner Center に開発者アカウント登録(無料)。
2. 同じ .zip パッケージをアップロード。
3. レビュー。
Chrome 拡張機能との互換性
- **ほぼ 100%**: Chrome MV3 拡張機能は Edge でほぼそのまま動く。
- **Edge 特化 API**: 一部の `chrome.*` の代わりに `edge.*` として公開される API があるが、大部分は同じ。
- **サイドバー**: Edge は Sidebar API を積極活用(Copilot 等)。
Edge でだけ可能なこと
- **強制インストールポリシー**: エンタープライズ環境で IT 管理者が強制配布可能。
- **Bing Chat / Copilot 統合**: マイクロソフトのサービスとの統合 API。
- **Edge Workspaces**: ワークスペースに紐づく拡張機能。
Edge のシェアは世界で 5-7% 程度と小さく見えるが、**エンタープライズ環境では Chrome に劣らず重要**。B2B 拡張機能なら Edge ストア登録は必須。
第 13 章 · AI 連携拡張機能 — Side Panel + LLM
2026 年の拡張機能開発で最も興味深い流れは **AI 連携**だ。ChatGPT ブラウザ拡張機能、Perplexity、Glasp、Compose AI、Wisedocs といったツールがみな同じパターンを使う。
典型的な AI 拡張機能アーキテクチャ
[Web Page]
| (ユーザーアクション: テキスト選択、ページ要約リクエスト)
v
[Content Script] -- ページコンテキスト抽出 -->
|
v
[Service Worker] -- LLM API 呼び出し(Claude / OpenAI / 自前) -->
|
v
[Side Panel] -- 結果をストリーミング表示 -->
|
v
[ユーザー]
Side Panel + ストリーミング UI
// sidepanel/App.tsx (WXT + React)
export default function SidePanelApp() {
const [messages, setMessages] = useState<{ role: 'user' | 'assistant'; content: string }[]>([])
const [input, setInput] = useState('')
async function send() {
const userMsg = { role: 'user' as const, content: input }
setMessages((m) => [...m, userMsg])
setInput('')
const response = await chrome.runtime.sendMessage({
type: 'ASK_LLM',
messages: [...messages, userMsg],
})
setMessages((m) => [...m, { role: 'assistant', content: response.text }])
}
return (
{messages.map((m, i) => (
{m.content}
))}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && send()}
className="flex-1 border p-2"
/>
Send
)
}
// background.ts
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'ASK_LLM') {
callLLM(msg.messages).then((text) => sendResponse({ text }))
return true // async
}
})
async function callLLM(messages: any[]) {
const apiKey = (await chrome.storage.local.get('apiKey')).apiKey
const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-opus-4-7',
max_tokens: 1024,
messages,
}),
})
const data = await res.json()
return data.content[0].text
}
ページコンテキストの活用
// content.ts
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'GET_PAGE_TEXT') {
sendResponse({
title: document.title,
url: location.href,
text: document.body.innerText.slice(0, 50000),
})
}
})
// sidepanel: 現在のタブのテキストを取得して要約リクエスト
async function summarizeCurrentPage() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
if (!tab.id) return
const pageData = await chrome.tabs.sendMessage(tab.id, { type: 'GET_PAGE_TEXT' })
// ... LLM 呼び出し
}
Chrome Built-in AI(Gemini Nano)
2024 年から Chrome は **オンデバイス Gemini Nano** を拡張機能で使えるよう提供を開始した(Origin Trial → GA)。`chrome.ai` という名前空間が段階的に解放されており、2026 年 5 月時点で一部 API(prompt、summarize、translate)が安定チャネル。
// 可用性確認
const availability = await ai.languageModel.availability()
if (availability === 'available') {
const session = await ai.languageModel.create()
const response = await session.prompt('Summarize: ...')
}
長所: ローカル実行、無料、プライバシー。短所: モデルが小さいので精緻な作業には限界。
ビジネスモデル
AI 拡張機能のよくあるビジネスモデル 4 つ。
1. **BYOK(Bring Your Own Key)**: ユーザーが自分の API キーを入れる。開発者にトラフィックコストがかからない。
2. **無料枠 + 有料**: 一定回数まで無料、以降サブスク。
3. **純粋 SaaS**: 拡張機能はクライアント、認証/課金は自前バックエンド。
4. **オンデバイス**: Chrome Built-in AI または自前の量子化モデルでコスト 0。
第 14 章 · 韓国 / 日本 — Kakao、Naver、pixiv
韓国
- **Kakao 拡張機能**: KakaoTalk シェア、Daum 検索、Kakao Bank 認証など Kakao エコシステムと連携するユーティリティ拡張機能が多い。Kakao は公式に拡張機能 SDK を出してはいないが、Kakao API と OAuth を拡張機能から使う事例が豊富。
- **Naver 拡張機能**: Naver 検索結果の強化、Naver Cloud、Naver 辞書、Naver Shopping 価格比較(Signal、Danawa のような比較ショッピング拡張機能)が代表的。
- **Toss / Karrot**: 決済・中古取引領域で社内向け拡張機能を運用する事例が多い。
- **翻訳器**: Papago、Naver 翻訳の拡張機能。モバイル優勢の韓国で、デスクトップ翻訳は今も有効な市場。
- **広告ブロック**: AdBlock Plus の EasyList Korea が活発。
日本
- **pixiv 拡張機能**: pixiv 作品ダウンローダ、タグ強化、イラストビューア補助といった非公式 OSS が多数。ただし pixiv ToS と衝突する可能性が常にあるので慎重に。
- **Niconico 拡張機能**: 動画ダウンロード補助、コメント強化、ビューア改善など。
- **Mercari / Rakuma**: 中古マーケットプレイスの価格追跡拡張機能。
- **Rakuten 拡張機能**: 楽天ポイントの自動計算、クーポン自動適用。
- **日本語学習**: Rikaikun、10ten Japanese Reader、Yomichan(現在は Yomitan としてフォーク) — 日本語学習者には事実上必携。
- **AdGuard**: 日本語 EasyList Japan のメンテナンスが活発。
Kakao/Naver OAuth in 拡張機能
// chrome.identity.launchWebAuthFlow による OAuth
async function loginWithKakao() {
const clientId = 'YOUR_KAKAO_REST_API_KEY'
const redirectUri = chrome.identity.getRedirectURL()
const authUrl =
`https://kauth.kakao.com/oauth/authorize?` +
`client_id=${clientId}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`response_type=code`
const responseUrl = await chrome.identity.launchWebAuthFlow({
url: authUrl,
interactive: true,
})
const code = new URL(responseUrl!).searchParams.get('code')
// code をバックエンドに送ってトークン交換
return code
}
`chrome.identity.getRedirectURL()` が返す URL を Kakao 開発者コンソールの Redirect URI に登録する必要がある。形式は `https://<extension-id>.chromiumapp.org/`。
韓国・日本の拡張機能開発のリアル
- 決済モジュール(Kakao Pay、Naver Pay、PayPay)を拡張機能から直接呼ぶのはほぼ不可能。バックエンド経由が必須。
- インアプリブラウザ(KakaoTalk、Line の内部ブラウザ)は拡張機能をサポートしない。モバイルで拡張機能を狙うなら iOS の Safari Web Extension がほぼ唯一の選択肢。
- 日本は保守的な市場なので、拡張機能の採用率は韓国・米国より低い傾向。代わりに一度入れると長く使う。
第 15 章 · 誰が何を選ぶべきか — 個人 / チーム / クロスブラウザ / AI 連携
個人開発者 + 素早いプロトタイプ
- **推奨スタック**: Plasmo または WXT。
- **理由**: HMR、規約、ボイラープレート最小化。1 週間以内に使える拡張機能を出せる。
- **ターゲットブラウザ**: Chrome 優先、うまくいけば Firefox/Edge へ拡張。
- **注意点**: Web Store レビュー時間(平均 1-7 日)を見込む。
小規模チーム + 長期プロダクト
- **推奨スタック**: WXT。
- **理由**: Vite ベースでビルドが速く、TS フレンドリー、マルチブラウザサポートが自然。
- **推奨**: Storybook や Chromatic で UI 回帰テスト。E2E は Playwright の拡張機能モード(BrowserContext に拡張機能をロード)。
クロスブラウザ優先
- **推奨スタック**: WXT + `webextension-polyfill`。
- **ビルドマトリクス**: Chrome MV3、Firefox MV3、Edge MV3、Safari(別 Xcode プロジェクト)。
- **注意点**: declarativeNetRequest 使用時は Firefox では webRequest にフォールバック分岐が必要なことがある。
AI 連携拡張機能
- **UI**: Side Panel 優先。ポップアップは短いアクション専用。
- **LLM**: BYOK モデルから始めて、ユーザー数が増えたら自前バックエンドを検討。
- **オンデバイス**: Chrome Built-in AI(Gemini Nano)も選択肢。無料という点が強い。
- **コンテキスト抽出**: content script でページテキスト + メタデータを取得 → Service Worker から LLM 呼び出し → Side Panel でストリーミング表示。
広告ブロック / コンテンツフィルタリング
- **MV3 Chrome**: DNR の上限内で可能。フル機能は難しい。
- **Firefox**: blocking webRequest でフル機能可能。uBlock Origin が Firefox でまだ生きている理由。
- **推奨**: Firefox をメインターゲット、Chrome は Lite 版で分岐。
エンタープライズ / B2B
- **Chrome**: エンタープライズポリシーで強制配布可能(`force_install`)。
- **Edge**: 同じ方式。Microsoft 365 環境で自然。
- **推奨**: 会社ドメインの SAML SSO 連携が可能なバックエンドを構築。拡張機能はクライアント役。
第 16 章 · 拡張機能セキュリティ — 何に気をつけるか
ホスト権限は最小に
{
"permissions": ["activeTab"],
"optional_host_permissions": ["https://*.example.com/*"]
}
`<all_urls>` はレビューで strict に見られる。`activeTab` を優先し、広範な権限が本当に必要なら `optional_host_permissions` でランタイム要求。
CSP を厳格に
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}
`unsafe-eval`、`unsafe-inline` は MV3 で禁止。外部 CDN スクリプトも禁止(すべての JS をパッケージに同梱しなければならない)。
externally_connectable
Web ページが拡張機能にメッセージを送れるようにするには:
{
"externally_connectable": {
"matches": ["https://yoursite.com/*"]
}
}
これを通じて自社サイトが拡張機能と通信するパターンが可能だが、ドメインを狭く取らないとセキュリティホールになる。
XSS 防御
- content script でページ DOM に何かを注入する際は `textContent` を使い、`innerHTML` は避ける。
- React を使えば基本的に安全だが、`dangerouslySetInnerHTML` は慎重に。
- Trusted Types の適用を検討。
個人情報
- API キーは `chrome.storage.local` に保存しつつ、可能なら BYOK またはバックエンド経由のトークン交換。
- ページコンテンツを外部に送るときはユーザーに明示的に伝え、オプトインさせる。
- Chrome Web Store のデータ使用開示ポリシーを必ず守る — 違反すると拡張機能がブロックされる。
第 17 章 · Chrome Web Store ポリシー — 通すコツ
よくある却下理由
1. **権限過多**: `<all_urls>` または広範なホスト権限の正当性不足。
2. **単一目的違反**: 1 拡張機能は明確な単一目的。「複数ユーティリティを束ねた」は却下事由。
3. **コンテンツポリシー**: 広告挿入、検索結果操作、アフィリエイト横取りは strict に禁止。
4. **ユーザーデータポリシー**: データ収集時に privacy policy URL 必須、明示的な開示。
5. **ソースコード難読化**: 意図的な難読化は禁止。minify は OK だが readable でなければならない。
6. **決済回避**: Chrome Web Store Payments を使わず外部決済で迂回するのは制限。
レビュー時間
- **新規登録**: 平均 1-7 日、長いと 2-3 週間。
- **更新**: 通常 1-2 日。
- **センシティブな権限(`<all_urls>`、`tabs`、`downloads`)があると延びる**。
ベータチャネル活用
`unlisted` または `private` 配布でベータテスターに先に配布した後、public に切り替え。レビューはもう一度通る。
ユーザーデータ開示(Privacy Practices)
Chrome Web Store は次を明示的に開示するよう要求:
- どんなユーザーデータを扱うか
- なぜ扱うか
- どう保護するか
- どこに送信するか
虚偽の開示は即時のブロック事由。
第 18 章 · テストと CI/CD
ユニットテスト
// Vitest でヘルパー関数をテスト
global.chrome = {
storage: {
local: {
set: vi.fn(),
get: vi.fn().mockResolvedValue({}),
},
},
alarms: {
create: vi.fn(),
onAlarm: { addListener: vi.fn() },
},
} as any
describe('syncData', () => {
it('saves data to storage', async () => {
await syncData()
expect(chrome.storage.local.set).toHaveBeenCalled()
})
})
E2E — Playwright with Extension
test('extension loads', async () => {
const pathToExtension = path.resolve(__dirname, '../.output/chrome-mv3')
const context = await chromium.launchPersistentContext('', {
headless: false,
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
})
const page = await context.newPage()
await page.goto('https://example.com')
// content script が注入されたか確認
const injected = await page.evaluate(() => (window as any).__MY_EXT_LOADED__)
expect(injected).toBe(true)
await context.close()
})
CI/CD
.github/workflows/release.yml
name: Build and Release
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
- run: pnpm build --target chrome-mv3
- run: pnpm build --target firefox-mv3
- uses: actions/upload-artifact@v4
with:
name: extensions
path: .output/
publish-chrome:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- name: Upload to Chrome Web Store
uses: PlasmoHQ/bpp@v3
with:
keys: $TEST_KEYS_PLACEHOLDER
chrome-file: extensions/chrome-mv3.zip
`PlasmoHQ/bpp`(Browser Plugin Publisher)アクションが Chrome/Firefox/Edge への自動アップロードをサポート。
第 19 章 · よくある落とし穴
「Service Worker が死んで作業が途切れる」
- 原因: 30 秒のアイドル後に終了。
- 対処: 状態を `chrome.storage` に保存。長時間作業は `chrome.alarms` か `chrome.notifications` で進捗表示。
「WebSocket が切れる」
- 原因: Service Worker 終了。
- 対処: Offscreen Document に WebSocket を置き、ワーカーとメッセージパッシング。
「content script からページの変数にアクセスできない」
- 原因: Content Script は **隔離コンテキスト(isolated world)** で実行。ページの `window` は見えない。
- 対処: `chrome.scripting.executeScript` で `world: 'MAIN'` オプション、または script タグ注入。
「Chrome では動くが Firefox では動かない」
- 原因: API 差分(特に DNR 制限、ホスト権限モデル)。
- 対処: WXT/Plasmo のクロスブラウザビルド、`webextension-polyfill`、分岐コード。
「レビューで却下された — 権限が過剰」
- 原因: `<all_urls>` または広範なホスト権限。
- 対処: `activeTab` 優先、本当に必要なドメインだけ明示。
「拡張機能が突然無効化された — ポリシー違反」
- 原因: データ使用開示の欠落、単一目的違反など。
- 対処: Chrome Web Store 開発者ダッシュボードで違反事由を確認、修正後に再提出。
第 20 章 · エピローグ — 拡張機能は死んだのか、生きているのか
2020 年代初頭には「PWA が拡張機能を置き換える」という言説があった。2026 年現在、その予測は半分しか当たっていない。PWA は拡張機能の一部の領域(インストール型 Web アプリ)を取ったが、**ブラウザのすべてのページとインタラクションする能力**は拡張機能だけが持つ。だから拡張機能は死なない。
むしろ MV3 の強制適用と AI の隆盛が **拡張機能のルネサンス**をもたらした。ChatGPT、Claude、Perplexity の拡張機能は数百万ユーザーを集め、毎日新しい AI 補助拡張機能がリリースされる。Side Panel API はチャット UI のためのキャンバスになり、ページコンテキストを自由に LLM に送れることは拡張機能だけの強みだ。
2026 年の拡張機能開発の推奨を一行に圧縮すれば:
> **WXT か Plasmo で始めよ。Manifest V3、Service Worker、Side Panel API を身につけよ。Chrome を最初に、Firefox/Edge は自動ビルドで、Safari は市場があれば別プロジェクトで。AI 連携なら Side Panel + LLM API から。権限は最小に、レビューポリシーは正確に、ユーザーデータ開示は正直に。**
これが 2026 年 5 月時点での正解。1 年後にはまた変わる — それが拡張機能の世界だ。
参考文献 / References
- Chrome Extensions Manifest V3 — [https://developer.chrome.com/docs/extensions/mv3/intro/](https://developer.chrome.com/docs/extensions/mv3/intro/)
- MV2 to MV3 Migration — [https://developer.chrome.com/docs/extensions/migrating/](https://developer.chrome.com/docs/extensions/migrating/)
- Service Workers in Extensions — [https://developer.chrome.com/docs/extensions/develop/concepts/service-workers](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers)
- declarativeNetRequest API — [https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest](https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest)
- Side Panel API — [https://developer.chrome.com/docs/extensions/reference/api/sidePanel](https://developer.chrome.com/docs/extensions/reference/api/sidePanel)
- Offscreen Documents API — [https://developer.chrome.com/docs/extensions/reference/api/offscreen](https://developer.chrome.com/docs/extensions/reference/api/offscreen)
- Scripting API — [https://developer.chrome.com/docs/extensions/reference/api/scripting](https://developer.chrome.com/docs/extensions/reference/api/scripting)
- Chrome Built-in AI — [https://developer.chrome.com/docs/ai/built-in](https://developer.chrome.com/docs/ai/built-in)
- Plasmo — [https://docs.plasmo.com/](https://docs.plasmo.com/)
- WXT — [https://wxt.dev/](https://wxt.dev/)
- vite-plugin-web-extension — [https://github.com/aklinker1/vite-plugin-web-extension](https://github.com/aklinker1/vite-plugin-web-extension)
- Mozilla WebExtensions — [https://extensionworkshop.com/](https://extensionworkshop.com/)
- Firefox MV3 — [https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/](https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/)
- Safari Web Extensions — [https://developer.apple.com/documentation/safariservices/safari_web_extensions](https://developer.apple.com/documentation/safariservices/safari_web_extensions)
- Edge Add-ons — [https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/](https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/)
- webextension-polyfill — [https://github.com/mozilla/webextension-polyfill](https://github.com/mozilla/webextension-polyfill)
- uBlock Origin Lite — [https://github.com/uBlockOrigin/uBOL-home](https://github.com/uBlockOrigin/uBOL-home)
- Chrome Web Store Developer Program Policies — [https://developer.chrome.com/docs/webstore/program-policies/](https://developer.chrome.com/docs/webstore/program-policies/)
- PlasmoHQ BPP (Browser Plugin Publisher) — [https://github.com/PlasmoHQ/bpp](https://github.com/PlasmoHQ/bpp)
- Yomitan (formerly Yomichan) — [https://github.com/yomidevs/yomitan](https://github.com/yomidevs/yomitan)
현재 단락 (1/739)
ブラウザ拡張機能の開発者は、5 年間「Manifest V3 はもうすぐ強制されますよ」と言われ続けた。毎回延期された。2022 年 1 月。2023 年 1 月。2024 年 1 月。そして 202...