Build an editor on Flutter Web. The user opens budget.csv, edits it, hits save. They get budget.csv in their Downloads folder. They edit again, save again: budget (1).csv. Again: budget (2).csv. Their real file — the one on their Desktop that they opened — has never been touched. By the end of an afternoon they have nine numbered copies and no idea which is current. This is not a Flutter problem; it is what "saving" has meant on the web for twenty years. The browser hands you a download, and a download is always a new file. The API that changed this The File System Access API gives a page a real handle to a real file, with the user's permission, granted through the browser's own picker. With a handle you can write back to the same file. Not a copy. That one. It is a genuine capability, gated properly: the user picks the file, the browser mediates every access, and the permission can be revoked. There is no way for a page to reach a file the user has not deliberately handed over. final file = await FileSystemAccess.openFile(); await file.write(bytes); // the same file, in place No download. No (1). The file on their Desktop now has their edits. Why the old way works the way it does The download-a-copy behaviour is not laziness on anyone's part. For most of the web's history, a page having write access to your filesystem would have been an enormous security hole, so the only sanctioned direction was out: the page produces bytes, the browser saves them somewhere the page cannot see or reach. That is why <a download> and the blob-URL trick that every Flutter Web file-save snippet uses cannot ever overwrite. They are not writing a file. They are handing the browser a payload and asking it to save one, and "save one" means a new one. Understanding that is what tells you the File System Access API is a genuinely different mechanism rather than a nicer wrapper over the same thing. The permission model changed; the API followed. Coming back after a reload The second half is what makes it feel like an application rather than a web page. Handles can be persisted. Store one, and after a page reload — or the next morning — you can ask for that same file again. The browser will re-prompt for permission, which is correct and is the point, but the user is confirming access to a file they already chose rather than hunting through a picker to find it again. That single behaviour is the difference between "a web tool I paste things into" and "the thing I edit my file with". Handles are stored in IndexedDB — they are structured-cloneable objects, not strings — so they persist like any other browser data and survive until the user clears site data. Which means the recovery flow is: On startup, look for a stored handle. If there is one, ask whether permission is still granted. If it is, open silently and show the document. If it is not, show one button: Reopen budget.csv. Step four is the honest version of "restore my session". You are not re-prompting for a file the user must find again; you are asking them to confirm a file you both already know about. Directories, not just files The same API grants handles to whole directories. That unlocks a different class of application entirely — a project folder, a photo set, a folder of Markdown notes — where the app can enumerate, read and write within the folder the user chose, and nothing outside it. For anything resembling an editor, that is the difference between a single-file toy and something people keep their work in. What you must handle This is where an honest package earns its keep, because the API is not universally available and pretending otherwise produces a broken app on a third of browsers. Support is real but partial. Chrome, Edge and other Chromium browsers have it. Safari and Firefox largely do not. So the capability check is not optional housekeeping — it decides which UI you show: if (await FileSystemAccess.isSupported) { // "Save" — writes in place } else { // "Download a copy" — the old behaviour, honestly labelled } Label the fallback accurately. A button that says Save and silently produces budget (3).csv is worse than a button that says Download a copy, because the first one lies about what just happened. Permission is per-handle and revocable. A write can fail because the user revoked access, or the file moved, or the browser dropped the grant. Handle it as a normal outcome rather than an exception path you never test. It needs a user gesture. The picker cannot be opened from a timer or an async continuation far from a tap. Open it directly in the button handler. Design for the fallback, do not bolt it on The temptation is to write the good path and add a fallback later. That produces an app whose Safari experience is an afterthought, and Safari is not a rounding error. A structure that works: define one interface with open, save and saveAs. Implement it twice — once over the File System Access API, once over the classic picker-and-download. Choose at startup. The rest of your app never branches, and the difference is confined to which implementation is behind the interface and what the save button says. The label matters more than it sounds. If the fallback button says Save and produces budget (3).csv, the app has lied about what it did. If it says Download a copy, the user understands the platform limitation immediately and does not blame the app when their original file is unchanged. Why this matters more on Flutter Web than elsewhere Flutter Web is unusually good at the kind of app this unlocks — editors, spreadsheets, diagram tools, code playgrounds, anything with a document. Those are exactly the apps where "download another copy" is most obviously wrong. It is also the gap people cite when they say Flutter Web "isn't ready for real apps". Not being able to save a file properly is a fair thing to hold against a platform. It is no longer true, on the browsers that matter, and it is worth knowing. A note on security, since people ask Every time this API comes up someone asks whether a web page can now read their whole disk. It cannot, and the reasons are worth knowing so you can answer the question when a user asks you: Every handle comes from a user gesture through the browser's own picker. A page cannot construct a handle to a path it names. Access is per-handle. Being granted budget.csv grants nothing about the folder it sits in, or any other file. The browser blocks sensitive locations — system directories, and in Chromium's case a maintained blocklist that includes things like the user's home root and library folders. Permission is revocable and is re-confirmed after a reload rather than granted permanently in the background. It requires a secure context. No HTTPS, no API. The design is closer to "the user hands your app a file" than to "your app gets filesystem access", which is the right shape for a capability this significant. Try it file_system_access is on pub.dev — MIT, 160/160 pub points. It wraps the picker, the in-place write, the persisted handle and the capability check. It returns false from the capability check on browsers that cannot do this, rather than throwing or half-working — so your fallback is a decision you make at startup rather than a crash you discover from a Safari user three weeks after launch. Originally published at devshakib.jumyn.com. I write about Flutter, Dart and the parts of shipping that are genuinely awkward — and publish the packages that came out of them at pub.dev/publishers/jumyn.com.