Reactive localStorage, sessionStorage, IndexedDB and cookies for Vue 3 (and React) — TTL, encryption, schema migrations, undo/redo, and cross-tab sync.

localStorage only stores strings, silently fills up without warning, and knows nothing about other open tabs — vue-storage-kit takes those quirks off your hands and adds what plain localStorage never had: entry lifetimes, encryption, cross-tab sync, and format migrations.
A shopper adds something to the cart in one tab and expects to see it in the other without reloading the page. Changes to storage propagate between tabs instantly, not only after a refresh.
A forgotten form draft or a temporary access token shouldn't sit in storage indefinitely — an entry's lifetime expires on its own, and stale data gets cleared out the next time it's read.
Anyone can open the browser's dev tools and read storage contents as plain text — sensitive values can be kept encrypted instead of relying on nobody looking.
After an update, a user's browser might still hold data shaped for a previous version of the app — it gets converted to the current shape automatically, instead of crashing or silently losing data.

useStorage works with localStorage, sessionStorage, IndexedDB, and in‑memory via a single interface. Reactive Refs for Vue and useSyncExternalStore for React automatically sync state with the chosen backend. Supports TTL, schema migrations, encryption, compression, signing, debounce/throttle, and history.

Version your data and auto‑apply migration chains (up/down) on structure changes. Set TTL for automatic removal of stale entries on read. Encrypt data via Web Crypto API (AES‑GCM) with PBKDF2 key derivation or a ready‑made CryptoKey, including key rotation.

Enable sync for instant change propagation via BroadcastChannel (with storage event fallback). Optional leader election (navigator.locks) resolves conflicts by last‑write‑wins. Built‑in history (history: n) provides undo/redo without extra code, while debounce/throttle control write frequency.

The Vue plugin sets a global prefix, target, serializer, and error handler. The Nuxt module auto‑imports all composables and makes useCookie SSR‑aware (H3 on the server). The React version uses useSyncExternalStore, sharing one engine with Vue. Separate entry points for Pinia, DevTools, and testing.

Async adapters (including IndexedDB) don’t block rendering; isReady lets you show a skeleton until loading. On the server, an in‑memory fallback is used. Built‑in DevTools inspector shows all instances, their values, TTL, and history, while the timeline logs every change, migration, and error.

When storage quota is exceeded, the library sweeps out TTL‑expired entries and retries the write, and evictOnQuota evicts the least‑used keys instead of handling QuotaExceededError by hand. HMAC signing detects when an entry was modified outside the library — via DevTools or a page script.
TTL stores the expiry right inside the value's envelope — an expired key is removed automatically on the next read, no manual timers.
import { useStorage } from 'vue-storage-kit'
const {
value: otp,
expiry,
remove,
} = useStorage('otp', {
defaultValue: '',
ttl: 5 * 60 * 1000, // 5 minutes
onExpire: () => router.push('/login'),
})
console.log(expiry.value) // Date | null — when this key expires With sync: true, a change in one tab reaches every other open tab instantly — no server, no manual subscriptions.
import { useStorage } from 'vue-storage-kit'
const { value: cart } = useStorage('cart', {
defaultValue: [] as CartItem[],
sync: true,
})
// cart.value stays in sync across every open tab automatically A user on an old version opens the app — the migration chain brings their stored data up to the current schema and persists the result immediately, no manual version if/else.
import { useStorage } from 'vue-storage-kit'
interface SettingsV3 {
theme: 'light' | 'dark'
locale: string
}
const { value: settings } = useStorage<SettingsV3>('settings', {
defaultValue: { theme: 'light', locale: 'en' },
version: 3,
migrations: [
{ version: 2, up: (d: any) => ({ ...d, theme: d.darkMode ? 'dark' : 'light' }) },
{ version: 3, up: (d: any) => ({ ...d, locale: d.lang ?? 'en' }) },
],
onMigrate: (from, to) => console.log(`Migrated settings ${from} → ${to}`),
})
// A v1 user with { darkMode: true } in storage reads
// { darkMode: true, theme: 'dark', locale: 'en' } — the migration chain
// runs and persists automatically, on the very first read.