# Migrate persisted state

Use `version` and `migrate` when a breaking state-shape change makes the data already in storage incompatible with the store in your code. The migration runs during rehydration, returns the current state shape, and lets `persist` write the migrated value back to storage.

## When to use this

Add a new version when you rename, remove, or otherwise change a persisted field. Keep the same `name` so existing data is found, and make `migrate` handle the stored versions your application still supports.

## Migrate a renamed field

1. Install Zustand in your application:

   ```bash
   npm install zustand
   ```

2. Set the storage item to the old shape and create the store with a new version. This complete React example renames `oldLayout` to `layout`:

   ```tsx
   import { StrictMode } from 'react'
   import { createRoot } from 'react-dom/client'
   import { create } from 'zustand'
   import { persist } from 'zustand/middleware'

   type Layout = 'compact' | 'comfortable'

   type SettingsStore = {
     theme: 'light' | 'dark'
     layout: Layout
   }

   type LegacySettings = {
     theme?: 'light' | 'dark'
     oldLayout?: Layout
   }

   function isLegacySettings(value: unknown): value is LegacySettings {
     return typeof value === 'object' && value !== null
   }

   localStorage.setItem(
     'settings',
     JSON.stringify({
       state: { theme: 'dark', oldLayout: 'compact' },
       version: 0,
     }),
   )

   const useSettingsStore = create<SettingsStore>()(
     persist<SettingsStore>(
       () => ({
         theme: 'light',
         layout: 'comfortable',
       }),
       {
         name: 'settings',
         version: 1,
         migrate: (persistedState, version) => {
           if (version === 0 && isLegacySettings(persistedState)) {
             return {
               theme: persistedState.theme ?? 'light',
               layout: persistedState.oldLayout ?? 'comfortable',
             }
           }

           return {
             theme: 'light',
             layout: 'comfortable',
           }
         },
       },
     ),
   )

   function Settings() {
     const theme = useSettingsStore((state) => state.theme)
     const layout = useSettingsStore((state) => state.layout)

     return <p>{theme} theme, {layout} layout</p>
   }

   const container = document.getElementById('root')!
   createRoot(container).render(
     <StrictMode>
       <Settings />
     </StrictMode>,
   )
   ```

   ![The mounted component shows the migrated dark theme and compact layout.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/a9864c6189c5996a13382411ef028665.png)

   The mounted component reads the migrated state and displays `dark theme, compact layout`. During hydration, Zustand passes the stored state and its stored version (`0`) to `migrate`; the returned state uses the current shape. Because the migration ran, the middleware persists the migrated value under `settings`.

3. In your application, replace the example's seeded `localStorage` value with the data your earlier release wrote. Keep the migration's return value compatible with the current store, and increment `version` again for the next breaking shape change.

## Options that matter

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `name` | `string` | — | Selects the storage key. It is required and must be unique. |
| `version` | `number` | `0` | Identifies the current persisted shape. A mismatch with the stored version triggers `migrate`; without a migration function, the stored value is not used. |
| `migrate` | `(persistedState: unknown, version: number) => PersistedState \| Promise<PersistedState>` | `(persistedState) => persistedState` | Converts the stored state from its recorded version to the current shape. It can return the result synchronously or asynchronously. |

## Pitfalls

- `version` is compared with the version stored alongside the state, not inferred from the fields. Change it when the persisted shape changes.
- Return the latest persisted shape from `migrate`; do not return the old field name.
- A migration runs during hydration. With asynchronous storage, the component can render its initial state before hydration finishes; wait for hydration when that temporary state matters. See [Persist store data](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/persist-store-data).
- Persist merges the migrated value with the current state shallowly by default. For partially persisted nested objects, use a custom merge as described in [Merge persisted state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/merge-persisted-state).

## Related

- [Persist store data](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/persist-store-data) — configure persistence and storage.
- [Persist selected state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/persist-selected-state) — persist only selected fields.
- [Merge persisted state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/merge-persisted-state) — preserve nested fields during hydration.
