[{"data":1,"prerenderedAt":648},["ShallowReactive",2],{"content-\u002Fdocs\u002Frecipes\u002Fpersistence":3},{"id":4,"title":5,"body":6,"description":16,"extension":642,"meta":643,"navigation":90,"path":644,"seo":645,"stem":646,"__hash__":647},"docs\u002Fdocs\u002Frecipes\u002Fpersistence.md","Persist drafts across reloads",{"type":7,"value":8,"toc":634},"minimark",[9,13,17,22,25,33,37,44,170,181,187,295,298,301,305,312,442,449,453,456,464,471,510,517,555,558,562,572,579,583,630],[10,11,5],"h1",{"id":12},"persist-drafts-across-reloads",[14,15,16],"p",{},"Long forms — multi-step onboarding, checkout, surveys — should\nsurvive a navigation mistake or a browser refresh. Attaform persists drafts\nto client-side storage with a per-field opt-in.",[18,19,21],"h2",{"id":20},"the-threat-model","The threat model",[14,23,24],{},"Client-side storage is unencrypted at rest, readable by any\nsame-origin script, and survives logouts. Persisting a name is\nfine; a CVV, password, SSN, or API token is a compliance liability.",[14,26,27,28,32],{},"Defaults are \"nothing persists\" — every persisted field must opt\nin at its ",[29,30,31],"code",{},"register()"," call site, and sensitive-named paths throw\nat mount unless acknowledged.",[18,34,36],{"id":35},"setup","Setup",[14,38,39,40,43],{},"Configure ",[29,41,42],{},"useForm"," with the operational settings (backend, key,\ndebounce window, etc.). Three input forms — pick the one that reads\nbest at the call site:",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F Shorthand: built-in backend with library defaults\nuseForm({ schema, key: 'signup', persist: 'local' })\n\n\u002F\u002F Shorthand: custom FormStorage adapter with library defaults\nuseForm({ schema, key: 'signup', persist: encryptedStorage })\n\n\u002F\u002F Full options: needed when you want to override anything beyond the backend\nuseForm({\n  schema,\n  key: 'signup',\n  persist: { storage: 'local', debounceMs: 500 },\n})\n","ts","",[29,52,53,62,85,92,98,110,115,121,129,135,146,164],{"__ignoreMap":50},[54,55,58],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"sJ8bj","\u002F\u002F Shorthand: built-in backend with library defaults\n",[54,63,65,68,72,76,79,82],{"class":56,"line":64},2,[54,66,42],{"class":67},"sScJk",[54,69,71],{"class":70},"sVt8B","({ schema, key: ",[54,73,75],{"class":74},"sZZnC","'signup'",[54,77,78],{"class":70},", persist: ",[54,80,81],{"class":74},"'local'",[54,83,84],{"class":70}," })\n",[54,86,88],{"class":56,"line":87},3,[54,89,91],{"emptyLinePlaceholder":90},true,"\n",[54,93,95],{"class":56,"line":94},4,[54,96,97],{"class":60},"\u002F\u002F Shorthand: custom FormStorage adapter with library defaults\n",[54,99,101,103,105,107],{"class":56,"line":100},5,[54,102,42],{"class":67},[54,104,71],{"class":70},[54,106,75],{"class":74},[54,108,109],{"class":70},", persist: encryptedStorage })\n",[54,111,113],{"class":56,"line":112},6,[54,114,91],{"emptyLinePlaceholder":90},[54,116,118],{"class":56,"line":117},7,[54,119,120],{"class":60},"\u002F\u002F Full options: needed when you want to override anything beyond the backend\n",[54,122,124,126],{"class":56,"line":123},8,[54,125,42],{"class":67},[54,127,128],{"class":70},"({\n",[54,130,132],{"class":56,"line":131},9,[54,133,134],{"class":70},"  schema,\n",[54,136,138,141,143],{"class":56,"line":137},10,[54,139,140],{"class":70},"  key: ",[54,142,75],{"class":74},[54,144,145],{"class":70},",\n",[54,147,149,152,154,157,161],{"class":56,"line":148},11,[54,150,151],{"class":70},"  persist: { storage: ",[54,153,81],{"class":74},[54,155,156],{"class":70},", debounceMs: ",[54,158,160],{"class":159},"sj4cs","500",[54,162,163],{"class":70}," },\n",[54,165,167],{"class":56,"line":166},12,[54,168,169],{"class":70},"})\n",[14,171,172,173,176,177,180],{},"The shorthand forms are equivalent to ",[29,174,175],{},"{ storage: 'local' }"," and\n",[29,178,179],{},"{ storage: encryptedStorage }"," — purely ergonomic sugar for the\ncommon \"I just want to pick a backend\" case.",[14,182,183,184,186],{},"Then opt each field into persistence at its ",[29,185,31],{}," call site:",[45,188,192],{"className":189,"code":190,"language":191,"meta":50,"style":50},"language-vue shiki shiki-themes github-light github-dark","\u003Cinput v-register=\"register('email', { persist: true })\" \u002F>\n\u003Cinput v-register=\"register('phone', { persist: true })\" \u002F>\n\u003C!-- This input does NOT persist — no opt-in. Value lives in memory only. -->\n\u003Cinput v-register=\"register('cvv')\" \u002F>\n","vue",[29,193,194,235,264,269],{"__ignoreMap":50},[54,195,196,199,203,206,209,212,215,218,221,224,227,230,232],{"class":56,"line":57},[54,197,198],{"class":70},"\u003C",[54,200,202],{"class":201},"s9eBZ","input",[54,204,205],{"class":67}," v-register",[54,207,208],{"class":70},"=",[54,210,211],{"class":74},"\"",[54,213,214],{"class":67},"register",[54,216,217],{"class":70},"(",[54,219,220],{"class":74},"'email'",[54,222,223],{"class":70},", { persist: ",[54,225,226],{"class":159},"true",[54,228,229],{"class":70}," })",[54,231,211],{"class":74},[54,233,234],{"class":70}," \u002F>\n",[54,236,237,239,241,243,245,247,249,251,254,256,258,260,262],{"class":56,"line":64},[54,238,198],{"class":70},[54,240,202],{"class":201},[54,242,205],{"class":67},[54,244,208],{"class":70},[54,246,211],{"class":74},[54,248,214],{"class":67},[54,250,217],{"class":70},[54,252,253],{"class":74},"'phone'",[54,255,223],{"class":70},[54,257,226],{"class":159},[54,259,229],{"class":70},[54,261,211],{"class":74},[54,263,234],{"class":70},[54,265,266],{"class":56,"line":87},[54,267,268],{"class":60},"\u003C!-- This input does NOT persist — no opt-in. Value lives in memory only. -->\n",[54,270,271,273,275,277,279,281,283,285,288,291,293],{"class":56,"line":94},[54,272,198],{"class":70},[54,274,202],{"class":201},[54,276,205],{"class":67},[54,278,208],{"class":70},[54,280,211],{"class":74},[54,282,214],{"class":67},[54,284,217],{"class":70},[54,286,287],{"class":74},"'cvv'",[54,289,290],{"class":70},")",[54,292,211],{"class":74},[54,294,234],{"class":70},[14,296,297],{},"Every typed character in an opted-in field debounces a write to the\nchosen backend. On next mount, those fields hydrate from the saved\npayload; non-opted fields fall back to schema defaults.",[14,299,300],{},"On a successful submit, the draft is cleared.",[18,302,304],{"id":303},"reactive-opt-in","Reactive opt-in",[14,306,307,308,311],{},"The ",[29,309,310],{},"persist"," flag is reactive — flip a remember-me toggle to add or\nremove the opt-in at runtime:",[45,313,315],{"className":189,"code":314,"language":191,"meta":50,"style":50},"\u003Cscript setup lang=\"ts\">\n  const rememberMe = ref(false)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-register=\"register('email', { persist: rememberMe })\" \u002F>\n  \u003Clabel>\u003Cinput type=\"checkbox\" v-model=\"rememberMe\" \u002F> Remember me\u003C\u002Flabel>\n\u003C\u002Ftemplate>\n",[29,316,317,338,361,370,374,383,399,434],{"__ignoreMap":50},[54,318,319,321,324,327,330,332,335],{"class":56,"line":57},[54,320,198],{"class":70},[54,322,323],{"class":201},"script",[54,325,326],{"class":67}," setup",[54,328,329],{"class":67}," lang",[54,331,208],{"class":70},[54,333,334],{"class":74},"\"ts\"",[54,336,337],{"class":70},">\n",[54,339,340,344,347,350,353,355,358],{"class":56,"line":64},[54,341,343],{"class":342},"szBVR","  const",[54,345,346],{"class":159}," rememberMe",[54,348,349],{"class":342}," =",[54,351,352],{"class":67}," ref",[54,354,217],{"class":70},[54,356,357],{"class":159},"false",[54,359,360],{"class":70},")\n",[54,362,363,366,368],{"class":56,"line":87},[54,364,365],{"class":70},"\u003C\u002F",[54,367,323],{"class":201},[54,369,337],{"class":70},[54,371,372],{"class":56,"line":94},[54,373,91],{"emptyLinePlaceholder":90},[54,375,376,378,381],{"class":56,"line":100},[54,377,198],{"class":70},[54,379,380],{"class":201},"template",[54,382,337],{"class":70},[54,384,385,388,390,392,394,397],{"class":56,"line":112},[54,386,387],{"class":70},"  \u003C",[54,389,202],{"class":201},[54,391,205],{"class":67},[54,393,208],{"class":70},[54,395,396],{"class":74},"\"register('email', { persist: rememberMe })\"",[54,398,234],{"class":70},[54,400,401,403,406,409,411,414,416,419,422,424,427,430,432],{"class":56,"line":117},[54,402,387],{"class":70},[54,404,405],{"class":201},"label",[54,407,408],{"class":70},">\u003C",[54,410,202],{"class":201},[54,412,413],{"class":67}," type",[54,415,208],{"class":70},[54,417,418],{"class":74},"\"checkbox\"",[54,420,421],{"class":67}," v-model",[54,423,208],{"class":70},[54,425,426],{"class":74},"\"rememberMe\"",[54,428,429],{"class":70}," \u002F> Remember me\u003C\u002F",[54,431,405],{"class":201},[54,433,337],{"class":70},[54,435,436,438,440],{"class":56,"line":123},[54,437,365],{"class":70},[54,439,380],{"class":201},[54,441,337],{"class":70},[14,443,444,445,448],{},"When ",[29,446,447],{},"rememberMe"," flips false → true, the directive's update hook\nadds the opt-in. Future writes from THIS input persist. Flip it back\nand the opt-in is removed; writes go in-memory only.",[18,450,452],{"id":451},"sensitive-name-protection","Sensitive-name protection",[14,454,455],{},"A small built-in heuristic flags sensitive-looking path names:",[45,457,462],{"className":458,"code":460,"language":461,"meta":50},[459],"language-text","password, passwd, pwd, cvv, cvc, ssn, social-security, dob,\ndate-of-birth, pin, token, secret, api-key, private-key,\ncard-number, card, iban, routing-number, account-number, passport,\ndriver-license, mfa-secret, recovery-code\n","text",[29,463,460],{"__ignoreMap":50},[14,465,466,467,470],{},"Opting one of these into persistence throws\n",[29,468,469],{},"SensitivePersistFieldError"," at mount:",[45,472,474],{"className":189,"code":473,"language":191,"meta":50,"style":50},"\u003C!-- Throws SensitivePersistFieldError -->\n\u003Cinput v-register=\"register('password', { persist: true })\" \u002F>\n",[29,475,476,481],{"__ignoreMap":50},[54,477,478],{"class":56,"line":57},[54,479,480],{"class":60},"\u003C!-- Throws SensitivePersistFieldError -->\n",[54,482,483,485,487,489,491,493,495,497,500,502,504,506,508],{"class":56,"line":64},[54,484,198],{"class":70},[54,486,202],{"class":201},[54,488,205],{"class":67},[54,490,208],{"class":70},[54,492,211],{"class":74},[54,494,214],{"class":67},[54,496,217],{"class":70},[54,498,499],{"class":74},"'password'",[54,501,223],{"class":70},[54,503,226],{"class":159},[54,505,229],{"class":70},[54,507,211],{"class":74},[54,509,234],{"class":70},[14,511,512,513,516],{},"If the persistence is intentional (custom encrypted adapter,\nnarrow-scope internal tool), pass ",[29,514,515],{},"acknowledgeSensitive: true",":",[45,518,520],{"className":189,"code":519,"language":191,"meta":50,"style":50},"\u003Cinput v-register=\"register('password', { persist: true, acknowledgeSensitive: true })\" \u002F>\n",[29,521,522],{"__ignoreMap":50},[54,523,524,526,528,530,532,534,536,538,540,542,544,547,549,551,553],{"class":56,"line":57},[54,525,198],{"class":70},[54,527,202],{"class":201},[54,529,205],{"class":67},[54,531,208],{"class":70},[54,533,211],{"class":74},[54,535,214],{"class":67},[54,537,217],{"class":70},[54,539,499],{"class":74},[54,541,223],{"class":70},[54,543,226],{"class":159},[54,545,546],{"class":70},", acknowledgeSensitive: ",[54,548,226],{"class":159},[54,550,229],{"class":70},[54,552,211],{"class":74},[54,554,234],{"class":70},[14,556,557],{},"The heuristic is a speed bump, not a soundness guarantee —\nadversarially named paths slip through. The real defence is the\nper-field opt-in.",[18,559,561],{"id":560},"reset","Reset",[14,563,564,567,568,571],{},[29,565,566],{},"form.reset()"," and ",[29,569,570],{},"form.resetField(path)"," wipe the persisted draft\nalongside the in-memory clear. Opt-ins survive — the next keystroke\nfrom a still-mounted opted-in input re-populates storage.",[14,573,574,575,578],{},"For \"wipe storage but keep in-memory,\" use ",[29,576,577],{},"form.clearPersistedDraft()",".",[18,580,582],{"id":581},"going-further","Going further",[584,585,586,595,616],"ul",{},[587,588,589,594],"li",{},[590,591,593],"a",{"href":592},"\u002Fdocs\u002Frecipes\u002Fpersistence-policy","Persistence policy"," — sparse\npayloads, error inclusion, schema-change auto-invalidation, what\npersistence is NOT for.",[587,596,597,601,602,604,605,604,608,611,612,615],{},[590,598,600],{"href":599},"\u002Fdocs\u002Frecipes\u002Fpersistence-backends","Persistence backends"," —\npicking ",[29,603,81],{}," \u002F ",[29,606,607],{},"'session'",[29,609,610],{},"'indexeddb'",", full options\nbag, switching backends safely, custom ",[29,613,614],{},"FormStorage"," adapters,\nSSR considerations.",[587,617,618,622,623,567,626,629],{},[590,619,621],{"href":620},"\u002Fdocs\u002Frecipes\u002Fpersistence-edge-cases","Persistence edge cases"," —\nimperative ",[29,624,625],{},"form.persist()",[29,627,628],{},"clearPersistedDraft()",", the\nfour component-binding patterns, cross-tab semantics, dev-mode\nwarnings, gotchas.",[631,632,633],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":50,"searchDepth":64,"depth":64,"links":635},[636,637,638,639,640,641],{"id":20,"depth":64,"text":21},{"id":35,"depth":64,"text":36},{"id":303,"depth":64,"text":304},{"id":451,"depth":64,"text":452},{"id":560,"depth":64,"text":561},{"id":581,"depth":64,"text":582},"md",{},"\u002Fdocs\u002Frecipes\u002Fpersistence",{"title":5,"description":16},"docs\u002Frecipes\u002Fpersistence","yQV8V5JrWlGK3VDZaT4d1Dp4lnitZlhGW_oe6vXJ9e4",1777934135559]