Skip to main content
Dublin Library

The Publishing Project

Reviewing The Filesystem Access API

 

The Filesystem Access API is a Chromium-only feature that's part of Project Fugu. It allows you to work opening and saving files to the local filesystem.

This post will discuss the feature and provide examples of how to open and save individual files.

This example will perform the following tasks

  1. Configure the file picker
  2. Write a function to open a file from the local file system
  3. Write a function to save the open file to disk

Before we start, I'll put the assumptions I make in the code here so you know what you're dealing with.

  • This code will only work on Chromium browsers
    • Since I don't work with any other Chromium browser (Brave, Vivaldi, etc) I have no way to know if other Chromium browsers enabled this feature
  • Since this is a demo, I did not wrap the code in feature queries. That is left as an exercise for the reader
  • We also know for certain that this feature is not available in Firefox and Safari and that it's unlikely they will implement it, so you'll have to work with a ponyfill or polyfill to make sure the feature works across browsers

Configuring the File Picker #

The pickerOpts object provides the configuration for the file picker object.

This configuration provides retrictions on the file types it will open and whether we can open multiple files.

This section also creates a filehandle placeholder variable that we'll use in later functions.

const pickerOpts = {
  types: [
    {
      description: "Text Files",
      accept: {
        "text/*": [
          ".md",
          ".txt",
          ".js",
          ".css",
        ],
        "text/html": [
          ".html",
          ".htm",
        ],
        "text/xml": [
          ".xml",
        ],
      }
    },
    {
      description: "Images",
      accept: {
        "image/*": [
          ".png",
          ".gif",
          ".jpeg",
          ".jpg",
          ".webp",
          ".avif",
          ".heif",
        ]
      }
    },
  ],
  excludeAcceptAllOption: true,
  multiple: false
};

Opening a file #

The openFile function handles opening a local file from the user's computer.

  1. Create an async function with no parameters. We'll use try/catch for this code
  2. Inside the try block, run showOpenFilePicker with the options we defined earlier and assign the result to the fileHandle variable
  3. Get the file referenced in the previous step
  4. Assign the content of the file to a variable that we can use later
  5. In the catch block handle any errors
  6. Outside the function, create a click event listener for the openButton button and assign the function as the second parameter.
// 1
async function openFile() {
  try {
    // 2
    const [ fileHandle ] = await window.showOpenFilePicker(pickerOpts);
    // 3
    const file = await fileHandle.getFile();
    // 4
    const contents = await file.text();
  } catch (err) { // 5
    console.error("there was an error: ", err.message);
  }
}

// 6
document
  .getElementById("theButton")
  .addEventListener("click", openFile);

Saving a file #

This implementation will save a file already open with the same name. It doesn't provide a way to rename the file.

  1. Create an async function
  2. If there is no value for the fileHandle variable then bail, theres's nothing to do
  3. Use a try/catch block
  4. Grab the value of the fileHandle
  5. Create a writeable stream we'll use to write the content to disk
  6. Write the content of the file to disk
  7. Close the stream
  8. In the catch block handle errors
  9. Set up a click event handler for the saveButton button element
// 1
async function saveFile() {
  // 2
  if (!fileHandle) {
    alert("No file opened yet. Please open a file first.");
    return;
  }
  // 3
  try {
    // Get the contents from the textarea
    // 4
    const contents = document.getElementById("content").value;

    // Create a writable stream
    // 5
    const writable = await fileHandle.createWritable();
    // 6
    await writable.write(contents);

    // Close the file
    await writable.close();// 7
    alert("File saved successfully!");
  } catch (err) { // 8
    console.error("Error saving file: ", err.message);
  }
}

// 9
document
  .getElementById("saveButton")
  .addEventListener("click", saveFile);

Saving a file as #

The save as functionality provides a different way save a file. This function provides a way to save the file under a different name.

It also provides a way to create new files. Since we capture the text from the text area, this function dones't rely on a file being open.

  1. Create an async function
  2. Create a try/catch block
  3. Create a saveFilePicker
  4. Set a variable with the content of the textarea element as its value
  5. Create a writeable stream
  6. Write the content we set up in step 4 to the writeable stream
  7. Close the writeable stream
  8. Update the global handle
  9. In the catch block handle errors
  10. Set up a click event listener and attach the saveAs function
// 1
async function saveAs() {
  // 2
  try {
    // Show save file picker
    // 3
    const newFileHandle = await window.showSaveFilePicker(pickerOpts);

    // Get the contents from the textarea
    // 4
    const contents = document.getElementById("content").value;

    // Create a writable stream
    // 5
    const writable = await newFileHandle.createWritable();
    // 6
    await writable.write(contents);

    // Close the file
    // 7
    await writable.close();
    alert("File saved successfully!");

    // Update the global file handle
    // 8
    fileHandle = newFileHandle;
  } catch (err) { // 9
    console.error("Error saving file as: ", err.message);
  }
}

// 10
document
  .getElementById("saveAsButton")
  .addEventListener("click", saveAs);

These three functions provide the basic functionality of an editor. There are many possibilities to move from here.

Edit on Github