Skip to main content
The persist middleware enables automatic state persistence to storage (localStorage, sessionStorage, or custom storage implementations). This ensures your store’s state survives page reloads and browser sessions.
import { persist, createJSONStorage } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }),
    { name: 'counter-storage' }
  )
)

Signature

persist<T, U>(
  stateCreator: StateCreator<T, [], []>,
  options?: PersistOptions<T, U>
): StateCreator<T, [['zustand/persist', U]], []>

Parameters

stateCreator
StateCreator<T>
required
The state creator function that defines your store’s initial state and actions.
options
PersistOptions<T, U>
Configuration options for persistence behavior.
name
string
required
Unique name for the stored item. Used as the storage key.
storage
PersistStorage
default:"createJSONStorage(() => localStorage)"
Custom storage implementation. Defaults to localStorage with JSON serialization.
partialize
(state: T) => PersistedState
Filter which parts of state to persist. Useful for excluding computed values or sensitive data.
onRehydrateStorage
(state: T) => ((state?: T, error?: unknown) => void) | void
Callback invoked before and after state rehydration. The returned function is called after rehydration completes or if an error occurs.
version
number
Version number for the persisted state. If the stored version doesn’t match, the state won’t be used.
migrate
(persistedState: any, version: number) => T | Promise<T>
Migration function to transform persisted state when versions don’t match. Allows you to handle breaking changes gracefully.
merge
(persistedState: any, currentState: T) => T
Custom merge logic for combining persisted state with current state. Defaults to shallow merge.
skipHydration
boolean
default:false
If true, prevents automatic rehydration on initialization. Call store.persist.rehydrate() manually instead. Useful for SSR.

Basic Usage

import { create } from 'zustand'
import { persist } from 'zustand/middleware'

type Store = {
  position: { x: number; y: number }
  setPosition: (position: { x: number; y: number }) => void
}

const usePositionStore = create<Store>()(
  persist(
    (set) => ({
      position: { x: 0, y: 0 },
      setPosition: (position) => set({ position }),
    }),
    { name: 'position-storage' }
  )
)

Partial Persistence

Persist only specific parts of your state using partialize:
const useStore = create<Store>()(
  persist(
    (set) => ({
      context: {
        position: { x: 0, y: 0 },
      },
      actions: {
        setPosition: (position) => set({ context: { position } }),
      },
    }),
    {
      name: 'position-storage',
      partialize: (state) => ({ context: state.context }),
    }
  )
)

Custom Storage

Use custom storage engines like sessionStorage or URL parameters:
import { createJSONStorage } from 'zustand/middleware'

// Session storage
const useStore = create(
  persist(
    (set) => ({ /* ... */ }),
    {
      name: 'my-storage',
      storage: createJSONStorage(() => sessionStorage),
    }
  )
)

// Custom storage (URL parameters)
const searchParamsStorage = {
  getItem: (key: string) => {
    const searchParams = new URL(location.href).searchParams
    return searchParams.get(key)
  },
  setItem: (key: string, value: string) => {
    const searchParams = new URL(location.href).searchParams
    searchParams.set(key, value)
    window.history.replaceState({}, '', `${location.pathname}?${searchParams}`)
  },
  removeItem: (key: string) => {
    const searchParams = new URL(location.href).searchParams
    searchParams.delete(key)
    window.history.replaceState({}, '', `${location.pathname}?${searchParams}`)
  },
}

const useStore = create(
  persist(
    (set) => ({ /* ... */ }),
    {
      name: 'position',
      storage: createJSONStorage(() => searchParamsStorage),
    }
  )
)

Versioning and Migration

Handle breaking changes to your state structure:
const useStore = create<Store>()(
  persist(
    (set) => ({
      position: { x: 0, y: 0 },
      setPosition: (position) => set({ position }),
    }),
    {
      name: 'position-storage',
      version: 1,
      migrate: (persistedState: any, version) => {
        if (version === 0) {
          // Migrate from version 0 to version 1
          // Old format: { x: 100, y: 100 }
          // New format: { position: { x: 100, y: 100 } }
          return {
            position: { x: persistedState.x, y: persistedState.y },
          }
        }
        return persistedState
      },
    }
  )
)

Deep Merging

Use deep merge for nested objects to prevent missing fields:
import createDeepMerge from '@fastify/deepmerge'

const deepMerge = createDeepMerge({ all: true })

const useStore = create<Store>()(
  persist(
    (set) => ({
      position: { x: 0, y: 0 },
      setPosition: (position) => set({ position }),
    }),
    {
      name: 'position-storage',
      merge: (persisted, current) => deepMerge(current, persisted),
    }
  )
)

Manual Hydration

Control when state is rehydrated (useful for SSR):
const useStore = create<Store>()(
  persist(
    (set) => ({
      position: { x: 0, y: 0 },
      setPosition: (position) => set({ position }),
    }),
    {
      name: 'position-storage',
      skipHydration: true,
    }
  )
)

// Later, manually trigger rehydration
setTimeout(() => {
  useStore.persist.rehydrate()
}, 2000)

Composing with Other Middleware

import { devtools, persist } from 'zustand/middleware'

const useStore = create<Store>()(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }),
      { name: 'counter-storage' }
    ),
    { name: 'CounterStore' }
  )
)

API Methods

Stores using persist middleware expose additional methods:
// Manually trigger rehydration
useStore.persist.rehydrate()

// Check if store has been hydrated
useStore.persist.hasHydrated()

// Subscribe to hydration events
useStore.persist.onHydrate((state) => {
  console.log('Hydration started')
})

useStore.persist.onFinishHydration((state) => {
  console.log('Hydration complete')
})

// Clear persisted state
useStore.persist.clearStorage()

Build docs developers (and LLMs) love