Skip to content

State Serialization

This page provides a complete API reference for Pinia state serialization and deserialization functionality.

Core Serialization API

$serialize()

Serialize store state to a transferable format.

ts
interface Store {
  $serialize(): SerializedState
}

type SerializedState = Record<string, any>

Example:

ts
const useUserStore = defineStore('user', () => {
  const name = ref('John')
  const age = ref(30)
  const preferences = ref({ theme: 'dark' })
  
  return { name, age, preferences }
})

const store = useUserStore()

// Serialize entire store state
const serialized = store.$serialize()
console.log(serialized)
// { name: 'John', age: 30, preferences: { theme: 'dark' } }

$hydrate()

Restore store state from serialized state.

ts
interface Store {
  $hydrate(state: SerializedState): void
}

Example:

ts
const store = useUserStore()
const serializedState = {
  name: 'Jane',
  age: 25,
  preferences: { theme: 'light' }
}

// Restore state
store.$hydrate(serializedState)
console.log(store.name) // 'Jane'

$state

Directly access and set the raw state of a store.

ts
interface Store {
  $state: StateTree
}

type StateTree = Record<string | number | symbol, any>

Example:

ts
const store = useUserStore()

// Get current state
const currentState = store.$state

// Replace entire state
store.$state = {
  name: 'Bob',
  age: 35,
  preferences: { theme: 'auto' }
}

SSR Serialization

getActivePinia().state.value

Get serialized state of all stores.

ts
function getSerializedState(): Record<string, StateTree> {
  const pinia = getActivePinia()
  return pinia?.state.value || {}
}

Server-side Example:

ts
// server.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import { renderToString } from 'vue/server-renderer'

export async function render(url: string) {
  const app = createSSRApp(App)
  const pinia = createPinia()
  app.use(pinia)
  
  // Render app
  const html = await renderToString(app)
  
  // Serialize state
  const state = JSON.stringify(pinia.state.value)
  
  return {
    html,
    state
  }
}

Client-side Example:

ts
// client.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'

const app = createApp(App)
const pinia = createPinia()

// Restore from server state
if (window.__PINIA_STATE__) {
  pinia.state.value = window.__PINIA_STATE__
}

app.use(pinia)
app.mount('#app')

createPinia() Options

Configure serialization behavior for Pinia instance.

ts
interface PiniaOptions {
  /**
   * Custom state serializer
   */
  serializer?: {
    serialize: (value: StateTree) => string
    deserialize: (value: string) => StateTree
  }
}

Example:

ts
import { createPinia } from 'pinia'

const pinia = createPinia({
  serializer: {
    serialize: JSON.stringify,
    deserialize: JSON.parse
  }
})

Custom Serialization

Store-level Serialization

Customize serialization logic for specific stores.

ts
const useUserStore = defineStore('user', () => {
  const name = ref('')
  const sensitiveData = ref('')
  const computedValue = computed(() => name.value.toUpperCase())
  
  // Custom serialization: exclude sensitive data and computed properties
  const $serialize = () => {
    return {
      name: name.value
      // Don't include sensitiveData and computedValue
    }
  }
  
  // Custom deserialization
  const $hydrate = (state: any) => {
    if (state.name !== undefined) {
      name.value = state.name
    }
  }
  
  return {
    name,
    sensitiveData,
    computedValue,
    $serialize,
    $hydrate
  }
})

Type-safe Serialization

Use TypeScript to ensure serialization type safety.

ts
interface UserState {
  name: string
  age: number
  preferences: {
    theme: 'light' | 'dark'
    language: string
  }
}

interface SerializedUserState {
  name: string
  age: number
  preferences: string // JSON string
}

const useUserStore = defineStore('user', (): UserState & {
  $serialize(): SerializedUserState
  $hydrate(state: SerializedUserState): void
} => {
  const name = ref('')
  const age = ref(0)
  const preferences = ref({ theme: 'light' as const, language: 'en' })
  
  const $serialize = (): SerializedUserState => {
    return {
      name: name.value,
      age: age.value,
      preferences: JSON.stringify(preferences.value)
    }
  }
  
  const $hydrate = (state: SerializedUserState) => {
    name.value = state.name
    age.value = state.age
    preferences.value = JSON.parse(state.preferences)
  }
  
  return {
    name,
    age,
    preferences,
    $serialize,
    $hydrate
  }
})

Advanced Serialization

Handling Special Types

Serialize complex data types like Date, RegExp, etc.

ts
function serializeValue(value: any): any {
  if (value instanceof Date) {
    return { __type: 'Date', value: value.toISOString() }
  }
  
  if (value instanceof RegExp) {
    return { __type: 'RegExp', source: value.source, flags: value.flags }
  }
  
  if (value instanceof Map) {
    return { __type: 'Map', entries: Array.from(value.entries()) }
  }
  
  if (value instanceof Set) {
    return { __type: 'Set', values: Array.from(value.values()) }
  }
  
  if (Array.isArray(value)) {
    return value.map(serializeValue)
  }
  
  if (value && typeof value === 'object') {
    const serialized: Record<string, any> = {}
    for (const [k, v] of Object.entries(value)) {
      serialized[k] = serializeValue(v)
    }
    return serialized
  }
  
  return value
}

function deserializeValue(value: any): any {
  if (value && typeof value === 'object' && value.__type) {
    switch (value.__type) {
      case 'Date':
        return new Date(value.value)
      case 'RegExp':
        return new RegExp(value.source, value.flags)
      case 'Map':
        return new Map(value.entries)
      case 'Set':
        return new Set(value.values)
    }
  }
  
  if (Array.isArray(value)) {
    return value.map(deserializeValue)
  }
  
  if (value && typeof value === 'object') {
    const deserialized: Record<string, any> = {}
    for (const [k, v] of Object.entries(value)) {
      deserialized[k] = deserializeValue(v)
    }
    return deserialized
  }
  
  return value
}

Selective Serialization

Serialize only specific parts of the state.

ts
const useAppStore = defineStore('app', () => {
  const user = ref(null)
  const settings = ref({})
  const cache = ref(new Map()) // Don't serialize cache
  const temporaryData = ref({}) // Don't serialize temporary data
  
  const $serialize = () => {
    return {
      user: user.value,
      settings: settings.value
      // Exclude cache and temporaryData
    }
  }
  
  const $hydrate = (state: any) => {
    if (state.user !== undefined) {
      user.value = state.user
    }
    if (state.settings !== undefined) {
      settings.value = state.settings
    }
    // Reset non-serialized data
    cache.value = new Map()
    temporaryData.value = {}
  }
  
  return {
    user,
    settings,
    cache,
    temporaryData,
    $serialize,
    $hydrate
  }
})

Persistence Integration

Local Storage Persistence

ts
function createPersistencePlugin(key: string = 'pinia-state') {
  return ({ store }: { store: any }) => {
    // Load from localStorage on store creation
    const saved = localStorage.getItem(`${key}-${store.$id}`)
    if (saved) {
      try {
        const state = JSON.parse(saved)
        store.$hydrate(state)
      } catch (error) {
        console.error('Failed to load persisted state:', error)
      }
    }
    
    // Save to localStorage on state changes
    store.$subscribe((mutation: any, state: any) => {
      try {
        const serialized = store.$serialize ? store.$serialize() : state
        localStorage.setItem(`${key}-${store.$id}`, JSON.stringify(serialized))
      } catch (error) {
        console.error('Failed to persist state:', error)
      }
    })
  }
}

// Usage
const pinia = createPinia()
pinia.use(createPersistencePlugin())

Session Storage Persistence

ts
function createSessionPersistencePlugin(key: string = 'pinia-session') {
  return ({ store }: { store: any }) => {
    // Load from sessionStorage
    const saved = sessionStorage.getItem(`${key}-${store.$id}`)
    if (saved) {
      try {
        const state = JSON.parse(saved)
        store.$hydrate(state)
      } catch (error) {
        console.error('Failed to load session state:', error)
      }
    }
    
    // Save to sessionStorage
    store.$subscribe((mutation: any, state: any) => {
      try {
        const serialized = store.$serialize ? store.$serialize() : state
        sessionStorage.setItem(`${key}-${store.$id}`, JSON.stringify(serialized))
      } catch (error) {
        console.error('Failed to persist session state:', error)
      }
    })
  }
}

Performance Optimization

Debounced Serialization

ts
import { debounce } from 'lodash-es'

function createDebouncedPersistencePlugin(delay: number = 300) {
  return ({ store }: { store: any }) => {
    const debouncedSave = debounce((state: any) => {
      try {
        const serialized = store.$serialize ? store.$serialize() : state
        localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(serialized))
      } catch (error) {
        console.error('Failed to persist state:', error)
      }
    }, delay)
    
    store.$subscribe((mutation: any, state: any) => {
      debouncedSave(state)
    })
  }
}

Compression

ts
import { compress, decompress } from 'lz-string'

function createCompressedPersistencePlugin() {
  return ({ store }: { store: any }) => {
    // Load and decompress
    const saved = localStorage.getItem(`pinia-${store.$id}`)
    if (saved) {
      try {
        const decompressed = decompress(saved)
        if (decompressed) {
          const state = JSON.parse(decompressed)
          store.$hydrate(state)
        }
      } catch (error) {
        console.error('Failed to load compressed state:', error)
      }
    }
    
    // Compress and save
    store.$subscribe((mutation: any, state: any) => {
      try {
        const serialized = store.$serialize ? store.$serialize() : state
        const compressed = compress(JSON.stringify(serialized))
        localStorage.setItem(`pinia-${store.$id}`, compressed)
      } catch (error) {
        console.error('Failed to persist compressed state:', error)
      }
    })
  }
}

Error Handling

Graceful Serialization Errors

ts
function safeSerialize(store: any): string | null {
  try {
    const state = store.$serialize ? store.$serialize() : store.$state
    return JSON.stringify(state)
  } catch (error) {
    console.error(`Failed to serialize store ${store.$id}:`, error)
    return null
  }
}

function safeDeserialize(store: any, data: string): boolean {
  try {
    const state = JSON.parse(data)
    if (store.$hydrate) {
      store.$hydrate(state)
    } else {
      store.$state = state
    }
    return true
  } catch (error) {
    console.error(`Failed to deserialize store ${store.$id}:`, error)
    return false
  }
}

Type Definitions

Serialization Interfaces

ts
interface SerializableStore {
  $serialize(): SerializedState
  $hydrate(state: SerializedState): void
}

interface SerializedState {
  [key: string]: any
}

interface SerializationOptions {
  include?: string[]
  exclude?: string[]
  transform?: {
    [key: string]: {
      serialize: (value: any) => any
      deserialize: (value: any) => any
    }
  }
}

interface PersistencePlugin {
  key?: string
  storage?: Storage
  serializer?: {
    serialize: (value: any) => string
    deserialize: (value: string) => any
  }
  beforeRestore?: (context: PiniaPluginContext) => void
  afterRestore?: (context: PiniaPluginContext) => void
}

Best Practices

Security Considerations

  1. Sanitize data: Always validate and sanitize deserialized data
  2. Exclude sensitive data: Don't serialize passwords, tokens, or sensitive information
  3. Validate structure: Ensure deserialized state matches expected structure
  4. Use encryption: Encrypt sensitive data before storing

Performance Tips

  1. Selective serialization: Only serialize necessary data
  2. Debounce saves: Avoid excessive storage operations
  3. Compress data: Use compression for large states
  4. Lazy loading: Load persisted state only when needed

Error Handling

  1. Graceful degradation: Handle serialization failures gracefully
  2. Fallback mechanisms: Provide fallbacks for corrupted data
  3. Logging: Log serialization errors for debugging
  4. Validation: Validate data structure before hydration

See Also

Released under the MIT License.