Persistence overview
Opt in per form, opt in per field, pick a backend. Attaform handles the hydrate-on-mount and the schema-aware drop of stale drafts.
- Category
- Module
- Off by default?
- Yes
- Storage
- local / session / IndexedDB / custom
- Sensitive-name guard
- built-in
Type a draft into the title and body fields, then refresh this page; your values come back. The form passes persist: 'session', each field's register call adds persist: true; both opt-ins are required, neither alone is enough. The two opt-in gates section unpacks why that's the shape.
Two opt-in gates
Persistence is off by default and gated by two opt-ins:
- Form-level: pick a backend with the
persistoption:useForm({ schema, persist: 'session' }) - Field-level: declare each field's participation in its
registercall site:<input v-register="form.register('email', { persist: true })" />
Without both, no writes hit the backend. Adding a new schema field can't accidentally leak into client-side storage unless its register call site says so. That's the kind of default that survives long forms growing over time.
Storage backends
useForm({ persist: 'local' }) // localStorage
useForm({ persist: 'session' }) // sessionStorage
useForm({ persist: 'indexeddb' }) // IndexedDB
useForm({ persist: customStorage }) // any FormStorage object
useForm({ persist: { storage: 'local', debounceMs: 500 } }) // full options
The built-in backends are loaded on demand; picking 'local' doesn't pull in IndexedDB code. Custom backends implement the FormStorage interface (read / write / clear / list).
Schema-aware hydration
Attaform stamps every persisted draft with a fingerprint of the schema. On remount:
- If the schema is unchanged, values rehydrate before the first render. No flash.
- If the schema has changed (a field renamed, a refinement tightened), the stale draft is dropped automatically.
No silent shape mismatches; no manual versioning.
Sensitive-name protection
Some path names imply secrets. Attaform's built-in DEFAULT_SENSITIVE_NAMES list (password, passwd, pwd, pin, cvv, card_number, ssn, token, secret, api_key, and others) warns and skips the opt-in at mount if you try to persist them. The field is simply not written, which is the safe default; pass acknowledgeSensitive: true to persist anyway.
Compose your own list by extending the default:
import { createAttaform, DEFAULT_SENSITIVE_NAMES } from 'attaform'
createAttaform({
defaults: {
sensitiveNames: [...DEFAULT_SENSITIVE_NAMES, 'mrn', 'tax_id'],
},
})
The resolved list also gates multi-tab sync broadcasts: one configurable source of truth for "what counts as sensitive" across both write paths. (DevTools renders raw values by design; the list does not gate display.)
Imperative control
await form.clearPersistedDraft() // wipe the backend entry
form.reset() // reset the in-memory store back to defaults
Pair with a "Clear my draft" button or a post-submit cleanup hook. By default, drafts auto-clear when handleSubmit's success callback resolves; set clearOnSubmitSuccess: false to keep them.
Where to next
- The form: the full reactive surface.
- Troubleshooting: diagnose hydration mismatches with the DevTools panel.