JavaScript: store and read files with the Origin Private File System

In this post you will learn how to use the Origin Private File System with JavaScript to store, read and remove any file on the web browser.

Sistema de archivos en la web con JavaScript y OPFS
File System in the web browser with JavaScript and OPFS

You may try the demo here: https://stackblitz.com/edit/vitejs-vite-hl34zf?file=index.html

As I said before, the Origin Private File System has arrived to revolutionize things with JavaScript. Thanks to OPFS we can have a complete file system with JavaScript directly in the web browser.

With this new technology, we can write any type of file in the web browser, as well as download it later. All of this without depending on localStorage or similar things; It is a different technology.

Text documents, images, videos and even databases can be saved, and there is no need to ask the user for permission or confirmation, everything is transparent.

Root folder

In order to access to the root folder we call navigator.storage.getDirectory(). This is the root folder, here we can create more folders and files. I will create a folder with the following code:

const directoryName = "pictures";
const opfsRoot = await navigator.storage.getDirectory();
const picturesFolder = await opfsRoot.getDirectoryHandle(directoryName, {
    create: true,
});

From here, I’m going to store, read, and delete from picturesFolder. Keep in mind that a “handle” is not the file or directory itself, but its handle as the name suggests.

To get the file we call getFile and similar things, as we will see later.

Store file in the web browser with JavaScript

Now that we have a directory handle we can call getFileHandle (setting the create option to true), then call createWritable and finally calling write.

You should pass an ArrayBuffer or blob to the write function, we do not need to convert the files. The file can come from anywhere; for example, from a web server or from a file type input.

In this example I am going to use a file type input and the function looks like this:

const storeFile = async (file, folder) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = async () => {
            const fileName = file.name;
            const fileHandle = await folder.getFileHandle(fileName, {
                create: true,
            });
            const writable = await fileHandle.createWritable();
            await writable.write(reader.result);
            await writable.close();
            resolve();
        };
        reader.onerror = error => {
            reject(error);
        }
        reader.readAsArrayBuffer(file);
    });
}

We can call this function from the input change event or any event (like a click on a button):

$fileSelect.addEventListener("change", async () => {
    const files = $fileSelect.files;
    for (const file of files) {
        await storeFile(file, picturesFolder)
    }
    $fileSelect.value = "";
    renderFiles(picturesFolder);
});

List files in a folder

Again, once we have an OPFS handler, we can iterate it with a for await loop, getting the name and the handler of each file inside that directory or folder. The simplest for await loop is like this:

for await (let [name, handle] of folder) {
//
}

Here I am using vanilla JavaScript to render the stored files, adding the listeners to download and remove each file.

const renderFiles = async (folder) => {
    while ($mainContainer.firstChild) {
        $mainContainer.removeChild($mainContainer.firstChild);
    }
    for await (let [name, handle] of folder) {
        const container = Object.assign(document.createElement("div"));
        const anchorFileName = Object.assign(document.createElement("a"), {
            textContent: name,
            href: "javascript:void(0)",
            classList: ["file"],
        });
        const anchorRemove = Object.assign(document.createElement("a"), {
            textContent: "Eliminar",
            href: "javascript:void(0)",
            classList: ["remove"],
        });
        anchorFileName.addEventListener("click", () => {
            downloadFile(name, folder);
        });
        anchorRemove.addEventListener("click", async () => {
            await removeFile(name, folder);
            await renderFiles(folder);
        })
        container.appendChild(anchorFileName);
        container.appendChild(anchorRemove);
        $mainContainer.append(container);
    }
};

Note: Although I use the word “download” here, there is no need for servers or similar things. Everything is offline, what we do when downloading the file is read it from the browser and ask the user the location where they want to save it.

Download a file from Origin Private File System

Previously we learned how to store a file in the browser with JavaScript and also how to obtain the list of files that exist within a directory.

Let’s see how to recover a file. Here we are going to download it, but we can send it to a server or make anything else with it.

const downloadFile = async (fileName, folder) => {
    const fileHandle = await folder.getFileHandle(fileName);
    const file = await fileHandle.getFile();
    const a = document.createElement("a");
    const objectUrl = URL.createObjectURL(file);
    a.href = objectUrl;
    a.download = fileName;
    a.click();
    URL.revokeObjectURL(objectUrl);
}

The actual file is in the file constant, and then we simply create a link to download it with URL.createObjectURL. Here we could do anything else with the file, for example, show it to the user, send it to a server and so on.

Remove file

Finally let’s see how to delete a file previously stored in the web browser with JavaScript. Once we have its handler we just invoke remove (note that, at the time of writing this post, remove only works in Chrome and Edge):

const removeFile = async (fileName, folder) => {
    if (!('remove' in FileSystemFileHandle.prototype)) {
        return alert("Tu navegador no soporta la eliminación de archivos");
    }
    if (!confirm('¿De verdad quieres eliminarlo?')) {
        return;
    }
    const fileHandle = await folder.getFileHandle(fileName);
    await fileHandle.remove();
}

Why did I say it is a revolution?

OPFS has brought with it two things: the file system itself, and the possibility for several existing libraries to use it.

What I liked the most is that SQLite3 now works perfectly in the web browser, so we are closer to creating serverless web applications (for cases when no server is needed) since we can have a database and also store files.

Conclusion and example

I have shown you the most important functions to use the Origin Private File System, it is a very simple example but it tries to teach you the most important thing. If you want to see the full code go to my GitHub: https://github.com/parzibyte/hello-opfs-js

You can try the example on any server of your choice, just make sure to send the COOP and COEP headers. When you run the example, the list of files and a field will appear so you can store more files (as in the image at the beginning of the post)

From this example we can do more complex things, since we already have an entire file system in the web browser that we can access with JavaScript.

We can even encrypt the files or display them as an image or video; the possibilities are endless.

I hope to bring a more complete application in the future, in the meantime I leave you with more JavaScript posts.


I am available for hiring if you need help! I can help you with your project or homework feel free to contact me.
If you liked the post, show your appreciation by sharing it, or making a donation

Leave a Comment