File System Access API: How VSCode.dev Edits Local Files in the Browser
Tags: webdev, javascript, browser, vscode
Published: 2023-09-25 07:30:00

Have you ever given [vscode.dev](http://vscode.dev/) a try? It's the online version of the popular code editor, vscode, that so many people love. With [vscode.dev](http://vscode.dev/), you get all the same capabilities as the offline version of the app. You can do things like version control, search through files, install extensions, lint and format your code and more.

One of the coolest things about [vscode.dev](http://vscode.dev/) is that you can easily **open a project folder directly from your local machine**. Once you've done that, you can seamlessly continue your development process right from your browser. You can add new files, make changes to existing ones, and do so much more, all while working within the browser. And all those changes you make are **automatically reflected in your project's folder on your local file system**.

![vscode.dev running in the browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/51awtayjfe1i1yzj2epp.png)

But, how does [vscode.dev](http://vscode.dev/) manages to read and change your local files? Thanks to the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API), which makes all of this possible!

## Introducing File System Access API
The File System Access API allows online applications, like [vscode.dev](http://vscode.dev/), to interact with your computer's files in a safe and controlled way. Without the need for any server-side intervention.

It's like a virtual bridge that connects the online world to your local files. With this API, [vscode.dev](http://vscode.dev/) can ask your permission to access specific folders on your computer. Once you give it the green light, it can read files from those folders, make changes to them, and even save new files there.

### Reading A File

![Reading a file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l68evmet26d4kimns70.gif)
When you click "open file" in [vscode.dev](http://vscode.dev), a file picker appears, letting you choose any file from your computer. This is made possible by the `window.showOpenFilePicker` method. This method shows a file picker that allows the user to select one or more files from his local machine. And returns an array of [FileSystemHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle)s *(We will call it* `fileHandle` *for ease)*. The `fileHandle` object contains some metadata about the selected file as the file type and file name and it acts as a reference for the file. This `fileHandle` will be used later on to get the file itself and its contents.

```javascript
const [fileHandle] = await window.showOpenFilePicker();
```

The `showOpenFilePicker` method also accepts an options parameter that can customize how it behaves, like allowing choosing multiple files, accepting certain file types only, etc. For example, you can allow selecting multiple files or restrict it to certain file types only. Giving you more control over the file picker.

```javascript
const pickerOptions = {
    types: [
        {
          description: "Images",
          accept: {"image/*": [".png", ".gif", ".jpeg", ".jpg"]},
        },
    ],
    multiple: true,
}

const [fileHandle] = await window.showOpenFilePicker(pickerOptions);
```

Once you have the `fileHandle` for the file you picked. You can use it to get the file itself, and then you can read the contents of the file.

```javascript
const [fileHandle] = await window.showOpenFilePicker(pickerOptions);
const file = await fileHandle.getFile();
const content = await file.text();
```

### Reading Folder

Reading a folder is quite similar to reading files. Instead of using the `showOpenFilePicker` method, we use `showDirectoryPicker`. It shows a popup where users can choose a folder. Once they select a folder, it gives us a `FileSystemDirectoryHandle` *(We will call it* `DirectoryHandle` *for ease)*. This `directoryHandle` is like the `fileHandle` we talked about earlier. It acts as a reference for the chosen folder and gives us some metadata about it, like its kind and its name.

```javascript
const directoryHandle = await window.showDirectoryPicker();
```

To access the contents of the directory, we use the `directoryHandle.values()` method. This method returns an asynchronous iterator, which means we can go through the directory's contents one by one. The directory can contain both files and other folders. So, each item returned by the `directoryHandle.values()` method can be either a `fileHandle` or another `folderHandle` and we can distinguish between the two by checking the `kind` property.

```javascript
const directoryHandle = await window.showDirectoryPicker();
for await (const entry of directoryHandle.values()) {
    if (entry.kind === 'file') {
        const fileHandle = entry; 
        // Do whatever we need with the file
    }
        
    if (entry.kind === 'directory') {
        const subdirectoryHandl = entry;
        // Do whatever we need with the folder 
    }
}
```

### Saving Changes To Opened File

So, when we're done making all the changes to the file and want to save it back to our computer, we remember that special `fileHandle` we got when we first chose the file. It's like our key to access that file again.

Using that `fileHandle`, we create something called a **writable stream** on the file. Then, we use this stream to write our changes to the file.

The writable stream doesn't immediately change the original file. Instead, it writes everything to a temporary file first. Only when the stream is completely done, and we close it, does it swap the temporary file with our intended file. That's when our changes become permanent!

So, to make sure our changes happen, we need to close the stream after writing to the file. This way, our modifications take effect, and the file on our computer gets updated.

```javascript
const writableStream = await fileHandle.createWritable();
await writableStream.write(newContent);
await writableStream.close();
```

### Creating New File

What if we want to make a completely new file that isn't already there in our computer's files? no worries! We've learned about the `showOpenFilePicker` method, which helps us open files from our local system. Now, we have a similar method `showSaveFilePicker` that helps us in doing so. The difference is, Instead of opening a dialog to choose a file to open, `showSaveFilePicker` opens a dialog to save a brand-new file.

![Creating a new file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfp6jwk2pfua1haia8f8.png)

Using this method is straightforward! It opens the save dialog, and when you choose a location to save the file, it gives you the `fileHandle`. With that `fileHandle`, you can **read the file** or **save changes to it**, just like we did before.

```javascript
const fileHandle = await window.showSaveFilePicker();
```

It also accepts an optional parameter. With this parameter, you can customize some things, like suggesting a name for the new file and setting its extension.

```javascript
const options = {
    suggestedName: "NasserSpace.txt"
};
const fileHandle = await window.showSaveFilePicker(options);
```

## Browser Permissions Needed!

The operating system won't let any website read or write to the user's disk without his permission. It's like when you install a new app on your mobile, and it asks for your permission to access things like files, camera, microphone, and so on.

In the same way, when a user visits your website, it needs his approval to access his local file system. The user is in control, and they have the final say on whether they want to grant your web app permission or not.

There are two types of file permissions: **"read"** and **"readwrite"**. With "read" permission, you can only read the file's contents, while **"readwrite"** permission allows you to both read and write to the file.

### Grant Permission Once

When the user picks a file with the `showOpenFilePicker` method, the browser automatically grants us **"read"** permission for that file. It makes sense since the user intentionally selected that file to read.

But when we read a whole folder using `showDirectoryPicker`, the browser requests the user's permission to access all the files within the folder with **"read"** permission.

This process only happens once. Once the user grants permission, we can read all the files without asking again.

![Browser Asking For read Permissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wu82dprnftpwg3lwuzdx.png)

In the same way, When you try to modify a file or folder for the first time, the browser will ask for the user's permission to do so. The permission that we need here is `readwrite` permission. The user only needs to grant this permission once. Once it's granted, you can freely **modify** and **save new files** to that folder and its subfolders without the user being asked again.

![Browser Asking For readwrite permissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94cprwrwlgbexx84y5ra.png)

Getting permission is a one-time setup that makes future accessing or writing to the disk smooth and hassle-free!

### Checking Users Permissions

In certain situations, we may need to check if the user has allowed us to access their local files. If they haven't granted permission yet, we can take the opportunity to explain to him why we need it and how we'll handle their files, to put their minds at ease. We can also guide the user on how to grant the permission, or if required, ask them again for permission.

To check permissions for a specific file, you can use the `queryPermission` method on the `fileHandle` you want to access.

The `queryPermission` method returns one of those values:

* **"granted"**: permission has already been granted by the user.
    
* **"denied"**: permission has been denied by the user.
    
* **"prompt":** user has not explicitly granted or denied permission yet.
    

```javascript
const options = { mode: 'readwrite' };
const permissionStatus = await fileHandle.queryPermission(options);

if(permissionStatus == 'denied') {
    // Show user why do we need his permission
}

if(permissionStatus == 'prompt') {
    // Request User Permission
}
```

### Requesting User Permission

When we try to read or modify a file, the browser automatically shows a dialog asking for user permission. But sometimes, we might want to ask for permission explicitly. To do that, we can use a new function called `requestPermission`.

This method also accepts an optional parameter that allows us to specify whether we're asking for `read` **permission** only or `readwrite` **permission**. It returns the same result as the `queryPermission` method: either **"granted"**, **"denied",** or **"prompt"**.

```javascript
const options = { mode: 'read' };
const permissionStatus = await fileHandle.requestPermission(options);
```

> _If the user has already granted access to that file, `requestPermission` won't show the request permission dialog again. Instead, it will directly return "granted"._

## Browsers Compatibility

**Supported:** Chrome, Microsoft Edge, Opera, Brave, Arc, and other Chromium-based browsers.

**Not Supported** *(yet)*: Firefox, Safari.

## Opening Doors to Innovation 💡

Building VSCode on top of the File System Access API is just one awesome application, but there's so much more we can do with this powerful tool! Some cool examples:

1. **File Management Apps:** We can create handy apps that help you organize your local files for better productivity. No more searching through cluttered folders!
    
2. **Videos and Photos Editors:** A web-based media editor that uses web technologies like web assembly and WebGPU along with the File System Access API. It lets you edit videos and photos right on your computer without any hassle.
    
3. **File Type Conversion Apps:** We can build a web app that handles file conversions from one format to another without sending them to a server. Quick and easy!
    

The possibilities are endless! With the File System Access API, we have a wide door open for creating and innovating all kinds of awesome applications. Let your imagination run wild!