Application state often needs to survive a browser refresh. This episode covers pinia-plugin-persistedstate for syncing to localStorage and sessionStorage, picking which fields to persist, using custom storage like IndexedDB, and handling rehydration safely.

Without persistence, all state is lost when the browser refreshes. For apps like shopping carts, themes, or user preferences, losing data is very disruptive. Pinia answers with its plugin ecosystem — the most popular being pinia-plugin-persistedstate.
Episode 9 covers persistence thoroughly: installing the plugin, saving the entire state or only specific fields, using custom storage, and handling rehydration. You'll also see that this plugin is a real application of the plugin system from episode 8.
Install the plugin and register it with the Pinia instance:
npm i pinia-plugin-persistedstateimport { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)pinia.use(piniaPluginPersistedstate) enables persistence for all stores. After this, every store that adds the persist option will automatically be saved to localStorage.
To save an entire store's state, simply set persist: true:
export const useThemeStore = defineStore('theme', {
state: () => ({ mode: 'light', accent: 'blue' }),
persist: true,
})persist: true saves the entire theme state to localStorage under a key named after the store id, i.e. theme. When the store is recreated after a refresh, the plugin hydrates the state from localStorage automatically.
Not every field deserves to be persisted. Transient fields like loading and error shouldn't be stored:
export const useSettingsStore = defineStore('settings', {
state: () => ({
language: 'id',
volume: 80,
loading: false,
}),
persist: {
key: 'app-settings',
pick: ['language', 'volume'],
},
})persist: { key, pick } saves state under the custom key app-settings and only the language and volume fields. The transient loading field will never make it into localStorage.
By default the plugin uses localStorage. For sessionStorage or other storage, configure it through the storage option:
persist: {
key: 'session-data',
storage: sessionStorage,
}persist: { storage: sessionStorage } keeps the data only while the tab is open. At larger scale, storage can be any object implementing getItem and setItem — for example an IndexedDB wrapper or a backend sync.
By default the plugin uses JSON.stringify for writing and JSON.parse for reading. If your state contains values JSON doesn't support directly — like Date or Map — provide transformation functions through the serialize and deserialize options:
persist: {
key: 'event-cache',
serializer: {
serialize: (state) => JSON.stringify(state),
deserialize: (raw) => JSON.parse(raw),
},
}With a custom serializer, you can turn Date into an ISO string when writing and back into a Date object when reading. This transformation is also useful for handling old data stored with a different format in previous versions of the app.
Rehydration happens when the plugin creates the store: the initial state is defined, then overwritten with the data from storage. Be careful with outdated data — always validate the structure before using the rehydrated result. A common pattern is to compare the stored fields against the default state, then fill the gaps with safe fallback values.
Warning
Persistence is not a substitute for security. Tokens and secrets must never be stored in localStorage — we'll cover data security thoroughly in episode 15.
Episode 9 equips you with production-ready persistence. You can now enable full persistence per store, pick which fields to persist with pick, use custom storage, and understand the rehydration process.
Key takeaways:
pinia-plugin-persistedstate is installed with pinia.use.persist: true saves the entire store state.persist: { pick: [...] } selects the fields to persist.persist: { storage } replaces localStorage with another storage.key changes the storage key name.In the next episode, episode 10, we'll discuss DevTools and debugging — inspecting state and actions per store in Vue DevTools, the time-travel feature, and troubleshooting common problems like non-reactive state, undetected changes, and circular store dependencies.