{"id":363339,"date":"2022-02-14T08:01:21","date_gmt":"2022-02-14T16:01:21","guid":{"rendered":"https:\/\/css-tricks.com\/?p=363339"},"modified":"2022-02-14T08:02:48","modified_gmt":"2022-02-14T16:02:48","slug":"getting-started-with-the-file-system-access-api","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/getting-started-with-the-file-system-access-api\/","title":{"rendered":"Getting Started With the File System Access API"},"content":{"rendered":"\n

The File System Access API is a web API that allows read and write access to a user’s local files. It unlocks new capabilities to build powerful web applications, such as text editors or IDE<\/abbr>s, image editing tools, improved import\/export, all in the frontend. Let\u2019s look into how to get started using this API.<\/p>\n\n\n\n\n\n\n\n

\"Screenshot<\/figure>\n\n\n

Reading files with the File System Access API<\/h3>\n\n\n

Before diving into the code required to read a file from the user’s system, an important detail to keep in mind is that calling the File System Access API needs to be done by a user gesture, in a secure context<\/strong>. In the following example, we’ll use a click event.<\/p>\n\n\n

Reading from a single file<\/h4>\n\n\n

Reading data from a file can be done in less than 10 lines of code. Here’s an example code sample:<\/p>\n\n\n\n

let fileHandle;\n \ndocument.querySelector(\".pick-file\").onclick = async () => {\n [fileHandle] = await window.showOpenFilePicker();\n \n const file = await fileHandle.getFile();\n const content = await file.text();\n \n return content;\n};<\/code><\/pre>\n\n\n\n

Let’s imagine we have a button in our HTML with the class .pick-file<\/code>. When clicking on this button, we launch the file picker by calling window.showOpenFilePicker()<\/code>, and we store the result from this query in a variable called fileHandle<\/code>. <\/p>\n\n\n\n

What we get back from calling showOpenFilePicker()<\/code> is an array of FileSystemFileHandle<\/code><\/a> objects representing each file we selected. As this example is for a single file, we destructure the result. I’ll show how to select multiple files a bit later.<\/p>\n\n\n\n

These objects contain a kind<\/code> and name<\/code> property. If you were to use console.log(fileHandle)<\/code>, you would see the following object:<\/p>\n\n\n\n

FileSystemFileHandle {kind: 'file', name: 'data.txt'}<\/code><\/pre>\n\n\n\n

The kind<\/code> can either be file<\/code> or directory<\/code>.<\/p>\n\n\n\n

On fileHandle<\/code>, we can then call the getFile()<\/code> method to get details about our file. Calling this method returns an object with a few properties, including a timestamp of when the file was last modified, the name of the file, its size, and type.<\/p>\n\n\n\n

Finally, we can call text()<\/code> on the file to get its content.<\/p>\n\n\n

Reading from multiple files<\/h4>\n\n\n

To read from multiple files, we need to pass an options<\/code> object to showOpenFilePicker()<\/code>.<\/p>\n\n\n\n

For example:<\/p>\n\n\n\n

let fileHandles;\nconst options = {\n multiple: true,\n};\n \ndocument.querySelector(\".pick-file\").onclick = async () => {\n fileHandles = await window.showOpenFilePicker(options);\n \n \/\/ The rest of the code will be shown below\n};<\/code><\/pre>\n\n\n\n

By default, the multiple<\/code> property is set to false<\/code>. Other options can be used to indicate the types of files that can be selected.<\/p>\n\n\n\n

For example, if we only wanted to accept .jpeg<\/code> files, the options object would include the following:<\/p>\n\n\n\n

const options = {\n types: [\n   {\n     description: \"Images\",\n     accept: {\n       \"image\/jpeg\": \".jpeg\",\n     },\n   },\n ],\n excludeAcceptAllOption: true,\n};<\/code><\/pre>\n\n\n\n

In this example, fileHandles<\/code> is an array containing multiple files, so getting their content would be done in the following way:<\/p>\n\n\n\n

let fileHandles;\nconst options = {\n multiple: true,\n};\n \ndocument.querySelector(\".pick-file\").onclick = async () => {\n fileHandles = await window.showOpenFilePicker(options);\n \n const allContent = await Promise.all(\n   fileHandles.map(async (fileHandle) => {\n     const file = await fileHandle.getFile();\n     const content = await file.text();\n     return content;\n   })\n );\n \n console.log(allContent);\n};<\/code><\/pre>\n\n\n

Writing to a file with the File System Access API<\/h3>\n\n\n

The File System Access API also allows you to write content to files. First, let’s look into how to save a new file.<\/p>\n\n\n

Writing to a new file<\/h4>\n\n\n

Writing to a new file can also be done in a very short amount of code!<\/p>\n\n\n\n

document.querySelector(\".save-file\").onclick = async () => {\n const options = {\n   types: [\n     {\n       description: \"Test files\",\n       accept: {\n         \"text\/plain\": [\".txt\"],\n       },\n     },\n   ],\n };\n \n const handle = await window.showSaveFilePicker(options);\n const writable = await handle.createWritable();\n \n await writable.write(\"Hello World\");\n await writable.close();\n \n return handle;\n};<\/code><\/pre>\n\n\n\n

If we imagine a second button with the class save-file<\/code>, on click, we open the file picker with the method showSaveFilePicker()<\/code> and we pass in an option<\/code> object containing the type of file to be saved, here a .txt<\/code> file.<\/p>\n\n\n\n

Calling this method will also return a FileSystemFileHandle<\/code> object like in the first section. On this object, we can call the createWritable()<\/code> method that will return a FileSystemWritableFileStream<\/code> object. We can then write some content to this stream with the write()<\/code> method in which we need to pass the content.<\/p>\n\n\n\n

Finally, we need to call the close()<\/code> method to close the file and finish writing the content to disk.<\/p>\n\n\n\n

If you wanted to write some HTML code to a file for example, you would only need to change what’s in the options<\/code> object to accept \"text\/html\": [\".html\"]<\/code>  and pass some HTML content to the write()<\/code> method.<\/p>\n\n\n

Editing an existing file<\/h4>\n\n\n

If you’d like to import a file and edit it with the File System Access API,  an example code sample would look like:<\/p>\n\n\n\n

let fileHandle;\n \ndocument.querySelector(\".pick-file\").onclick = async () => {\n [fileHandle] = await window.showOpenFilePicker();\n \n const file = await fileHandle.getFile();\n const writable = await fileHandle.createWritable();\n \n await writable.write(\"This is a new line\");\n await writable.close();\n};<\/code><\/pre>\n\n\n\n

If you’ve been following the rest of this post, you might recognize that we start with the showOpenFilePicker()<\/code> and getFile()<\/code> methods to read a file and we then use createWritable()<\/code>, write()<\/code> and close()<\/code> to write to that same file.<\/p>\n\n\n\n

If the file you’re importing already has content, this code sample will replace the current content with the new one passed into the write()<\/code> method.<\/p>\n\n\n

Additional File System Access API features<\/h3>\n\n\n

Without going into too much detail, the File System Access API also lets you list files in directories and delete files or directories.<\/p>\n\n\n

Read directories<\/h4>\n\n\n

Reading directories can be done with a tiny bit of code:<\/p>\n\n\n\n

document.querySelector(\".read-dir\").onclick = async () => {\n const directoryHandle = await window.showDirectoryPicker();\n \n for await (const entry of directoryHandle.values()) {\n   console.log(entry.kind, entry.name);\n }\n};<\/code><\/pre>\n\n\n\n

If we add a new button with the class .read-dir<\/code>, on click, calling the showDirectoryPicker()<\/code> method will open the file picker and, when selecting a directory on your computer, this code will list the files found in that directory.<\/p>\n\n\n

Delete files<\/h4>\n\n\n

Deleting a file in a directory can be done with the following code sample:<\/p>\n\n\n\n

document.querySelector(\".pick-file\").onclick = async () => {\n const [fileHandle] = await window.showOpenFilePicker();\n await fileHandle.remove();\n};<\/code><\/pre>\n\n\n\n

If you want to delete a folder, you only need to make a small change to the code sample above:<\/p>\n\n\n\n

document.querySelector(\".read-dir\").onclick = async () => {\n const directoryHandle = await window.showDirectoryPicker();\n await directoryHandle.remove();\n};<\/code><\/pre>\n\n\n\n

Finally, if you want to remove a specific file when selecting a folder, you could write it like this:<\/p>\n\n\n\n

\/\/ Delete a single file named data.txt in the selected folder\ndocument.querySelector(\".pick-folder\").onclick = async () => {\n   const directoryHandle = await window.showDirectoryPicker();\n   await directoryHandle.removeEntry(\"data.txt\");\n};<\/code><\/pre>\n\n\n\n

And if you want to remove an entire folder, you would need the following lines:<\/p>\n\n\n\n

\/\/ Recursively delete the folder named \"data\"\ndocument.querySelector(\".pick-folder\").onclick = async () => {\n   const directoryHandle = await window.showDirectoryPicker();\n   await directoryHandle.removeEntry('data', { recursive: true });\n};<\/code><\/pre>\n\n\n

File System Access API browser support<\/h3>\n\n\n

At the moment, IE and Firefox don’t seem to be supporting the File System Access API. However, there exists a ponyfill<\/a> called browser-fs-access<\/a>.<\/p>\n\n\n

This browser support data is from Caniuse<\/a>, which has more detail. A number indicates that browser supports the feature at that version and up.<\/p><\/div>

Desktop<\/h4>
Chrome<\/span><\/th>Firefox<\/span><\/th>IE<\/span><\/th>Edge<\/span><\/th>Safari<\/span><\/th><\/tr><\/thead>
127<\/span><\/td>128<\/span><\/td>No<\/span><\/td>124<\/span><\/td>TP<\/span><\/td><\/tr><\/table><\/div>

Mobile \/ Tablet<\/h4>
Android Chrome<\/span><\/th>Android Firefox<\/span><\/th>Android<\/span><\/th>iOS Safari<\/span><\/th><\/tr><\/thead>
124<\/span><\/td>No<\/span><\/td>124<\/span><\/td>17.5<\/span><\/td><\/tr><\/table><\/div><\/div>\n\n\n

Wrapping up<\/h3>\n\n\n

If you’d like to try the File System Access API, check out this live demo text editor<\/a> built by Google engineers. Otherwise, if you’d like to learn more about this API and all its features, here are some resources:<\/p>\n\n\n\n