
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A wrapper around the Origin private file system providing path support and an API based on Unix commands.
npm install opfsx
OPFSX provides "commands" which can be used on the main thread and within workers.
A list of all available commands can be found below with basic code snippets and example usages can be found in ./examples.
You can import all commands or specific commands individually:
import * as opfsx from "opfsx"
await opfsx.write("hello.txt", "hello world")
await opfsx.tree("/")
import {write, tree} from "opsfx"
write("hello.txt", "hello world")
tree("/")
A path like /example/path or even /example/path.txt is ambiguous and could refer to a file or directory.
It's obvious for most commands if a path should be treated as a file or directory (mkdir, ls, tree, etc), however in situations where there could be ambiguity a trailing slash is used to signal that the path is intended as a directory.
resolveResolve the given path into a FileSystemDirectoryHandle or FileSystemFileHandle.
A path with a trailing slash will resolve to a directory and paths without will resolve to a file.
The default behaviour is to throw an error if any directory/file doesn't exist, passing the create: true option will instead create all required directories and potentially the file itself, before returning the handle of the new directory/file.
import * as opfs from "opfsx"
const fileHandle = await opfs.resolve("/example/path/test.txt")
// will resolve to file as there is no trailing slash
const fileHandle2 = await opfs.resolve("/example/path/test", {create: true})
// will cause any missing directories to be created, then return the handle for the 'yet.txt' directory
const directoryHandle = await opfs.resolve("/example/not/create/yet.txt/", {create: true})
The resolve command is useful if you wish to make use of the faster FileSystemSyncAccessHandle within a worker too:
import * as opfs from "opfsx"
const fileHandle = opfs.resolve("/example/path/test.txt")
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
writeWrite a string, Blob or File to the given file path. This command will recursively create any missing directories and the file itself if missing.
Write text content to a file:
import * as opfs from "opfsx"
await opfs.write("/example/path/test.txt", "hello world")
Write a file:
import * as opfs from "opfsx"
const markdownFile = new File(["# hello world \n this is some **example** markdown"], "hello.md", {type: "text/markdown"})
await opfs.write(`/example/path/hello.md`, markdownFile)
You are not limited to just text files, you could write things like images too:
import * as opfs from "opfsx"
// assuming an input accepting image files...
const profileImage = document.getElementById("profile-image").files[0];
await opfs.write(`/images/profile/${profileImage.name}`, profileImage)
readRead a file, returning the File instance retrieved from FileSystemFileHandle.getFile().
If you are writing code within a worker, you may want to consider fetching the handle via resolve() and using FileSystemSyncAccessHandle instead.
import * as opfs from "opfsx"
const file = await opfs.read("/example/path/hello.md")
const content = await file.text()
console.log(file.name) // hello.md
console.log(content) // hello world
Construct URL from an image file:
import * as opfs from "opfsx"
const file = await opfs.read("/images/example.png")
const url = URL.createObjectURL(file)
console.log(file.name) // example.png
console.log(url) // something like 'blob:https://example.com/7bf69880-a2d0-4a12-81a8-56637cc80b23' which you could now add to an <img/> element
lsList items in a directory. Only direct children are returned by default, but you can pass the recursive: true flag to also include all subdirectories and their files.
This command always returns a flat array, even with the recursive options set. If you wish to list all content while preserving the directory structure, use the tree() command instead.
In future this command may support some form on built-in filtering, but for now it will always return all files and directories.
import * as opfs from "opfsx"
// List files and directoreis at the given location
await opfs.ls("/example/path")
// List all .md files in a directory and all subdiretories
const items = await opfs.ls("/example/nested", {recursive: true})
const markdownFiles = items.filter(item => item.kind === 'file' && item.name.endsWith('.md'))
Recursive list .md files in a directory:
import * as opfs from "opfsx"
const items = await opfs.ls("/example", {recursive: true})
const markdownFiles = items.filter(item => item.kind === 'file' && item.name.endsWith('.md'))
treeGet the "tree" structure of a directory, recursively reading all subdirectories and files.
import * as opfs from "opfsx"
await opfs.tree("/example")
mkdirCreate a directory, recursively creating any folders in the path that don't exist yet.
import * as opfs from "opfsx"
await opfs.mkdir("/example/nested/path")
rmRemove a file or directory. To remove a directory, you must pass the recursive: true option.
import * as opfs from "opfsx"
await opfs.rm("/example/hello.md")
// Pass 'recursive' option to delete a directory
await opfs.rm("/", {recursive: true})
cpCopy a file or directory to another location, will fail if any directory in destination path doesn't exist unless passing create: true option.
In cases where the destination path could be interpreted as either a file or directory, a trailing slash is used to signal that the path is a directory.
Copy a file:
import * as opfs from "opfsx"
await opfs.cp("/example/hello.md", "/example2/hello.md")
Copy an entire directory:
import * as opfs from "opfsx"
// assuming /example2 doesn't exist yet, create option is required here
await opfs.cp("/example", "/example2/nested", {create: true})
Handling ambiguous paths:
import * as opfs from "opfsx"
// test.md would be copied to a file called 'nested' with no file extension:
await opfs.cp("/example/test.md", "/example2/nested")
// test.md would be copied into the directory called 'nested'. This requires the `create` option if the directory doesn't exist yet.
await opfs.cp("/example/test.md", "/example2/nested/", {create: true})
// would copy 'folder' into the 'nested' directory
await opfs.cp("/example/folder", "/example2/nested", {create: true})
mvOPFS has no concept of moving files, so this command just runs cp() then rm().
The operation will fail if any directory in destination path doesn't exist unless passing create: true option.
Ambiguous paths are handled just like cp(), using a trailing slash to signal a directory.
import * as opfs from "opfsx"
await opfs.mv("/example/hello.md", "/example2/hello.md")
await opfs.mv("/example/nested/test1", "/example/new-folder", {create: true})
statGet metadata about a given directory or file:
import * as opfs from "opfsx"
await opfs.stat("/example/nested/test1")
Feel free to suggest features, give feedback, suggest improvements, raise bugs, open PRs and anything else.
This project is released under the MIT license.
FAQs
An OPFS wrapper providing path support and an API based on Unix commands.
We found that opfsx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.