fs_shim

A portable file system library for Dart and Flutter.

fs_shim exposes a single asynchronous File/Directory/Link API (a subset of dart:io) that runs the same code on:

  • ๐Ÿ–ฅ Dart VM / Flutter native (IO)
  • ๐ŸŒ Web โ€” browser storage (through idb_shim)
  • ๐ŸŒ Web โ€” OPFS (Origin Private File System), a thin layer over the native browser API
  • ๐Ÿงช Tests / in-memory (through idb_shim)

API supported

It contains a subset of the io File/Directory API. Basically all sync methods are removed since on the web indexedDB (and OPFS) cannot be accessed in a synchronous way. All operations are asynchronous and return a Future (or a Stream for openRead/list).

Classes

  • File (create, openWrite, openRead, writeAsBytes, writeAsString, copy)
  • Link (create, target)
  • Directory (create, list)
  • FileSystem (file, link, directory, type, isFile, isDirectory, isLink)
  • FileSystemEntity (path, exists, delete, rename, absolute, isAbsolute, stat, parent)
  • FileStat
  • FileSystemEntityType
  • FileSystemException

Static method

  • Directory.current
  • FileSystemEntity.isFile
  • FileSystemEntity.isDirectory
  • FileSystemEntity.isLink

Static and File/Directory/Link constructors use fileSystemDefault which is platform dependent (web or io).

Implementations

File system Import Platform Links Random access
fileSystemDefault package:fs_shim/fs_shim.dart all io io / web
fileSystemIo package:fs_shim/fs_shim.dart io โœ…ยน โœ…
fileSystemMemory package:fs_shim/fs_shim.dart all โœ… โœ…
fileSystemWeb package:fs_shim/fs_browser.dart web โœ… โœ… (opt-in)
fileSystemOpfsWeb package:fs_shim/fs_opfs_web.dart web โŒ โŒ

ยน File links are not supported on Windows (fs.supportsFileLink returns false).

You can always check capabilities at runtime with fs.supportsLink, fs.supportsFileLink and fs.supportsRandomAccess.

Installation

Add the following dependency to your pubspec.yaml:

dependencies:
  fs_shim: ^<latest>

Usage

In memory

A simple usage example:

import 'package:fs_shim/fs_shim.dart';
import 'package:path/path.dart';

Future<void> main() async {
  final fs = fileSystemMemory;

  // Create a top level directory
  final dir = fs.directory('/dir');

  // and a file in it
  final file = fs.file(join(dir.path, 'file'));

  // create a file
  await file.create(recursive: true);
  await file.writeAsString('Hello world!');

  // read a file
  print('file: ${await file.readAsString()}');

  // use a file link if supported
  if (fs.supportsFileLink) {
    final link = fs.link(join(dir.path, 'link'));
    await link.create(file.path);

    print('link: ${await fs.file(link.path).readAsString()}');
  }

  // list dir content
  print(await dir.list(recursive: true, followLinks: true).toList());
}

The same code runs unchanged against any other implementation: just swap the fs instance.

Using IO API

Using fs_shim.dart

You can simply replace in the above example:

final fs = fileSystemMemory;

by

final fs = fileSystemIo;

If you only target io, you can still use the File and Directory constructors, replace

import 'dart:io'
    hide
    Directory,
    File,
    Link,
    FileSystemEntity,
    FileMode,
    FileStat,
    OSError,
    FileSystemException,
    FileSystemEntityType;

import 'package:fs_shim/fs_shim.dart';

by

import 'package:fs_shim/fs_io.dart';

Then a reduced set of the IO API can be used, same source code that might require some cleanup if you import from existing code.

Simple example

import 'package:fs_shim/fs_io.dart';
import 'package:path/path.dart';

Future<void> main() async {
  final fs = fileSystemDefault;
  // safe place when running from package root
  final dirPath = join(Directory.current.path, 'test_out', 'example', 'dir');

  // Create a top level directory
  final dir = Directory(dirPath);
  print('dir: $dir');
  // delete its content
  if (await dir.exists()) {
    await dir.delete(recursive: true);
  }

  // and a file in it
  final file = File(join(dir.path, 'file'));

  // create a file
  await file.create(recursive: true);
  await file.writeAsString('Hello world!');

  // read a file
  print('file: ${await file.readAsString()}');

  // use a file link if supported
  if (fs.supportsFileLink) {
    final link = Link(join(dir.path, 'link'));

    await link.create(basename(file.path));
    final linkFile = File(link.path);
    print('link: ${await linkFile.readAsString()}');
  }

  // list dir content
  print(await dir.list(recursive: true, followLinks: true).toList());
}

Browser usage

You can simply replace in the in-memory example:

final fs = fileSystemMemory;

by

import 'package:fs_shim/fs_browser.dart';

final fs = fileSystemWeb;

The default implementation on the browser uses fileSystemWeb (backed by indexedDB through idb_shim).

OPFS usage (Origin Private File System)

fs_shim also provides an implementation on top of the browser Origin Private File System (OPFS). It is a thin layer over the native navigator.storage.getDirectory() API (using dart:js_interop, so it is Wasm compatible).

import 'package:fs_shim/fs_opfs_web.dart';

final fs = fileSystemOpfsWeb;

There is a single OPFS per origin, so fileSystemOpfsWeb returns a shared instance. The same File/Directory code shown in the examples above runs unchanged on OPFS.

When to use OPFS instead of fileSystemWeb:

  • OPFS is backed directly by the browser's native file system primitives, which is well suited for storing larger files and binary content.
  • fileSystemWeb (indexedDB) supports links and (opt-in) random access, which OPFS does not.

Limitations of the OPFS implementation:

  • Links are not supported (fs.supportsLink / fs.supportsFileLink return false).
  • Random access is not supported (fs.supportsRandomAccess returns false).
  • Web only โ€” fileSystemOpfsWeb relies on navigator.storage.getDirectory(). The import is safe on any platform, but accessing the instance off the web is not supported.

Instead of the origin private root, a file system can also be rooted at any JS FileSystemDirectoryHandle (File System Access API), for example a directory picked by the user:

import 'package:fs_shim/fs_opfs_web.dart';
import 'package:web/web.dart';

final handle = await window.showDirectoryPicker().toDart; // Chromium-only
final fs = fileSystemOpfsWebWithRootHandle(handle);

The handle is used as-is (any interop binding works). Write operations require the handle to have been granted readwrite permission, and handles do not persist across page reloads unless saved by the application (e.g. in indexedDB) and permission requested again.

Random access support

Random access is supported since version 2.1.0 on io and web (indexedDB).

The default web implementation is not optimized for random access (it might change in the future). You can specify a paging parameter (initial testing is good in some scenarios with a 16Kb page; you might tune it for your needs).

import 'package:fs_shim/fs_browser.dart';

// Use default paging 16Kb
final fs =
  fileSystemWeb.withIdbOptions(options: FileSystemIdbOptions.pageDefault);

Storage remains compatible if the options are changed.

Random access is not supported by the OPFS implementation.

Utilities

  • Lightweight glob support (**, * and ? in a posix style path)
  • Copy utilities (copy files, directories recursively)

Testing

fs_shim is well suited for testing file system access in VM unit tests using fileSystemMemory, then running the exact same code on io or the web.

Dev dependencies

Stable

fs_shim: any

Bleeding edge

fs_shim:
    git: https://github.com/tekartik/fs_shim.dart

Features and bugs

  • On Windows file links are not supported (fs.supportsFileLink returns false)

  • On Windows directory link targets are absolute

  • On the web (indexedDB), the size of the file system is limited by the size limit of indexedDB databases (browser dependent)

  • On the web (OPFS), links and random access are not supported

  • Project source code

Libraries

fs
The fs_shim library.
fs_browser
fs_idb
fs_io
fs_memory
fs_mixin
fs_none
fs_opfs_web
File system implementation on top of the browser Origin Private File System (OPFS), using raw web interop.
fs_shim
utils/copy
utils/entity
utils/glob
utils/import_export
utils/io/copy
utils/io/entity
utils/io/read_write
utils/part
utils/path
utils/read_write
utils/src/utils_impl