The File System Access API: simplifying access to local files Stay organized with collections Save and categorize content based on your preferences.
The File System Access API allows web apps to read or save changes directly to files and folders on the user's device.
What is the File System Access API?
TheFile System Access API enables developers to build powerful web apps that interact withfiles on the user's local device, such as IDEs, photo and video editors, text editors, and more. Aftera user grants a web app access, this API allows them to read or save changes directly to files andfolders on the user's device. Beyond reading and writing files, the File System Access API providesthe ability to open a directory and enumerate its contents.
Note: The File System Access API—despite the similar name—is distinct from theFileSystem
interface exposed by theFile and Directory Entries API, whichdocuments the types and operations made available by browsers to script when a hierarchy of filesand directories are dragged and dropped onto a page or selected using form elements or equivalentuser actions.It is likewise distinct from the deprecatedFile API: Directories and System specification, whichdefines an API to navigate file system hierarchies and a means by which browsers may exposesandboxed sections of a user's local file system to web applications.If you've worked with reading and writing files before, much of what I'm about to share will befamiliar to you. I encourage you to read it anyway, because not all systems are alike.
Note: We've put a lot of thought into the design and implementation of the File System AccessAPI to ensure that people can manage their file system. See thesecurity and permissions section for more information.The File System Access API is supported on most Chromium browsers onWindows, macOS, ChromeOS, Linux, and Android. A notable exception is Brave where it iscurrently only available behind a flag.
Using the File System Access API
To show off the power and usefulness of the File System Access API, I wrote a single filetexteditor. It lets you open a text file, edit it, save the changes back to disk, or starta new file and save the changes to disk. It's nothing fancy, but provides enough to help youunderstand the concepts.
Browser support
Feature detection
To find out if the File System Access API is supported, check if the picker methodyou're interested in exists.
if('showOpenFilePicker'inself){// The `showOpenFilePicker()` method of the File System Access API is supported.}
Try it
See the File System Access API in action in thetext editor demo.
Read a file from the local file system
The first use case I want to tackle is to ask the user to choose a file, then open and read thatfile from disk.
Ask the user to pick a file to read
The entry point to the File System Access API iswindow.showOpenFilePicker()
. When called, it shows a file picker dialog,and prompts the user to select a file. After they select a file, the API returns an array of filehandles. An optionaloptions
parameter lets you influence the behavior of the file picker, forexample, by allowing the user to select multiple files, or directories, or different file types.Without any options specified, the file picker allows the user to select a single file. This isperfect for a text editor.
Like many other powerful APIs, callingshowOpenFilePicker()
must be done in asecurecontext, and must be called from within a user gesture.
letfileHandle;butOpenFile.addEventListener('click',async()=>{//Destructuretheone-elementarray.[fileHandle]=awaitwindow.showOpenFilePicker();//Dosomethingwiththefilehandle.});
Once the user selects a file,showOpenFilePicker()
returns an array of handles, in this case aone-element array with oneFileSystemFileHandle
that contains the properties andmethods needed to interact with the file.
It's helpful to keep a reference to the file handle so that it can be used later. It'll beneeded to save changes to the file, or to perform any other file operations.
Read a file from the file system
Now that you have a handle to a file, you can get the file's properties, or access the file itself.For now, I'll read its contents. Callinghandle.getFile()
returns aFile
object, which contains a blob. To get the data from the blob, call one ofitsmethods, (slice()
,stream()
,text()
, orarrayBuffer()
).
constfile=awaitfileHandle.getFile();constcontents=awaitfile.text();
stream()
,text()
, orarrayBuffer()
methods. For gettingrandom access to a file's contents,use theslice()
method.TheFile
object returned byFileSystemFileHandle.getFile()
is only readable as long as theunderlying file on disk hasn't changed. If the file on disk is modified, theFile
object becomesunreadable and you'll need to callgetFile()
again to get a newFile
object to read the changeddata.
Putting it all together
When users click theOpen button, the browser shows a file picker. Once they've selected a file, theapp reads the contents and puts them into a<textarea>
.
letfileHandle;butOpenFile.addEventListener('click',async()=>{[fileHandle]=awaitwindow.showOpenFilePicker();constfile=awaitfileHandle.getFile();constcontents=awaitfile.text();textArea.value=contents;});
Write the file to the local file system
In the text editor, there are two ways to save a file:Save, andSave As.Savewrites the changes back to the original file using the file handle retrieved earlier. ButSaveAs creates a new file, and thus requires a new file handle.
Create a new file
To save a file, callshowSaveFilePicker()
, which shows the file pickerin "save" mode, allowing the user to pick a new file they want to use for saving. For the texteditor, I also wanted it to automatically add a.txt
extension, so I provided some additionalparameters.
asyncfunctiongetNewFileHandle(){constoptions={types:[{description:'Text Files',accept:{'text/plain':['.txt'],},},],};consthandle=awaitwindow.showSaveFilePicker(options);returnhandle;}
showSaveFilePicker()
code has run, resulting in aSecurityError Failed to execute 'showSaveFilePicker' on 'Window': Must be handling a user gesture to show a file picker.
.Instead, get the file handle first, and onlyafter obtaining the file handle start processing thedata.Save changes to disk
You can find all the code for saving changes to a file in mytext editor demo onGitHub. The core file system interactions are infs-helpers.js
. At its simplest, the process looks like the following code.I'll walk through each step and explain it.
// fileHandle is an instance of FileSystemFileHandle..asyncfunctionwriteFile(fileHandle,contents){// Create a FileSystemWritableFileStream to write to.constwritable=awaitfileHandle.createWritable();// Write the contents of the file to the stream.awaitwritable.write(contents);// Close the file and write the contents to disk.awaitwritable.close();}
Writing data to disk uses aFileSystemWritableFileStream
object, a subclassofWritableStream
. Create the stream by callingcreateWritable()
on the filehandle object. WhencreateWritable()
is called, the browser first checks if the user has grantedwrite permission to the file. If permission to write hasn't been granted, the browser promptsthe user for permission. If permission isn't granted,createWritable()
throws aDOMException
, and the app won't be able to write to the file. In the text editor, theDOMException
objects are handled in thesaveFile()
method.
Thewrite()
method takes a string, which is what's needed for a text editor. But it can also takeaBufferSource, or aBlob. For example, you can pipe a stream directly toit:
asyncfunctionwriteURLToFile(fileHandle,url){//CreateaFileSystemWritableFileStreamtowriteto.constwritable=awaitfileHandle.createWritable();//MakeanHTTPrequestforthecontents.constresponse=awaitfetch(url);//Streamtheresponseintothefile.awaitresponse.body.pipeTo(writable);//pipeTo()closesthedestinationpipebydefault,noneedtocloseit.}
You can alsoseek()
, ortruncate()
within the stream to update thefile at a specific position, or resize the file.
close()
or when the stream is automatically closed by the pipe.Specifying a suggested filename and start directory
In many cases you may want your app to suggest a default filename or location. For example, a texteditor might want to suggest a default filename ofUntitled Text.txt
rather thanUntitled
. Youcan achieve this by passing asuggestedName
property as part of theshowSaveFilePicker
options.
constfileHandle=awaitself.showSaveFilePicker({suggestedName:'Untitled Text.txt',types:[{description:'Text documents',accept:{'text/plain':['.txt'],},}],});
The same goes for the default start directory. If you're building a text editor, you may want tostart the file save or file open dialog in the defaultdocuments
folder, whereas for an imageeditor, may want to start in the defaultpictures
folder. You can suggest a default startdirectory by passing astartIn
property to theshowSaveFilePicker
,showDirectoryPicker()
, orshowOpenFilePicker
methods like so.
constfileHandle=awaitself.showOpenFilePicker({startIn:'pictures'});
The list of the well-known system directories is:
desktop
: The user's desktop directory, if such a thing exists.documents
: Directory in which documents created by the user would typically be stored.downloads
: Directory where downloaded files would typically be stored.music
: Directory where audio files would typically be stored.pictures
: Directory where photos and other still images would typically be stored.videos
: Directory where videos or movies would typically be stored.
Apart from well-known system directories, you can also pass an existing file or directory handle asa value forstartIn
. The dialog would then open in the same directory.
//Assume`directoryHandle`isahandletoapreviouslyopeneddirectory.constfileHandle=awaitself.showOpenFilePicker({startIn:directoryHandle});
Specifying the purpose of different file pickers
Sometimes applications have different pickers for different purposes. For example, a rich texteditor may allow the user to open text files, but also to import images. By default, each filepicker would open at the last-remembered location. You can circumvent this by storingid
valuesfor each type of picker. If anid
is specified, the file picker implementation remembers aseparate last-used directory for thatid
.
constfileHandle1=awaitself.showSaveFilePicker({id:'openText',});constfileHandle2=awaitself.showSaveFilePicker({id:'importImage',});
Storing file handles or directory handles in IndexedDB
File handles and directory handles are serializable, which means that you can save a file ordirectory handle to IndexedDB, or callpostMessage()
to send them between the same top-levelorigin.
Saving file or directory handles to IndexedDB means that you can store state, or remember whichfiles or directories a user was working on. This makes it possible to keep a list of recently openedor edited files, offer to re-open the last file when the app is opened, restore the previous workingdirectory, and more. In the text editor, I store a list of the five most recent files the user hasopened, making it possible to access those files again.
The following code example shows storing and retrieving a file handle and a directory handle. You cansee this in action over on Glitch. (I usetheidb-keyval library for brevity.)
import{get,set}from'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';constpre1=document.querySelector('pre.file');constpre2=document.querySelector('pre.directory');constbutton1=document.querySelector('button.file');constbutton2=document.querySelector('button.directory');// File handlebutton1.addEventListener('click',async()=>{try{constfileHandleOrUndefined=awaitget('file');if(fileHandleOrUndefined){pre1.textContent=`Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;return;}const[fileHandle]=awaitwindow.showOpenFilePicker();awaitset('file',fileHandle);pre1.textContent=`Stored file handle for "${fileHandle.name}" in IndexedDB.`;}catch(error){alert(error.name,error.message);}});// Directory handlebutton2.addEventListener('click',async()=>{try{constdirectoryHandleOrUndefined=awaitget('directory');if(directoryHandleOrUndefined){pre2.textContent=`Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;return;}constdirectoryHandle=awaitwindow.showDirectoryPicker();awaitset('directory',directoryHandle);pre2.textContent=`Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;}catch(error){alert(error.name,error.message);}});
Stored file or directory handles and permissions
Sincepermissions are not always persisted between sessions, you should verify whether the userhas granted permission to the file or directory usingqueryPermission()
. If they haven't, callrequestPermission()
to (re-)request it. This works the same for file and directory handles. Youneed to runfileOrDirectoryHandle.requestPermission(descriptor)
orfileOrDirectoryHandle.queryPermission(descriptor)
respectively.
In the text editor, I created averifyPermission()
method that checks if the user has alreadygranted permission, and if required, makes the request.
asyncfunctionverifyPermission(fileHandle,readWrite){constoptions={};if(readWrite){options.mode='readwrite';}//Checkifpermissionwasalreadygranted.Ifso,returntrue.if((awaitfileHandle.queryPermission(options))==='granted'){returntrue;}//Requestpermission.Iftheusergrantspermission,returntrue.if((awaitfileHandle.requestPermission(options))==='granted'){returntrue;}//Theuserdidn't grant permission, so return false.returnfalse;}
By requesting write permission with the read request, I reduced the number of permission prompts;the user sees one prompt when opening the file, and grants permission to both read and write to it.
Opening a directory and enumerating its contents
To enumerate all files in a directory, callshowDirectoryPicker()
. The userselects a directory in a picker, after which aFileSystemDirectoryHandle
isreturned, which lets you enumerate and access the directory's files. By default, you will have readaccess to the files in the directory, but if you need write access, you can pass{ mode: 'readwrite' }
to the method.
butDir.addEventListener('click',async()=>{constdirHandle=awaitwindow.showDirectoryPicker();forawait(constentryofdirHandle.values()){console.log(entry.kind,entry.name);}});
If you additionally need to access each file usinggetFile()
to, for example, obtain the individualfile sizes, don't useawait
on each result sequentially, but rather process all files inparallel, for example, usingPromise.all()
.
butDir.addEventListener('click',async()=>{constdirHandle=awaitwindow.showDirectoryPicker();constpromises=[];forawait(constentryofdirHandle.values()){if(entry.kind!=='file'){continue;}promises.push(entry.getFile().then((file)=>`${file.name} (${file.size})`));}console.log(awaitPromise.all(promises));});
Creating or accessing files and folders in a directory
From a directory, you can create or access files and folders using thegetFileHandle()
or respectively thegetDirectoryHandle()
method. By passing in an optionaloptions
object with a key ofcreate
and a boolean value oftrue
orfalse
, you can determine if a new file or folder should be created if it doesn't exist.
// In an existing directory, create a new directory named "My Documents".constnewDirectoryHandle=awaitexistingDirectoryHandle.getDirectoryHandle('My Documents',{create:true,});// In this new directory, create a file named "My Notes.txt".constnewFileHandle=awaitnewDirectoryHandle.getFileHandle('My Notes.txt',{create:true});
Resolving the path of an item in a directory
When working with files or folders in a directory, it can be useful to resolve the path of the itemin question. This can be done with the aptly namedresolve()
method. For resolving, theitem can be a direct or indirect child of the directory.
// Resolve the path of the previously created file called "My Notes.txt".constpath=awaitnewDirectoryHandle.resolve(newFileHandle);// `path` is now ["My Documents", "My Notes.txt"]
Deleting files and folders in a directory
If you have obtained access to a directory, you can delete the contained files and folders with theremoveEntry()
method. For folders, deletion can optionally be recursive and includeall subfolders and the files contained therein.
// Delete a file.awaitdirectoryHandle.removeEntry('Abandoned Projects.txt');// Recursively delete a folder.awaitdirectoryHandle.removeEntry('Old Stuff',{recursive:true});
Deleting a file or folder directly
If you have access to a file or directory handle, callremove()
on aFileSystemFileHandle
orFileSystemDirectoryHandle
to remove it.
// Delete a file.awaitfileHandle.remove();// Delete a directory.awaitdirectoryHandle.remove();
Renaming and moving files and folders
Files and folders can be renamed or moved to a new location by callingmove()
on theFileSystemHandle
interface.FileSystemHandle
has the child interfacesFileSystemFileHandle
andFileSystemDirectoryHandle
. Themove()
method takes one or two parameters. The first can eitherbe a string with the new name or aFileSystemDirectoryHandle
to the destination folder. In thelatter case, the optional second parameter is a string with the new name, so moving and renaming canhappen in one step.
// Rename the file.awaitfile.move('new_name');// Move the file to a new directory.awaitfile.move(directory);// Move the file to a new directory and rename it.awaitfile.move(directory,'newer_name');
FileSystemHandle.move()
method has shipped for files within the origin private file system (OPFS),is behind a flag for files if the source or destination is outside of the OPFS,and is notyet supported for directories.Drag and drop integration
TheHTML Drag and Drop interfacesenable web applications to acceptdragged and dropped fileson a web page. During a drag and drop operation, dragged file and directory items are associatedwith file entries and directory entries respectively. TheDataTransferItem.getAsFileSystemHandle()
method returns a promise with aFileSystemFileHandle
object if the dragged item is a file, and apromise with aFileSystemDirectoryHandle
object if the dragged item is a directory. The following listingshows this in action. Note that the Drag and Drop interface'sDataTransferItem.kind
is"file"
for both filesand directories, whereasFileSystemHandle.kind
of the File System Access API is"file"
for files and"directory"
for directories.
elem.addEventListener('dragover',(e)=>{// Prevent navigation.e.preventDefault();});elem.addEventListener('drop',async(e)=>{e.preventDefault();constfileHandlesPromises=[...e.dataTransfer.items].filter((item)=>item.kind==='file').map((item)=>item.getAsFileSystemHandle());forawait(consthandleoffileHandlesPromises){if(handle.kind==='directory'){console.log(`Directory:${handle.name}`);}else{console.log(`File:${handle.name}`);}}});
Accessing the origin private file system
The origin private file system is a storage endpoint that, as the name suggests, is private to theorigin of the page. While browsers typically implement this by persisting the contents of thisorigin private file system to disk somewhere, it isnot intended that the contents be useraccessible. Similarly, there isno expectation that files or directories with names matching thenames of children of the origin private file system exist. While the browser might make it seem thatthere are files, internally—since this is an origin private file system—the browser might storethese "files" in a database or any other data structure. Essentially, if you use this API,donot expect to find the created files matched one-to-one somewhere on the hard disk. You can operate as usual onthe origin private file system once you have access to the rootFileSystemDirectoryHandle
.
constroot=awaitnavigator.storage.getDirectory();// Create a new file handle.constfileHandle=awaitroot.getFileHandle('Untitled.txt',{create:true});// Create a new directory handle.constdirHandle=awaitroot.getDirectoryHandle('New Folder',{create:true});// Recursively remove a directory.awaitroot.removeEntry('Old Stuff',{recursive:true});
Accessing files optimized for performance from the origin private file system
The origin private file system provides optional access to a special kind of file that is highlyoptimized for performance, for example, by offering in-place and exclusive write access to a file'scontent. In Chromium 102 and later, there is an additional method on the origin private file system forsimplifying file access:createSyncAccessHandle()
(for synchronous read and write operations).It is exposed onFileSystemFileHandle
, but exclusively inWeb Workers.
// (Read and write operations are synchronous,// but obtaining the handle is asynchronous.)// Synchronous access exclusively in Worker contexts.constaccessHandle=awaitfileHandle.createSyncAccessHandle();constwrittenBytes=accessHandle.write(buffer);constreadBytes=accessHandle.read(buffer,{at:1});
createAccessHandle()
method (that is, the async variant available onthe main thread and in Web Workers) should trackcrbug.com/1323922.Polyfilling
It is not possible to completely polyfill the File System Access API methods.
- The
showOpenFilePicker()
method can be approximated with an<input type="file">
element. - The
showSaveFilePicker()
method can be simulated with a<a download="file_name">
element,albeit this triggers a programmatic download and not allow for overwriting existing files. - The
showDirectoryPicker()
method can be somewhat emulated with the non-standard<input type="file" webkitdirectory>
element.
We have developed a library calledbrowser-fs-access that uses the FileSystem Access API wherever possible and that falls back to these next best options in all othercases.
Security and permissions
The Chrome team has designed and implemented the File System Access API using the core principlesdefined inControlling Access to Powerful Web Platform Features, including usercontrol and transparency, and user ergonomics.
Opening a file or saving a new file

When opening a file, the user provides permission to read a file or directory using the file picker.The open file picker can only be shown using a user gesture when served from asecurecontext. If users change their minds, they can cancel the selection in the filepicker and the site does not get access to anything. This is the same behavior as that of the<input type="file">
element.

Similarly, when a web app wants to save a new file, the browser shows the save file picker,allowing the user to specify the name and location of the new file. Since they are saving a new fileto the device (versus overwriting an existing file), the file picker grants the app permission towrite to the file.
Restricted folders
To help protect users and their data, the browser may limit the user's ability to save to certainfolders, for example, core operating system folders like Windows, the macOS Library folders.When this happens, the browser shows a prompt and ask the user to choose a differentfolder.
Modifying an existing file or directory
A web app cannot modify a file on disk without getting explicit permission from the user.
Permission prompt
If a person wants to save changes to a file that they previously granted read access to, the browsershows a permission prompt, requesting permission for the site to write changes to disk.The permission request can only be triggered by a user gesture, for example, by clicking a Savebutton.

Alternatively, a web app that edits multiple files, such as an IDE, can also ask for permission to savechanges at the time of opening.
If the user chooses Cancel, and does not grant write access, the web app cannot save changes to thelocal file. It should provide an alternative method for the user to save their data, forexample by providing a way to"download" the file or saving data to the cloud.
Transparency

Once a user has granted permission to a web app to save a local file, the browser shows an iconin the address bar. Clicking on the icon opens a pop-over showing the list of files the user has givenaccess to. The user can always revoke that access if they choose.
Permission persistence
The web app can continue to save changes to the file without prompting until all tabs for itsorigin are closed. Once a tab is closed, the site loses all access. The next time the user uses theweb app, they will be re-prompted for access to the files.
Feedback
We want to hear about your experiences with the File System Access API.
Tell us about the API design
Is there something about the API that doesn't work like you expected? Or are there missing methodsor properties that you need to implement your idea? Have a question or comment on the securitymodel?
- File a spec issue on theWICG File System Access GitHub repo, or add your thoughtsto an existing issue.
Problem with the implementation?
Did you find a bug with Chrome's implementation? Or is the implementation different from the spec?
- File a bug athttps://new.crbug.com. Be sure to include as much detail as you can,instructions for reproducing, and setComponents to
Blink>Storage>FileSystem
.
Planning to use the API?
Planning to use the File System Access API on your site? Your public support helps us to prioritizefeatures, and shows other browser vendors how critical it is to support them.
- Share how you plan to use it on theWICG Discourse thread.
- Send a tweet to@ChromiumDev using the hashtag
#FileSystemAccess
andlet us know where and how you're using it.
Helpful links
- Public explainer
- File System Access specification &File specification
- Tracking bug
- ChromeStatus.com entry
- TypeScript definitions
- File System Access API - Chromium Security Model
- Blink Component:
Blink>Storage>FileSystem
Acknowledgements
The File System Access API spec was written byMarijn Kruisselbrink.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2024-08-19 UTC.