Skip to main content
As your application evolves, the shape of your local data needs to evolve too. CL Sync’s migration system lets you write versioned, data-level transformations that run automatically during store.hydrate() when the persisted schema version lags behind the configured version. Migrations have full read/write access to records, collection states, outbox items, and metadata.

How migrations run

When hydrate() finds that the persisted schema version is less than schema.version, it runs each migration whose version number falls in the range (persistedVersion, configuredVersion], in ascending order. After all migrations complete, the new schema version is written to IndexedDB.
hydrate() throws a version mismatch error if the persisted schema version is newer than the configured one. This guards against data corruption when a user opens an older version of your app after upgrading. Always bump schema.version before shipping schema changes.

The migration context

Every migration receives a SyncMigrationContext that exposes typed read/write helpers for every table CL Sync manages:

Defining migrations

Declare an array of SyncMigration objects and pass them to createSyncStore. Each migration’s version is the version it migrates to.

Migration examples

Rename a field

Backfill a computed field

Prune stale outbox entries

Move collection state after rename

If you rename a collection, delete the old collection state so CL Sync doesn’t treat the stale key as valid:

Full example

Here’s a store configured with two sequential migrations:
If a user’s persisted version is 1, both migrations run in order on the next hydrate() call. If their persisted version is already 2, only the version-3 migration runs.
Migrations run synchronously inside hydrate(). Keep them fast — avoid network requests or heavy computation. If you need to fetch fresh data after a migration, do so after hydrate() resolves.