npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

use-fs-access

v1.3.2

Published

A React hook that builds on top of the File System Access API to enable easy file and directory operations in modern browsers.

Readme

use-fs-access

🗂️ React hook library that builds on top of the File System Access API, offering a clean and simple way to interact with the user's local file system from within a React application.

This hook enables React developers to easily open directories, read and write files, create or delete files and directories, and build powerful file-based workflows directly in the browser — all without leaving the comfort of React's ecosystem.

Additional advanced features include lazy-loading directory structures, file watching with a polling mechanism, and batch file processing. The library also supports persisting access to previously opened directories via built-in IndexedDB storage, and offers customizable file and directory filtering (with default filters for node_modules, .git, and dist). These features make it ideal for a variety of use cases, including file managers, code editors, offline-first applications, and any other app that requires seamless local file access.

⚠️ Please note that the File System Access API is not supported in all browsers. It is currently supported in modern Chromium-based browsers (e.g., Google Chrome, Microsoft Edge) and a few others. Be sure to check the compatibility table for the most up-to-date information on supported browsers.


📦 Installation

npm install use-fs-access
# or
yarn add use-fs-access

✨ Features

  • Open, expand, create, or delete directories
  • Create, read, write, and delete files
  • Watch files and directories (via polling)
  • Lazy-load directory contents
  • Save and access previously opened directories
  • Filter files and directories
  • Fully extensible filter and storage mechanism
  • Built-in TypeScript support

🚀 Quick Start

Here's a full demo component showcasing how to use use-fs-access to open directories, view and modify files, and interact with the file system.

✅ Make sure the browser supports the File System Access API.

import { useState } from "react";
import useFileSystemAccess from "use-fs-access";
import { FileOrDirectoryInfo } from "use-fs-access/core";
import { buildFileTree, FileTreeNode } from "use-fs-access/extensions";
import { defaultDirectoryStore } from "use-fs-access/stores";

function FileSystemAccessDemo() {
  const {
    isSupported,
    files,
    pending,
    openDirectory,
    expandDirectory,
    openFile,
    closeFile,
    createDirectory,
    writeFile,
    deleteFile,
    deleteDirectory,
    directoryAccessor,
  } = useFileSystemAccess({
    enableFileWatcher: true,
    fileWatcherOptions: {
      debug: true,
      pollInterval: 250, // [ms]
      // batchSize: 50, [ms]
      // cacheTime: 5000, [ms]
    },
    filters: [
      // - gitIgnoreFilter, (apply .gitignore rules)
      // - gitFolderFilter, (excludes .git folder)
      // - distFilter       (excludes node_modules, dist, ...)
      // - defaultFilters,  (includes all the above by default)
    ],
    store: defaultDirectoryStore, // - use IndexedDb to store recently opened directories by default
    // FILE WATCHER
    onFilesAdded: (newFiles: Map<string, FileOrDirectoryInfo>) => {}, // - Track when new files are added
    onFilesDeleted: (deletedFiles: Map<string, FileOrDirectoryInfo>) => {}, // - Track when files are deleted
    onFilesModified: (modifiledFiles: Map<string, FileOrDirectoryInfo>) => {}, // - Track when opened files are modified
  });

  const welcomeMessage =
    "Hello from the demo!\nClick 'Open Directory' to choose a folder to explore.\n\n" +
    "Click 'Open' button on a file to view its contents, or 'Expand' on a folder to load more files.";
  const [newFolderPath, setNewFolderPath] = useState("");
  const [newFilePath, setNewFilePath] = useState("");
  const [fileContent, setFileContent] = useState(welcomeMessage);

  const fileTree: FileTreeNode = buildFileTree(files);

  const renderFileTree = (node: FileTreeNode, depth = 0) => {
    const isDir = node.kind === "directory";
    const indent = { paddingLeft: `${depth * 20}px` };

    return (
      <div key={node.path} style={indent}>
        <strong>
          {isDir ? "📁" : "📄"} {node.name} {node.opened && "(opened)"}
        </strong>
        <span style={{ marginLeft: "20px" }}>
          {isDir ? (
            <>
              {!node.loaded && (
                <button onClick={async () => await expandDirectory(node.path)}>
                  Expand
                </button>
              )}

              <button
                onClick={async () => {
                  const yes = confirm(
                    "Are you sure you want to delete this directory?\nAll files and subdirectories will be permanently removed."
                  );
                  if (yes) await deleteDirectory(node.path);
                }}
              >
                Delete
              </button>
            </>
          ) : (
            <>
              <button
                onClick={async () => {
                  if (node.opened) {
                    await closeFile(node.path);
                    setFileContent(welcomeMessage);
                  } else {
                    const file = await openFile(node.path);
                    setFileContent(file.content);
                  }
                }}
              >
                {node.opened ? "Close" : "Open"}
              </button>
              <button
                onClick={async () => {
                  const yes = confirm(
                    "Delete this file?\nThis cannot be undone."
                  );
                  if (yes) await deleteFile(node.path);
                }}
              >
                Delete
              </button>
            </>
          )}
        </span>

        {isDir &&
          node.children &&
          node.children.map((child) => renderFileTree(child, depth + 1))}
      </div>
    );
  };

  return (
    <div>
      <h1>File System Access API Demo</h1>
      <hr />
      {!isSupported ? (
        <p style={{ color: "red" }}>API not supported on this browser</p>
      ) : (
        <>
          <div style={{ marginBottom: "20px" }}>
            <button
              onClick={async () => await openDirectory({ save: true })}
              disabled={pending}
            >
              📂 Open Directory
            </button>
          </div>

          <>
            <div style={{ marginBottom: "10px" }}>
              <input
                placeholder="New folder path (ex. /{path-from-root}/dir1 )"
                value={newFolderPath}
                onChange={(e) => setNewFolderPath(e.target.value)}
                style={{ width: "300px", marginRight: "5px" }}
              />
              <button
                onClick={async () => {
                  if (newFolderPath) {
                    const name = newFolderPath.substring(
                      newFolderPath.lastIndexOf("/") + 1
                    );
                    const parentPath = newFolderPath.substring(
                      0,
                      newFolderPath.indexOf("/")
                    );
                    await createDirectory(name, parentPath);
                    setNewFolderPath("");
                  }
                }}
              >
                ➕ Create New Folder
              </button>
            </div>

            <div style={{ marginBottom: "10px" }}>
              <input
                placeholder="New file path (ex. /root/file.txt )"
                value={newFilePath}
                onChange={(e) => setNewFilePath(e.target.value)}
                style={{ width: "300px", marginRight: "5px" }}
              />
              <button
                onClick={async () => {
                  if (newFilePath) {
                    await writeFile(newFilePath);
                    setNewFilePath("");
                  }
                }}
              >
                💾 Create New File
              </button>
            </div>
          </>

          <div style={{ marginTop: "20px" }}>
            <h2>File Tree:</h2>
            {files.size === 0 ? (
              <i>No directory opened yet.</i>
            ) : (
              <div>{renderFileTree(fileTree)}</div>
            )}
          </div>
          <hr />
          <div>
            {fileContent != undefined && (
              <textarea
                readOnly
                style={{ minWidth: "500px", minHeight: "300px" }}
                value={fileContent}
              ></textarea>
            )}
          </div>
        </>
      )}
    </div>
  );
}