- Published on
ブラウザ拡張機能開発 2026 — Manifest V3 / Plasmo / WXT / Side Panel API / Safari Web Extensions 徹底ガイド
- Authors

- Name
- Youngju Kim
- @fjvbn20031
プロローグ — 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>
<html>
<body>
<script src="offscreen.js"></script>
</body>
</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
import type { PlasmoCSConfig } from 'plasmo'
import { useState } from 'react'
export const config: PlasmoCSConfig = {
matches: ['https://*.example.com/*'],
}
export default function ContentUI() {
const [count, setCount] = useState(0)
return (
<div style={{ position: 'fixed', top: 10, right: 10, background: 'white', padding: 12 }}>
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
</div>
)
}
これが自動でページに注入されて動く。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
import { defineConfig } from 'wxt'
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
import { defineConfig } from 'vite'
import webExtension from 'vite-plugin-web-extension'
import path from 'path'
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 を返すようにできる。
import browser from 'webextension-polyfill'
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 に拡張機能を載せる
- Microsoft Partner Center に開発者アカウント登録(無料)。
- 同じ .zip パッケージをアップロード。
- レビュー。
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)
import { useState } from '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 (
<div className="flex flex-col h-screen p-4">
<div className="flex-1 overflow-auto">
{messages.map((m, i) => (
<div key={i} className={m.role === 'user' ? 'text-blue-600' : 'text-gray-800'}>
{m.content}
</div>
))}
</div>
<div className="flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && send()}
className="flex-1 border p-2"
/>
<button onClick={send} className="bg-blue-500 text-white px-4">
Send
</button>
</div>
</div>
)
}
// 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 つ。
- BYOK(Bring Your Own Key): ユーザーが自分の API キーを入れる。開発者にトラフィックコストがかからない。
- 無料枠 + 有料: 一定回数まで無料、以降サブスク。
- 純粋 SaaS: 拡張機能はクライアント、認証/課金は自前バックエンド。
- オンデバイス: 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 ポリシー — 通すコツ
よくある却下理由
- 権限過多:
<all_urls>または広範なホスト権限の正当性不足。 - 単一目的違反: 1 拡張機能は明確な単一目的。「複数ユーティリティを束ねた」は却下事由。
- コンテンツポリシー: 広告挿入、検索結果操作、アフィリエイト横取りは strict に禁止。
- ユーザーデータポリシー: データ収集時に privacy policy URL 必須、明示的な開示。
- ソースコード難読化: 意図的な難読化は禁止。minify は OK だが readable でなければならない。
- 決済回避: 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 でヘルパー関数をテスト
import { describe, it, expect, vi } from 'vitest'
import { syncData } from './background'
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
import { test, expect, chromium } from '@playwright/test'
import path from 'path'
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/
- MV2 to MV3 Migration — https://developer.chrome.com/docs/extensions/migrating/
- Service Workers in Extensions — https://developer.chrome.com/docs/extensions/develop/concepts/service-workers
- declarativeNetRequest API — https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest
- Side Panel API — https://developer.chrome.com/docs/extensions/reference/api/sidePanel
- Offscreen Documents API — https://developer.chrome.com/docs/extensions/reference/api/offscreen
- Scripting API — https://developer.chrome.com/docs/extensions/reference/api/scripting
- Chrome Built-in AI — https://developer.chrome.com/docs/ai/built-in
- Plasmo — https://docs.plasmo.com/
- WXT — https://wxt.dev/
- vite-plugin-web-extension — https://github.com/aklinker1/vite-plugin-web-extension
- Mozilla WebExtensions — https://extensionworkshop.com/
- Firefox MV3 — https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/
- 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/
- webextension-polyfill — https://github.com/mozilla/webextension-polyfill
- uBlock Origin Lite — https://github.com/uBlockOrigin/uBOL-home
- Chrome Web Store Developer Program Policies — https://developer.chrome.com/docs/webstore/program-policies/
- PlasmoHQ BPP (Browser Plugin Publisher) — https://github.com/PlasmoHQ/bpp
- Yomitan (formerly Yomichan) — https://github.com/yomidevs/yomitan