Skip to main content

Storage Migrations

As your extension evolves, the shape of data stored in chrome.storage changes — keys get renamed, formats change, old data needs to be transformed. Without a migration system, users upgrading from an old version can end up with broken or stale state.

runStorageMigrations() in @repo/browser-utils implements a numbered migration list. The background service worker runs it on every startup. Each migration checks whether it has already been applied (via a stored version number) and runs only if needed.

How It Works

  1. The background worker defines a STORAGE_MIGRATIONS array with { version, migrate } objects
  2. On startup, runStorageMigrations(STORAGE_MIGRATIONS) reads the current schema version from storage
  3. Any migration with version greater than the current version runs in order
  4. After each migration, the new version is written to storage
  5. Migrations are idempotent — they run once per version

Adding a Migration

Migrations live in apps/background/src/index.ts. Each migration has:

  • version: A number. Use incrementing integers (1, 2, 3, …)
  • migrate: An async function. Use getStorageValue and setStorageValue from @repo/browser-utils

Example:

import { getStorageValue, setStorageValue, type StorageMigration } from '@repo/browser-utils';

const STORAGE_MIGRATIONS: StorageMigration[] = [
{
version: 1,
migrate: async () => {
const installReason = await getStorageValue<string>('installReason');
if (installReason === null) {
await setStorageValue('installReason', 'unknown');
}
},
},
{
version: 2,
migrate: async () => {
// Rename key: oldKey -> newKey
const old = await getStorageValue<string>('oldKey');
if (old !== null) {
await setStorageValue('newKey', old);
await removeStorageValue('oldKey');
}
},
},
];

const storageReady = runStorageMigrations(STORAGE_MIGRATIONS);

Sync vs Local

By default, migrations run against chrome.storage.local. To migrate sync storage, pass the area name:

runStorageMigrations(MIGRATIONS, 'sync');

Best Practices

  • Never remove a migration — old installs may not have run it yet. Add new migrations with higher version numbers
  • Keep migrations small — one logical change per migration
  • Await storageReady — Background message handlers that read storage should await storageReady before accessing migrated data