自作のChrome拡張を社内やクライアントに配るとき、こんな相談をよく受けます。
「修正版を渡したら、前回の比較結果が消えて、またゼロから比較するはめになった」
機能追加のたびにユーザーが過去データを失うのは、拡張の出来とは別のところにある更新手順の設計ミスです。本記事では、なぜデータが消えるのかをChromeの仕組みから説明し、データを残したまま更新する手順と、その手順を支える実装をまとめます。
環境・前提
- OS: macOS 14 / Windows 11(両方の手順を記載)
- Chrome: 120 以降(Manifest V3)
- 配布形態: 社内・取引先へのUnpacked配布(デベロッパーモードで読み込み)
- 拡張の機能: 対象ページの情報を収集し、前回スナップショットと差分を比較表示
- 使用API:
chrome.storage.local(syncでも同様の話)
なぜ「いったん削除して読み込み直す」とデータが消えるのか
結論から言うと、Chrome拡張のストレージは「拡張ID」に紐づいて保存されるためです。
Unpacked拡張のIDは、読み込んだフォルダの絶対パスから導出されます。パスが同じなら同じID、パスが違えば別IDです。
つまり、
- ユーザーが旧バージョンのフォルダを削除する
- 新しいバージョンを別のフォルダ(例:
my-ext-v2)に置いて読み込む - パスが変わったので拡張IDが変わる
- Chromeは「別の拡張」として扱うため、
chrome.storage.localの前回データは見えなくなる
という流れです。データ自体はプロファイル内に残っていますが、新しいIDからは参照できません。ユーザーから見ると「消えた」ようにしか見えません。
回避策として manifest.json に key を書いてIDを固定する方法もありますが、鍵の管理が必要になるため、社内配布のようなカジュアルな配布には向きません。Web Store配布ならIDは不変なので、この問題自体が起きません。
解決策:上書きコピー+更新ボタン
発想を変えます。フォルダを差し替えるのではなく、中身を上書きするのです。
手順(macOS / Linux)
# ビルド成果物を、ユーザーが読み込んでいるフォルダへ同期する
rsync -a --delete ~/dev/my-ext/dist/ ~/extensions/my-ext/
手順(Windows)
robocopy C:\dev\my-ext\dist C:\extensions\my-ext /MIR
コピー後、ユーザーは chrome://extensions を開き、該当拡張のカードにある更新ボタン(↻)を押すだけです。パスが変わらないため拡張IDは同じまま、chrome.storage.local のデータも保持されます。
注意点
-
--delete//MIRは「コピー先にしかないファイル」を削除します。ストレージは拡張フォルダ内ではなくChromeのプロファイル側にあるので、この削除でデータは消えません。 - ただし、フォルダ内のJSONファイルを
fetch()してデータ代わりにしている実装は消えます。保存は必ずchrome.storageを使ってください。 - 読み込んでいるフォルダのパスをユーザーに変えさせないこと。これが守れないと元の木阿弥です。
手順ごとの比較
| 更新手順 | 拡張ID | 保存データ | 向いている場面 |
|---|---|---|---|
| 削除 → 別フォルダから読み込み | 変わる | 参照不能 | 初回セットアップのみ |
| 削除 → 同じパスに再配置 | 同じ | 残る | 手数が多く事故りやすい |
| 上書きコピー → ↻ | 同じ | 残る | 日常の更新(推奨) |
| Chrome Web Store 経由 | 同じ | 残る | 一般配布 |
同じパスなら削除→再配置でもIDは維持されますが、ユーザーがつい別フォルダに置いてしまうのが現実です。「削除」という言葉自体を使わない運用にしておくのが安全です。
実装側の備え:スキーマ変更に耐えるマイグレーション
更新手順を整えても、保存データの形式を変えると結局壊れます。schemaVersion を持たせて、起動時にマイグレーションを走らせましょう。
const SCHEMA_VERSION = 3;
async function migrate() {
const { schemaVersion = 1, lastSnapshot } =
await chrome.storage.local.get(['schemaVersion', 'lastSnapshot']);
if (schemaVersion >= SCHEMA_VERSION) return;
if (schemaVersion < 2 && Array.isArray(lastSnapshot)) {
// 旧形式: { url, hash }
// 新形式: { url, hash, capturedAt }
const migrated = lastSnapshot.map((row) => ({ ...row, capturedAt: 0 }));
await chrome.storage.local.set({ lastSnapshot: migrated });
}
if (schemaVersion < 3) {
// 例: スナップショットの保持件数を上限200件に切り詰める
if (Array.isArray(lastSnapshot)) {
await chrome.storage.local.set({
lastSnapshot: lastSnapshot.slice(-200),
});
}
}
await chrome.storage.local.set({ schemaVersion: SCHEMA_VERSION });
}
chrome.runtime.onStartup.addListener(migrate);
chrome.runtime.onInstalled.addListener(migrate);
ポイントは、onInstalled だけでなく Service Worker起動時にもチェックすることです。Unpacked拡張を↻で更新した場合、onInstalled が期待どおりに発火しないケースがあり、そのまま古いスキーマで動き続けてしまいます。起動時チェックを入れておけば、どのタイミングで更新されても一度は必ず通ります。
manifest.json 側も忘れずにバージョンを上げておきましょう。
{
"manifest_version": 3,
"name": "Diff Snapshot",
"version": "1.4.0",
"permissions": ["storage", "activeTab"],
"background": { "service_worker": "background.js" }
}
初回納品時に伝えておくこと
データを持つ拡張は、機能と同じ重さで更新手順を設計してください。初回の納品時に以下をチェックリストとして渡しておくと、後の問い合わせが激減します。
- 読み込むフォルダのパスは固定。以降は移動しない
- 更新時は「削除」ではなく「上書きコピー」
- コピー後に
chrome://extensionsで↻を押す - 拡張IDが前回と同じかを目視確認する(変わっていたら過去データは引き継げない)
- データを明示的に消したいときは、拡張側に用意したリセットボタンを使う
FAQ
Q. 消えたように見えたデータを復元できますか?
A. プロファイル内の Local Extension Settings/<旧拡張ID> に残っている可能性はありますが、Chrome内部のLevelDBなので直接触るのは推奨しません。復元を狙うより、更新手順を直して運用で防ぐほうが現実的です。
Q. chrome.storage.sync ならアカウント同期されるので消えませんか?
A. いいえ。sync も拡張IDに紐づくため、IDが変われば別物として扱われます。また同期は非同期で遅延するため、更新直後に読むと空に見えることがあります。
Q. Web Storeに出す予定はありませんが、key を設定すべきですか?
A. 社内配布で鍵管理のコストを払えるなら有効です。ただし key をリポジトリに含めると公開リスクがあるため、CIで注入するなど別の管理が必要になります。上書きコピー運用で足りるなら不要です。
Q. 更新後に「前回データが壊れている」と表示されます。
A. マイグレーション漏れの典型です。schemaVersion を導入し、Service Worker起動時に必ず走らせるようにしてください。
まとめ
- 拡張のストレージは拡張IDに紐づく。IDはUnpackedの場合フォルダパス由来
- 「削除して読み込み直す」手順はIDを変え、保存データを参照不能にする
- 更新は「同じフォルダへ上書きコピー → ↻」で行う。IDが変わらないのでデータは残る
- 実装側は
schemaVersionと起動時マイグレーションで形式変更に備える - この手順は最初の納品時に、機能の説明と同じ粒度で伝える
拡張に限らず、状態を持つツールを配るときは「どう更新させるか」までがプロダクトです。機能だけ作って更新手順を後回しにすると、ユーザーは毎回データを失いながら使うことになります。
この記事を書いた人
BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。
GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。
👉 BENTEN Web Works — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中