[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fg2dkb2ksujd5":3},{"_id":4,"slug":5,"title":6,"subtitle":7,"kind":8,"cards":9,"tags":58,"categories":60,"source":62,"lang":65,"author":66,"audioState":69,"stats":70,"publishedAt":73,"renderer":74},"6abbb87fca21c797c7e9d446","your-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7","Your 'Save' Button Makes Copies. The File System Access API Doesn't.","You're building a little in-browser markdown editor.","news",[10,13,18,23,28,33,38,43,48,53],{"headline":6,"body":11,"imageUrl":12,"sourceImageUrl":12},"You're building a little in-browser markdown editor. \"Save\" is one line: turn the textarea into a Blob, wrap it in an \u003Ca download>, click it programmatically. Ship it, feels great in your own testing — you save once, done.","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fbestpractic.org%2Fmedia%2Fcovers%2Ffile-system-access-api-real-file-read-write.png",{"headline":14,"body":15,"imageUrl":16,"images":17},"A real user opens notes.md, edits it, hits","A real user opens notes.md, edits it, hits Save. Then edits it again, hits Save again. Then again. They go looking for their notes later and their Downloads folder has notes.md, notes (1).md, notes (2).md, notes (3).md — four files, three of them stale, and no indication which one is the one they actually want. They didn't do anything wrong. Your \"Save\" button was never a save button. It was a \"make a new file\" button that happened to reuse a familiar icon. The wrong way, and why it feels right","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F1.webp",{"local":16},{"headline":19,"body":20,"imageUrl":21,"images":22},"Here's the pattern, and it's everywhere — CodeSandbox-style","Here's the pattern, and it's everywhere — CodeSandbox-style playgrounds, browser-based note apps, config generators, anything that needs to hand the user a file:","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F2.webp",{"local":21},{"headline":24,"body":25,"imageUrl":26,"images":27},"It works, in the sense that a file","It works, in the sense that a file lands on disk. But look at what it actually is: \u003Ca download> triggers the browser's download pipeline, and the download pipeline has exactly one job — write a new file, and if the name collides, rename it so it doesn't clobber anything. That collision-avoidance is a deliberate safety feature of downloads in general (you don't want a sketchy site silently overwriting your existing files), and it's exactly the behavior you don't want from a save button. There is no API call in that snippet that means \"update the file the user already has open.\" There's no handle to the original file at all — just a one-shot blob with a suggested name.","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F3.webp",{"local":26},{"headline":29,"body":30,"imageUrl":31,"images":32},"Try it below and watch a simulated Downloads","Try it below and watch a simulated Downloads folder fill up — every click is a new file, even though it looks and feels like \"saving.\" Runs right in your browser — poke at it and watch the concept react live. What was actually missing: a handle","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F4.webp",{"local":31},{"headline":34,"body":35,"imageUrl":36,"images":37},"The gap isn't in your code, it's in","The gap isn't in your code, it's in the platform. A regular \u003Cinput type=\"file\"> gives you a File object — bytes, a name, a size — but reading it doesn't hand you any way to write back to that same path. Browsers spent two decades treating \"the filesystem\" as something a web page should never be allowed to touch, for good reason: letting arbitrary sites read or overwrite files by name would be catastrophic. So \u003Cinput type=\"file\"> and \u003Ca download> were built to avoid ever creating that connection.","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F5.webp",{"local":36},{"headline":39,"body":40,"imageUrl":41,"images":42},"The File System Access API is what closes","The File System Access API is what closes that gap, deliberately and with permission gates at every step. window.showOpenFilePicker() doesn't just return a file's contents — it returns a FileSystemFileHandle, a persistent reference to that specific file that your page can ask to write to later: Saving back is where the difference actually shows up. You write through the same handle you opened:","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F6.webp",{"local":41},{"headline":44,"body":45,"imageUrl":46,"images":47},"No new filename, no collision to resolve, no","No new filename, no collision to resolve, no (1) appended anywhere. createWritable() opens a stream to the exact file the handle points at; write() sends the new bytes; close() commits them — until then the new bytes go to a temporary file (it starts empty, since keepExistingData defaults to false), and the original is only replaced when the stream closes. Same file, every time — which is the one property a save button actually needs and the download link structurally cannot provide.","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F7.webp",{"local":46},{"headline":49,"body":50,"imageUrl":51,"images":52},"That's not a toy example above — it's","That's not a toy example above — it's real. Try the second panel in the playground: pick an actual file from your machine, edit it, hit Save (Chrome asks once whether the site may save changes — that's the permission gate), then check the file on disk. It changed. Same file, same name, no sibling copies. The part that has to stay honest: this is Chromium, not \"the web\"","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F8.webp",{"local":51},{"headline":54,"body":55,"imageUrl":56,"images":57},"This is the part every \"just switch to","This is the part every \"just switch to the File System Access API\" take glosses over: Firefox and Safari don't implement the part that matters here — the file pickers. They do ship the handle-and-stream half (FileSystemFileHandle, getFile(), and since Safari 26 in September 2025, createWritable()), but only for the sandboxed origin private file system (navigator.storage.getDirectory()), which never touches the user's real files. The half that hands a page an actual file — showOpenFilePicker(), showSaveFilePicker(), showDirectoryPicker() — isn't \"not yet, check back next release\": WebKit's standards position is \"oppose\" on security grounds, and Mozilla's is \"negative\". Chrome, Edge, Opera and most other Chromium browsers support the pickers (Brave ships them switched off); that's the whole list.","\u002Fapi\u002Fmedia\u002Fposts\u002Fyour-save-button-makes-copies-the-file-system-access-api-doe-af28e1a7\u002F9.webp",{"local":56},[59],"dev",[61],"Technology",{"name":63,"url":64},"Dev.to","https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-save-button-makes-copies-the-file-system-access-api-doesnt-2d7h","en",{"handle":67,"displayName":68},"spots","Spots","queued",{"views":71,"likes":72,"saves":72,"shares":72,"completions":72,"opens":72,"skips":72,"depthSum":72},3,0,"2026-09-29T13:09:19.601Z","local"]