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.

Persistence Demo Open in playground

Type a draft, refresh the page — your values come back.

Two opt-in gates

Persistence is off by default and gated by two opt-ins:

  1. Form-level: pick a backend with the persist option:
    useForm({ schema, persist: 'session' })
    
  2. Field-level: declare each field's participation in its register call 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