Reactive Storage
useStorage() — the core composable. Works with localStorage, sessionStorage, and an in-memory fallback.
useStorage<T>(key: string, options: StorageOptions<T>): UseStorageReturn<T>Options
defaultValue
T
Value returned when the key is absent or has expired.
target
'local' | 'session' | 'memory' | 'indexeddb' · default: 'local'
Storage backend. 'indexeddb' stores through a single dedicated object store (db vue-storage-kit, store kv) — for custom databases/stores or secondary indexes, use useIndexedDB() / useIDBRef() instead.
ttl
number
Time-to-live in milliseconds; 0 or omitted = no expiry.
version
number · default: 1
Schema version of the stored data.
migrations
Migration[] · default: []
Migration functions run when stored version differs from version — see Schema migrations.
encrypt
boolean | EncryptOptions · default: false
Enable AES-GCM encryption — see Encryption.
compress
boolean | CompressOptions · default: false
Compress the stored envelope via the Compression Streams API. Applied before encrypt, so compression still works on the plaintext (compressed ciphertext yields no size benefit) — see Compression.
sign
boolean | SignOptions · default: false
HMAC-SHA256 check, applied as the outermost layer (wraps compressed/encrypted data too). Detects accidental corruption without requiring secrecy — not a defense against a user editing their own storage — see Corruption detection — combine with encrypt for both.
sync
boolean | SyncOptions · default: false
Enable cross-tab sync via BroadcastChannel — see Tab sync.
debounce
number
Coalesce writes: only persist debounce ms after the last change. Mutually exclusive with throttle (throttle wins if both are set).
throttle
number
Write at most once every throttle ms even during continuous changes, instead of only after they stop.
history
number
Keep up to this many past values in memory for undo()/redo(). Not persisted — resets on reload.
evictOnQuota
boolean | { max?: number } · default: false
On QuotaExceededError, if sweeping this adapter's expired-TTL entries isn't enough, evict its least-recently-written other keys (oldest first, up to max, default 1) and retry. Off by default — deleting unrelated keys is a real side effect.
serializer
Serializer<T> · default: JSON serializer
Custom serialize / deserialize pair.
onError
(err: StorageError) => void
Called instead of throwing on quota exceeded, parse errors, crypto errors, invalid signatures, or other write failures.
onExpire
(key: string) => void
Called when a TTL-expired key is removed on read.
onMigrate
(from: number, to: number) => void
Called after a successful migration.
Return value
value
Ref<T>
Reactive two-way binding; assigning writes to storage.
isReady
Ref<boolean>
false until the initial async read completes (important for IndexedDB and encrypted values).
error
Ref<StorageError | null>
Last error, null if none.
expiry
ComputedRef<Date | null>
When the key expires, null if no TTL.
canUndo
ComputedRef<boolean>
Whether undo() currently does anything (always false unless history is set).
canRedo
ComputedRef<boolean>
Whether redo() currently does anything (always false unless history is set).
remove
() => void
Delete the key from storage and reset value to defaultValue.
refresh
() => Promise<void>
Re-read from storage (useful if another process may have written).
undo
() => void
Navigate back through values recorded via history; a no-op if history isn't set or the undo stack is empty.
redo
() => void
Navigate forward through values recorded via history; a no-op if history isn't set or the redo stack is empty.
Examples
Basic read/write:
const { value: counter } = useStorage('counter', { defaultValue: 0 })
counter.value++ // writes to localStorage immediatelySession storage:
const { value: token } = useStorage('auth-token', {
defaultValue: '',
target: 'session',
})TTL — auto-expire after 30 minutes:
const { value: cache, expiry } = useStorage('search-cache', {
defaultValue: [] as string[],
ttl: 30 * 60 * 1000,
onExpire: (key) => console.log(`${key} expired`),
})
console.log(expiry.value) // Date | nullError handling:
const { value, error } = useStorage('data', {
defaultValue: {},
onError: (err) => {
if (err.type === 'quota-exceeded') showToast('Storage is full')
if (err.type === 'parse-error') console.warn('Corrupted value, reset to default')
},
})Custom serializer:
import type { Serializer } from 'vue-storage-kit'
const base64Serializer: Serializer<string> = {
serialize: (v) => btoa(v),
deserialize: (raw) => atob(raw),
}
const { value } = useStorage('encoded', {
defaultValue: '',
serializer: base64Serializer,
})useLocalStorage / useSessionStorage
Shorthand composables — identical to useStorage but with target pre-set and defaultValue as the second argument (vueuse-compatible signature).
useLocalStorage<T>(key: string, defaultValue: T, opts?): UseStorageReturn<T>
useSessionStorage<T>(key: string, defaultValue: T, opts?): UseStorageReturn<T>import { useLocalStorage, useSessionStorage } from 'vue-storage-kit'
const { value: settings } = useLocalStorage('settings', { theme: 'light', lang: 'en' })
const { value: draft } = useSessionStorage('draft', '')These are drop-in replacements for @vueuse/core useLocalStorage / useSessionStorage.
defineStorageKey
A typed, reusable key descriptor — define a key's shape and options once, then pass the descriptor to useStorage() directly instead of repeating key/options at every call site.
function defineStorageKey<T>(key: string, options: StorageOptions<T>): StorageKeyDef<T>useStorage() accepts either the usual (key, options) pair or a single StorageKeyDef<T>:
useStorage<T>(key: string, options: StorageOptions<T>): UseStorageReturn<T>
useStorage<T>(def: StorageKeyDef<T>): UseStorageReturn<T>import { defineStorageKey, useStorage } from 'vue-storage-kit'
// Define once, e.g. in a shared keys.ts module
const themeKey = defineStorageKey('theme', {
defaultValue: 'light' as 'light' | 'dark',
ttl: undefined,
})
// Use anywhere, fully typed, without repeating the options
const { value: theme } = useStorage(themeKey)