Virtual File System#

Stability: 1 - Experimental

The node:vfs module provides a virtual file system with a node:fs-like API. It is useful for tests, fixtures, embedded assets, and other scenarios where you need a self-contained file system without touching the actual file-system.

To access it:

import vfs from 'node:vfs';
const vfs = require('node:vfs');
javascript

This module is only available under the node: scheme, and only when Node.js is started with the --experimental-vfs flag.

Security#

The VFS API is not a sandbox, permission system, or access-control mechanism. It does not isolate untrusted code from the host file system or from other Node.js capabilities. Code that can access a VirtualFileSystem instance, mount it, select its provider, or pass paths to it is trusted application code.

Mounting a VFS only redirects supported node:fs calls whose resolved paths are under the mount point. It does not prevent code from using other paths or other Node.js APIs to access resources available to the process. RealFSProvider maps VFS paths under its configured root and rejects paths that resolve outside that root, but that check is not a security boundary. ZipProvider has no real file-system paths of its own to escape; its entries only ever exist within the archive's own namespace. Do not rely on VFS to run untrusted code; use operating-system-level isolation, such as separate users, containers, or platform sandboxes, when a security boundary is required.

Basic usage#

const vfs = require('node:vfs');

const myVfs = vfs.create();
myVfs.mkdirSync('/dir', { recursive: true });
myVfs.writeFileSync('/dir/hello.txt', 'Hello, VFS!');

console.log(myVfs.readFileSync('/dir/hello.txt', 'utf8')); // 'Hello, VFS!'
cjs

vfs.create() returns a VirtualFileSystem instance backed by a MemoryProvider by default. The instance exposes synchronous, callback-based, and promise-based file system methods that mirror the shape of the node:fs API. All paths are POSIX-style and absolute (starting with /).

By default, the file tree is private to the VFS instance. To expose it through the global node:fs module, require(), and import, call vfs.mount(); call vfs.unmount() (or rely on a using declaration) to detach again.

vfs.create([provider][, options])#

Convenience factory equivalent to new VirtualFileSystem(provider, options).

const vfs = require('node:vfs');

// Default in-memory provider
const memoryVfs = vfs.create();

// Explicit provider
const realVfs = vfs.create(new vfs.RealFSProvider('/tmp/vfs-root'));
cjs

Class: VirtualFileSystem#

A VirtualFileSystem wraps a VirtualProvider and exposes a node:fs-like API. Each instance maintains its own file tree.

new VirtualFileSystem([provider][, options])#

  • provider <VirtualProvider> The provider to use. Default: new MemoryProvider().
  • options <Object>
    • emitExperimentalWarning <boolean> Whether to emit the experimental warning. Default: true.

vfs.mount()#

  • Returns: <string> The absolute mount point.

Mounts the virtual file system and returns the resulting mount point. After mounting, files in the VFS can be accessed through the node:fs module and resolved through require() and import using paths under the returned mount point.

Mount points always live inside a reserved namespace that cannot have child file system entries, so virtual paths never conflate with (or shadow) real paths. The virtual path scheme is subject to change and users should not manually construct them based on assumptions. Instead, obtain them from what vfs.mount() returns or vfs.mountPoint.

const vfs = require('node:vfs');
const fs = require('node:fs');

const myVfs = vfs.create();
myVfs.writeFileSync('/data.txt', 'Hello');
const mountPoint = myVfs.mount();
// e.g. '/dev/null/vfs/0'

fs.readFileSync(`${mountPoint}/data.txt`, 'utf8'); // 'Hello'
cjs

Each VirtualFileSystem instance may be mounted at most once at a time. Attempting to mount an already-mounted instance throws ERR_INVALID_STATE. Because each instance mounts inside its own per-layer namespace, mounts from different instances can never overlap.

The VFS supports the Explicit Resource Management proposal. Use a using declaration to unmount automatically when leaving scope:

const vfs = require('node:vfs');
const fs = require('node:fs');

let mountPoint;
{
  using myVfs = vfs.create();
  myVfs.writeFileSync('/data.txt', 'Hello');
  mountPoint = myVfs.mount();

  fs.readFileSync(`${mountPoint}/data.txt`, 'utf8'); // 'Hello'
} // VFS is automatically unmounted here

fs.existsSync(`${mountPoint}/data.txt`); // false
cjs

vfs.unmount()#

Unmounts the virtual file system. After unmounting, virtual files are no longer reachable through node:fs, require(), or import. The same instance may be mounted again by calling mount().

This method is idempotent: calling unmount() on a VFS that is not currently mounted has no effect.

vfs.mounted#

true while the VFS is mounted; false otherwise.

vfs.mountPoint#

The current mount point as an absolute string (the value returned by the last vfs.mount() call), or null when the VFS is not mounted.

vfs.mountPointURL#

The current mount point as a file: URL string (the vfs.mountPoint path converted with url.pathToFileURL()), or null when the VFS is not mounted.

This is a convenience for addressing mounted files with URL-based APIs such as dynamic import():

import vfs from 'node:vfs';

const myVfs = vfs.create();
myVfs.writeFileSync('/mod.mjs', 'export const value = 42;');
myVfs.mount();

const { value } = await import(`${myVfs.mountPointURL}/mod.mjs`);
console.log(value); // 42

myVfs.unmount();
mjs

vfs.provider#

The provider backing this VFS instance.

vfs.readonly#

true when the underlying provider is read-only.

APIs#

VirtualFileSystem implements the following methods, with the same signatures as their node:fs counterparts:

Synchronous API#
  • existsSync(path)
  • statSync(path[, options])
  • lstatSync(path[, options])
  • readFileSync(path[, options])
  • writeFileSync(path, data[, options])
  • appendFileSync(path, data[, options])
  • readdirSync(path[, options])
  • mkdirSync(path[, options])
  • rmdirSync(path)
  • unlinkSync(path)
  • renameSync(oldPath, newPath)
  • copyFileSync(src, dest[, mode])
  • realpathSync(path[, options])
  • readlinkSync(path[, options])
  • symlinkSync(target, path[, type])
  • accessSync(path[, mode])
  • rmSync(path[, options])
  • truncateSync(path[, len])
  • ftruncateSync(fd[, len])
  • linkSync(existingPath, newPath)
  • chmodSync(path, mode)
  • chownSync(path, uid, gid)
  • lchownSync(path, uid, gid)
  • utimesSync(path, atime, mtime)
  • lutimesSync(path, atime, mtime)
  • mkdtempSync(prefix)
  • opendirSync(path[, options])
  • openAsBlob(path[, options])
  • File-descriptor ops: openSync, closeSync, readSync, writeSync, fstatSync
  • Streams: createReadStream, createWriteStream
  • Watchers: watch, watchFile, unwatchFile
Callback API#

readFile, writeFile, stat, lstat, readdir, realpath, readlink, access, open, close, read, write, rm, fstat, truncate, ftruncate, link, mkdtemp, opendir. Each takes a Node.js-style callback (err, ...result) => {}.

Promise API#

vfs.promises exposes the promise-based variants:

const vfs = require('node:vfs');

async function example() {
  const myVfs = vfs.create();
  await myVfs.promises.writeFile('/file.txt', 'hello');
  const data =