[{"data":1,"prerenderedAt":517},["ShallowReactive",2],{"content-\u002Fdocs\u002Fpersistence\u002Foverview":3},{"id":4,"title":5,"body":6,"description":496,"extension":497,"meta":498,"metaRows":499,"navigation":362,"path":512,"seo":513,"source":514,"stem":515,"__hash__":516},"docs\u002Fdocs\u002Fpersistence\u002Foverview.md","Persistence overview",{"type":7,"value":8,"toc":488},"minimark",[9,13,20,23,45,49,54,62,165,171,175,259,269,273,276,285,288,292,335,338,412,415,419,453,464,468,484],[10,11,5],"h1",{"id":12},"persistence-overview",[14,15,16],"blockquote",{},[17,18,19],"p",{},"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.",[21,22],"docs-meta-table",{},[17,24,25,26,30,31,34,35,38,39,44],{},"Type a draft into the title and body fields, then refresh this page; your values come back. The form passes ",[27,28,29],"code",{},"persist: 'session'",", each field's ",[27,32,33],{},"register"," call adds ",[27,36,37],{},"persist: true","; both opt-ins are required, neither alone is enough. The ",[40,41,43],"a",{"href":42},"#two-opt-in-gates","two opt-in gates"," section unpacks why that's the shape.",[46,47],"docs-demo",{"label":48,"slug":12},"Persistence Demo",[50,51,53],"h2",{"id":52},"two-opt-in-gates","Two opt-in gates",[17,55,56,57,61],{},"Persistence is ",[58,59,60],"strong",{},"off by default"," and gated by two opt-ins:",[63,64,65,105],"ol",{},[66,67,68,71,72,75,76],"li",{},[58,69,70],{},"Form-level",": pick a backend with the ",[27,73,74],{},"persist"," option:",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-ts shiki shiki-themes github-light github-dark","useForm({ schema, persist: 'session' })\n","ts","",[27,84,85],{"__ignoreMap":82},[86,87,90,94,98,102],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sScJk","useForm",[86,95,97],{"class":96},"sVt8B","({ schema, persist: ",[86,99,101],{"class":100},"sZZnC","'session'",[86,103,104],{"class":96}," })\n",[66,106,107,110,111,113,114],{},[58,108,109],{},"Field-level",": declare each field's participation in its ",[27,112,33],{}," call site:",[77,115,119],{"className":116,"code":117,"language":118,"meta":82,"style":82},"language-vue shiki shiki-themes github-light github-dark","\u003Cinput v-register=\"form.register('email', { persist: true })\" \u002F>\n","vue",[27,120,121],{"__ignoreMap":82},[86,122,123,126,130,133,136,139,142,144,147,150,153,157,160,162],{"class":88,"line":89},[86,124,125],{"class":96},"\u003C",[86,127,129],{"class":128},"s9eBZ","input",[86,131,132],{"class":92}," v-register",[86,134,135],{"class":96},"=",[86,137,138],{"class":100},"\"",[86,140,141],{"class":96},"form.",[86,143,33],{"class":92},[86,145,146],{"class":96},"(",[86,148,149],{"class":100},"'email'",[86,151,152],{"class":96},", { persist: ",[86,154,156],{"class":155},"sj4cs","true",[86,158,159],{"class":96}," })",[86,161,138],{"class":100},[86,163,164],{"class":96}," \u002F>\n",[17,166,167,168,170],{},"Without both, no writes hit the backend. Adding a new schema field can't accidentally leak into client-side storage unless its ",[27,169,33],{}," call site says so. That's the kind of default that survives long forms growing over time.",[50,172,174],{"id":173},"storage-backends","Storage backends",[77,176,178],{"className":79,"code":177,"language":81,"meta":82,"style":82},"useForm({ persist: 'local' }) \u002F\u002F localStorage\nuseForm({ persist: 'session' }) \u002F\u002F sessionStorage\nuseForm({ persist: 'indexeddb' }) \u002F\u002F IndexedDB\nuseForm({ persist: customStorage }) \u002F\u002F any FormStorage object\nuseForm({ persist: { storage: 'local', debounceMs: 500 } }) \u002F\u002F full options\n",[27,179,180,197,211,226,237],{"__ignoreMap":82},[86,181,182,184,187,190,193],{"class":88,"line":89},[86,183,93],{"class":92},[86,185,186],{"class":96},"({ persist: ",[86,188,189],{"class":100},"'local'",[86,191,192],{"class":96}," }) ",[86,194,196],{"class":195},"sJ8bj","\u002F\u002F localStorage\n",[86,198,200,202,204,206,208],{"class":88,"line":199},2,[86,201,93],{"class":92},[86,203,186],{"class":96},[86,205,101],{"class":100},[86,207,192],{"class":96},[86,209,210],{"class":195},"\u002F\u002F sessionStorage\n",[86,212,214,216,218,221,223],{"class":88,"line":213},3,[86,215,93],{"class":92},[86,217,186],{"class":96},[86,219,220],{"class":100},"'indexeddb'",[86,222,192],{"class":96},[86,224,225],{"class":195},"\u002F\u002F IndexedDB\n",[86,227,229,231,234],{"class":88,"line":228},4,[86,230,93],{"class":92},[86,232,233],{"class":96},"({ persist: customStorage }) ",[86,235,236],{"class":195},"\u002F\u002F any FormStorage object\n",[86,238,240,242,245,247,250,253,256],{"class":88,"line":239},5,[86,241,93],{"class":92},[86,243,244],{"class":96},"({ persist: { storage: ",[86,246,189],{"class":100},[86,248,249],{"class":96},", debounceMs: ",[86,251,252],{"class":155},"500",[86,254,255],{"class":96}," } }) ",[86,257,258],{"class":195},"\u002F\u002F full options\n",[17,260,261,262,264,265,268],{},"The built-in backends are loaded on demand; picking ",[27,263,189],{}," doesn't pull in IndexedDB code. Custom backends implement the ",[27,266,267],{},"FormStorage"," interface (read \u002F write \u002F clear \u002F list).",[50,270,272],{"id":271},"schema-aware-hydration","Schema-aware hydration",[17,274,275],{},"Attaform stamps every persisted draft with a fingerprint of the schema. On remount:",[277,278,279,282],"ul",{},[66,280,281],{},"If the schema is unchanged, values rehydrate before the first render. No flash.",[66,283,284],{},"If the schema has changed (a field renamed, a refinement tightened), the stale draft is dropped automatically.",[17,286,287],{},"No silent shape mismatches; no manual versioning.",[50,289,291],{"id":290},"sensitive-name-protection","Sensitive-name protection",[17,293,294,295,298,299,302,303,302,306,302,309,302,312,302,315,302,318,302,321,302,324,302,327,330,331,334],{},"Some path names imply secrets. Attaform's built-in ",[27,296,297],{},"DEFAULT_SENSITIVE_NAMES"," list (",[27,300,301],{},"password",", ",[27,304,305],{},"passwd",[27,307,308],{},"pwd",[27,310,311],{},"pin",[27,313,314],{},"cvv",[27,316,317],{},"card_number",[27,319,320],{},"ssn",[27,322,323],{},"token",[27,325,326],{},"secret",[27,328,329],{},"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 ",[27,332,333],{},"acknowledgeSensitive: true"," to persist anyway.",[17,336,337],{},"Compose your own list by extending the default:",[77,339,341],{"className":79,"code":340,"language":81,"meta":82,"style":82},"import { createAttaform, DEFAULT_SENSITIVE_NAMES } from 'attaform'\n\ncreateAttaform({\n  defaults: {\n    sensitiveNames: [...DEFAULT_SENSITIVE_NAMES, 'mrn', 'tax_id'],\n  },\n})\n",[27,342,343,358,364,372,377,400,406],{"__ignoreMap":82},[86,344,345,349,352,355],{"class":88,"line":89},[86,346,348],{"class":347},"szBVR","import",[86,350,351],{"class":96}," { createAttaform, DEFAULT_SENSITIVE_NAMES } ",[86,353,354],{"class":347},"from",[86,356,357],{"class":100}," 'attaform'\n",[86,359,360],{"class":88,"line":199},[86,361,363],{"emptyLinePlaceholder":362},true,"\n",[86,365,366,369],{"class":88,"line":213},[86,367,368],{"class":92},"createAttaform",[86,370,371],{"class":96},"({\n",[86,373,374],{"class":88,"line":228},[86,375,376],{"class":96},"  defaults: {\n",[86,378,379,382,385,387,389,392,394,397],{"class":88,"line":239},[86,380,381],{"class":96},"    sensitiveNames: [",[86,383,384],{"class":347},"...",[86,386,297],{"class":155},[86,388,302],{"class":96},[86,390,391],{"class":100},"'mrn'",[86,393,302],{"class":96},[86,395,396],{"class":100},"'tax_id'",[86,398,399],{"class":96},"],\n",[86,401,403],{"class":88,"line":402},6,[86,404,405],{"class":96},"  },\n",[86,407,409],{"class":88,"line":408},7,[86,410,411],{"class":96},"})\n",[17,413,414],{},"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.)",[50,416,418],{"id":417},"imperative-control","Imperative control",[77,420,422],{"className":79,"code":421,"language":81,"meta":82,"style":82},"await form.clearPersistedDraft() \u002F\u002F wipe the backend entry\nform.reset() \u002F\u002F reset the in-memory store back to defaults\n",[27,423,424,441],{"__ignoreMap":82},[86,425,426,429,432,435,438],{"class":88,"line":89},[86,427,428],{"class":347},"await",[86,430,431],{"class":96}," form.",[86,433,434],{"class":92},"clearPersistedDraft",[86,436,437],{"class":96},"() ",[86,439,440],{"class":195},"\u002F\u002F wipe the backend entry\n",[86,442,443,445,448,450],{"class":88,"line":199},[86,444,141],{"class":96},[86,446,447],{"class":92},"reset",[86,449,437],{"class":96},[86,451,452],{"class":195},"\u002F\u002F reset the in-memory store back to defaults\n",[17,454,455,456,459,460,463],{},"Pair with a \"Clear my draft\" button or a post-submit cleanup hook. By default, drafts auto-clear when ",[27,457,458],{},"handleSubmit","'s success callback resolves; set ",[27,461,462],{},"clearOnSubmitSuccess: false"," to keep them.",[50,465,467],{"id":466},"where-to-next","Where to next",[277,469,470,477],{},[66,471,472,476],{},[40,473,475],{"href":474},"\u002Fdocs\u002Freading-the-form\u002Fthe-form","The form",": the full reactive surface.",[66,478,479,483],{},[40,480,482],{"href":481},"\u002Fdocs\u002Fdevtools-and-debugging\u002Ftroubleshooting","Troubleshooting",": diagnose hydration mismatches with the DevTools panel.",[485,486,487],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":82,"searchDepth":199,"depth":199,"links":489},[490,491,492,493,494,495],{"id":52,"depth":199,"text":53},{"id":173,"depth":199,"text":174},{"id":271,"depth":199,"text":272},{"id":290,"depth":199,"text":291},{"id":417,"depth":199,"text":418},{"id":466,"depth":199,"text":467},"Opt in per form and per field, pick a storage backend, and Attaform saves drafts across reloads with schema-aware hydration, sensitive-name guards, and zero ceremony beyond the persist option.","md",{},[500,503,506,509],{"label":501,"value":502},"Category","Module",{"label":504,"value":505},"Off by default?","Yes",{"label":507,"value":508},"Storage","local \u002F session \u002F IndexedDB \u002F custom",{"label":510,"value":511},"Sensitive-name guard","built-in","\u002Fdocs\u002Fpersistence\u002Foverview",{"title":5,"description":496},null,"docs\u002Fpersistence\u002Foverview","pb2y-bxcaByMouBxGyfrFAJsBJ9FNZwO24XT83bjIZc",1780949760820]