X
Popular Searches

Web Apps Can Interact With Your Filesystem Now

Photo of Google Chrome on a smartphone
Pexels / Deepanker Verma

The File System Access API is a new browser feature that lets websites and apps directly interact with your device’s filesystem. There is now partial support for the API in recent Chrome and Opera versions.

What’s now the File System Access API used to be known as the Native File System API. It’s had a long development involving several rounds of iteration to address security concerns. The feature was turned on in the stable release of Chrome 86.

Overview

File support on the web has historically been limited to selecting a file from your device using

< input type="file">

. It’s impossible to write directly to the user’s filesystem using previous APIs. You have to use download links if you need to provide a file to the user.

The File System Access API provides a way for websites to attain read-write access to your device’s filesystem. This makes it possible to create new classes of complex web apps that interact with your files. Web-based text editors, photo libraries and media players could all load content stored locally on your device.

Advertisement

Using the API, you can enumerate the contents of directories, read data from files and write back to new and existing files stored on the user’s device. Access is provided via the FileSystemFileHandle and FileSystemDirectoryHandle handle objects, which represent files on the user’s system.

The File System Access API can only be used from pages loaded over HTTPS. It’s gated behind permissions which the user must explicitly consent to. The user will be prompted to provide consent each time you use the API. This uses the same system as permission requests for other web features, such as notifications and camera access.

Like most modern JavaScript APIs, File System Access is asynchronous. Calls to its API surface return Promises. The cleanest way to consume it is via the async/await syntax of ES7, which is what we’ll use in the following examples.

Reading a File

To read a file, you open a file picker using the window.showOpenFilePicker() function. There’s no need to use the HTML

< input type="file">

element.

The user’s operating system will render a native file picker. Once the user selects a file, the returned Promise will resolve with an array of FileSystemFileHandle objects.

To get the contents of the file, call the getFile() method of the file handle. This returns a File object, which is what you get when working with an HTML filepicker. Once you have the File, you can use its blob methods such as text() and stream() to read its data.

const [fileHandle] = await window.showOpenFilePicker();
const file = await file.getFile();
const fileData = await file.text();
console.log(fileData);
Advertisement

window.showOpenFilePicker() accepts an options object as its sole parameter. You can allow the user to select multiple files by setting the multiple option. To restrict the user to specific file types, pass the types option with an array of objects describing the allowed types. Each type object should contain description and accept properties which determine the type’s file picker label and permissible MIME types respectively.

const files = await window.showOpenFilePicker({
    multiple: true,
    types: [
        {
            description: "Image Files",
            accept: ["image/jpeg", "image/png"]
        }
    ]
});
 
for await (file of files) {
    const fileData = await file.getFile();
    console.log(fileData);
}

An “all files” option is usually shown as a permissible file type, even when the types option is set. You can disable the all files override by setting the excludeAcceptAllOption option.

Writing to a File

Once you have a file handle, you can also write to it! There’s another abstraction layer to contend with as you must first acquire a FileSystemWritableFileStream. This stream represents the file and allows you to keep writes in-memory until they’re persisted to the disk.

Here’s how to replace the contents of a file handle obtained from showOpenFilePicker():

// User selects a file
const fileHandle = await window.showOpenFilePicker();
 
// Get a `FileSystemWritableFileStream` we can write to
const writableFileStream = await fileHandle.createWritable();
 
// Write new file contents into the stream
await writableFileStream.write("Hello World");
 
// Close the writable stream - its content is now persisted to the file on disk
await writableFileStream.close();

Calling write() on a writable stream will insert text at the current cursor position in the stream. To change the position of the cursor, call the seek() method passing the new cursor position. A third method on writable streams is truncate(), which resizes the file down to a maximum size in bytes which you must specify.

Sometimes you’ll need to write to a new file. You can do this by asking the user to pick a new location for the file using window.showSaveFilePicker(). This works similarly to window.showOpenFilePicker() – it returns a FileHandle which you’ll then be able to call createWritable() on to get a writable stream.

const fileHandle = await window.showSaveFilePicker();
const writable = await fileHandle.createWritable();
await writable.write("Hello World");
await writable.close();
Advertisement

You can use the excludeAcceptAllOption and types options with window.showSaveFilePicker(). They work identically to their showOpenFilePicker() counterparts.

Directories

The File System Access API also exposes directories. The user can be prompted to select a directory using window.showDirectoryPicker(). This function accepts no parameters. It returns a promise which will resolve with a FileSystemDirectoryHandle representing the directory.

You can enumerate the contents of the directory by iterating over its values(). The values will be either file or directory handle instances.

const directoryHandle = window.showDirectoryPicker();
for await (let handle of directoryHandle.values()) {
    if (handle.type === "file") {
        // file
    }
    if (handle.type === "directory") {
        // subdirectory
    }
}

If you know the name of the file or subdirectory you’re looking for, you can use the getFileHandle() or getDirectoryHandle() methods to retrieve it. These methods both accept an options object which currently has a single property, create. Setting this to true will cause the requested file or directory to be automatically created within the filesystem if it doesn’t already exist.

You can delete files and folders from a directory using the removeEntry() method on a directory handle. This accepts the relative name of the file or subdirectory to delete. When deleting a directory, you may optionally set the recursive option to also remove its contents.

await directoryHandle.removeEntry("file.txt");
await directoryHandle.removeEntry("Subdirectory", {recursive: true});

Permissions

Permissions for the File System Access API are split into read and write components. Browers will usually show a separate permission prompt for both read and write and for each file or directory you access.

Advertisement

Once you’ve acquired permission, you can use the file or directory handle for as long as your site remains open. An exception is if the underlying resource gets changed or modified on disk, in which case the handle is invalidated.

You can check whether you still have permission to use a handle by calling its queryPermission() method:

const mode = "read";    // may also be "readwrite"
if (await fileHandle.queryPermission({mode}) === "granted") {
    // OK
}

When you need to re-request permission, use the requestPermission() method in the same manner.

You should avoid prompting for permission too often or for too many files. This is likely to create a poor user experience due to multiple successive dialogs appearing. Always prompt for permission in response to an explicit user action such as clicking a button.

The API makes no provision for persistent permission grants. Once all tabs for your site have been closed, any granted file permissions will be revoked.

Conclusion

The File System Access API is one of the most exciting browser APIs to have launched in recent months. It has the potential to transform the capabilities of web applications, making them an even more viable alternative to desktop programs.

Advertisement

Whether you’re building a text editor, photo gallery or enterprise line-of-business app, being able to interact with the device’s filesystem gives you new possibilities. Although browser support will be limited to Chrome and Opera for the foreeseable future (Mozilla views the API as “potentially very dangerous”), File System Access helps evolve the web as an application platform and further reduces the gap between the web and native apps.

James Walker James Walker
James Walker is a CloudSavvy IT contributor. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows with DevOps, CI/CD, Docker, and Kubernetes. Read Full Bio »

The above article may contain affiliate links, which help support CloudSavvy IT.