demo · v150
Data Persistence Guide
A PWA origin migration in Chrome 150 can preserve the install shortcut after Chrome verifies the destination manifest, the old-origin association file, and the user's migration path — but what about stored data? Not all storage types survive the origin change automatically. This guide covers every storage type, whether it is preserved by the browser, and what migration code is needed for each.
| Storage type | Survives origin migration? | Action required |
|---|---|---|
| Install shortcut Home screen / taskbar icon |
auto-preserved | Chrome 150 can move the install record after the destination manifest declares migrate_from, the old origin authorizes it with allow_migration, and the migration prompt or force behavior completes. The user keeps their shortcut. |
| Service Worker SW registration + caches |
requires action | A new SW must be registered at the new origin. Old SW caches are scoped to the old origin and are NOT migrated. The new SW must re-prime its caches on first install. |
| IndexedDB Structured data storage |
requires action | IDB is keyed by origin. Data at the old origin is NOT accessible from the new origin. Must be exported and re-imported. Add a migration step that reads old data via the old origin before the migration completes. |
| localStorage / sessionStorage Key-value storage |
requires action | Origin-scoped. localStorage at the old origin cannot be read from the new origin. Migrate by posting data from the old origin to the new origin during a shared session, or via server-side transfer. |
| Cookies SameSite, HttpOnly, etc. |
requires action | Cookies are scoped to domain/path. Session cookies at the old origin are lost. Persistent cookies must be re-issued by the new origin's server when the user visits. |
| Cache API SW and window caches |
lost on migration | Cache Storage is origin-scoped. All cached responses at the old origin are lost. The new SW re-populates the cache on first activation. |
| Push subscription Web Push API |
lost — re-request | Push subscriptions are tied to the SW registration endpoint, which is origin-scoped. The user must re-subscribe at the new origin. Server must update its subscription database. |
| Notification permission Notification.requestPermission |
lost — re-request | Browser permissions are origin-scoped. Notification permission granted at the old origin does not transfer. The new origin must re-request permission. |
| OPFS (Origin Private FS) navigator.storage.getDirectory() |
requires action | OPFS is origin-scoped. Files must be read from the old origin and re-written to the new origin during a migration window. |
| WebAuthn / Passkeys navigator.credentials |
requires action | Passkeys are bound to the Relying Party ID (rpId) which is typically the origin's domain. Declare the new origin as an acceptable rpId before migrating, then re-issue credentials. |
| Clipboard permission | varies | Transient permission typically re-granted per-use. Non-persistent — not affected by migration. |
| Geolocation / Camera / Mic permission | lost — re-request | Browser permissions are origin-scoped. All must be re-requested at the new origin. |
IndexedDB migration pattern
1
On the old origin, before migration: export IndexedDB data to a server-side endpoint or to a cookie/sessionStorage value that the new origin can read.
2
On the new origin, on first load: detect that a migration is in progress (e.g. via a URL parameter
?migration=1 or a server-set flag), download the exported data, and write it into the new origin's IndexedDB.3
Clean up the exported data from the server and mark the migration as complete in the new origin's storage.
// On the new origin (after migration):
async function handleMigration() {
if (localStorage.getItem('migration_done')) return;
// Fetch data exported from old origin
const res = await fetch('/api/migration/data');
if (!res.ok) return;
const data = await res.json();
// Write to new origin's IDB
const db = await openDatabase('myapp-db', 1);
const tx = db.transaction('records', 'readwrite');
for (const record of data.records) {
tx.store.put(record);
}
await tx.done;
localStorage.setItem('migration_done', '1');
// Notify server to delete the export
await fetch('/api/migration/cleanup', { method: 'POST' });
}
see also
- Install State Simulator — four-phase migration walkthrough
- Verification Checker — Chrome validation checks
- Manifest Builder — generate both required manifest files
- ChromeStatus: PWA origin migration
implementation reference
Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗