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
- Sanitize data: Always validate and sanitize deserialized data
- Exclude sensitive data: Don't serialize passwords, tokens, or sensitive information
- Validate structure: Ensure deserialized state matches expected structure
- Use encryption: Encrypt sensitive data before storing
Performance Tips
- Selective serialization: Only serialize necessary data
- Debounce saves: Avoid excessive storage operations
- Compress data: Use compression for large states
- Lazy loading: Load persisted state only when needed
Error Handling
- Graceful degradation: Handle serialization failures gracefully
- Fallback mechanisms: Provide fallbacks for corrupted data
- Logging: Log serialization errors for debugging
- Validation: Validate data structure before hydration