A high-performance, Node.js-compatible file system (fs) module for React Native, powered by Nitro Modules.
- 🚀 Extreme Performance: Low-overhead communication via JSI and Nitro.
- 📦 Zero-copy Binary Data: Efficiently handle large files using
ArrayBufferandNitroBuffer. - 🛠️ Node.js Compatible API: Supports
fsmethods likereadFile,writeFile,mkdir,stat, and more (Sync & Async). - 🏗️ Streaming Support: Built-in
ReadStreamandWriteStreamfor efficient data processing. - 📂 Directory & Watcher: Support for directory iteration and file system watching.
- 🌐 URL Path Support: Handle
file://,bookmark://(iOS), andcontent://(Android) URIs. - 🔓 Native Picker: Built-in cross-platform file and directory picker with persistent access support.
| Feature | react-native-fs |
expo-file-system |
react-native-blob-util |
Nitro File System |
|---|---|---|---|---|
| Architecture | Legacy Bridge | Turbo Modules / Expo | Legacy Bridge / C++ | Nitro (JSI / C++) |
| Communication | High (Base64/JSON) | Medium | Medium | Ultra Low (Direct JSI) |
| Binary Handling | Slow (Base64) | Fast | Fast | Top (Zero-copy Buffers) |
| API Style | Custom | Custom | Stream / Mixed | Node.js fs Compatible |
| Sync API | Poor | None | Limited | Full Support |
npm install react-native-nitro-file-system react-native-nitro-modules react-native-nitro-buffer
# or
yarn add react-native-nitro-file-system react-native-nitro-modules react-native-nitro-buffer| Category | Status | Supported Methods |
|---|---|---|
| File I/O | ✅ 100% | open, read, write, close, readFile, writeFile, appendFile, truncate, fsync, readv, writev |
| Metadata | ✅ 100% | stat, lstat, fstat, access, utimes, futimes, lutimes (including bigint support) |
| Directories | ✅ 100% | mkdir, rmdir, readdir, rm, mkdtemp, opendir (Dir class), cp |
| Permissions | ✅ 100% | chmod, fchmod, lchmod, chown, fchown, lchown |
| Links | ✅ 100% | link, symlink, readlink, realpath |
| Watching | ✅ 100% | watch, watchFile, unwatchFile |
| Streams | ✅ 100% | createReadStream, createWriteStream |
| Native Picker | ✅ 100% | pickFiles, pickDirectory |
| Promises | ✅ 100% | fs.promises.* (Full coverage) |
The library provides a Paths object containing common system directory paths for cross-platform use.
import { Paths } from 'react-native-nitro-file-system';
console.log(Paths.cache); // App caches directory
console.log(Paths.document); // App documents directory
console.log(Paths.temp); // Temporary directory
console.log(Paths.library); // (iOS only) Library directory
console.log(Paths.mainBundle); // (iOS only) Main bundle directory
console.log(Paths.externalCache); // (Android only) External caches
console.log(Paths.externalStorage); // (Android only) External storage root| Path Property | Platform | Description |
|---|---|---|
cache |
All | The absolute path to the app's caches directory. |
document |
All | The absolute path to the app's document directory. |
temp |
All | The absolute path to the system temporary directory. |
download |
All | The absolute path to the downloads directory. |
pictures |
All | The absolute path to the pictures directory. |
library |
iOS | The absolute path to the NSLibraryDirectory. |
mainBundle |
iOS | The absolute path to the main bundle directory. |
externalCache |
Android | The absolute path to the external caches directory. |
external |
Android | The absolute path to the external files directory. |
externalStorage |
Android | The absolute path to the external storage root directory. |
import fs from 'react-native-nitro-file-system';
// Write a file (Sync)
fs.writeFileSync('/path/to/file.txt', 'Hello Nitro!');
// Read a file (Async)
fs.readFile('/path/to/file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data); // "Hello Nitro!"
});
// Using Promises
const content = await fs.promises.readFile('/path/to/file.txt', 'utf8');
### Recursive Copying (cp / cpSync)
The library supports high-performance recursive directory copying, matching the behavior of Node.js `fs.cp`.
```typescript
import fs from 'react-native-nitro-file-system';
// Sync recursive copy
fs.cpSync('/path/to/src', '/path/to/dest', { recursive: true, force: true });
// Async recursive copy
await fs.promises.cp('/path/to/src', '/path/to/dest', { recursive: true });
#### `CpOptions`
| Option | Type | Default | Description | Supported |
| :--- | :--- | :--- | :--- | :--- |
| `recursive` | `boolean` | `false` | Copy directories recursively. | ✅ |
| `force` | `boolean` | `true` | Overwrite existing files. | ✅ |
| `dereference`| `boolean` | `false` | Dereference symlinks. | ✅ |
| `errorOnExist`| `boolean` | `false` | Throw error if destination exists and `force` is false. | ✅ |
| `preserveTimestamps` | `boolean` | `false` | Preserve mtime and atime from source. | ✅ |
| `filter` | `Function`| `undefined` | Filter function for files/directories. | ❌ |
| `mode` | `number` | `0` | Modifiers for copy operation. | ❌ |
### URL-style Path Support
The library provides robust support for URL-style paths and standard `URL` objects across all API methods. This is particularly useful when working with Expo or React Native components that return `file://` URIs.
- **Automatic Decoding**: Percent-encoded characters (like `%20` for spaces) are automatically decoded.
- **Protocol Handling**: Both `file://` and `file:/` prefixes are supported.
- **URL Objects**: You can pass standard JavaScript `URL` objects directly to any `fs` method.
```typescript
import fs from 'react-native-nitro-file-system';
// 1. Using file:// URI strings with percent encoding
const uri = 'file:///path/to/my%20document.txt';
fs.writeFileSync(uri, 'Content with spaces');
// 2. Using standard URL objects
const url = new URL('file:///path/to/config.json');
const data = fs.readFileSync(url, 'utf8');
// 3. Works with all APIs including Streams and Promises
const promiseContent = await fs.promises.readFile(new URL('file:///path/abc.txt'));
const stream = fs.createReadStream('file:///path/to/large_file.bin');
The library supports Android content:// URIs for the following operations:
- Read/Write:
fs.open,fs.read,fs.write,fs.readFile,fs.writeFile. - Metadata:
fs.stat,fs.lstat,fs.access(existence check). - Cleanup:
fs.unlink,fs.rm. - Utility:
fs.copyFile.
Note: Directory operations (mkdir, readdir, rename, chmod) are not supported for content:// URIs as they are virtual resources.
// Read directly from a content:// URI
const contentUri = 'content://com.android.providers.media.documents/document/image%3A1234';
const data = fs.readFileSync(contentUri, 'base64');
// Get file stats
const stats = fs.statSync(contentUri);
console.log(stats.size);The library provides direct, high-performance access to Android's assets directory via the asset:// protocol.
- Read-Only: Since APK assets are packaged as read-only, only read-related operations are supported.
- Path format: Use
asset://followed by the relative path within your project'ssrc/main/assetsdirectory. - Directory Support: You can list files in an asset directory using
fs.readdiror recursively copy an entire asset directory usingfs.cp. - copyFile Support: You can use
asset://as the source path infs.cpandfs.copyFileto extract assets to other directories.
import fs from 'react-native-nitro-file-system';
// Recursive copy a bundled asset directory to documents
fs.cpSync('asset://web-bundle/ruffle', `${Paths.document}/ruffle`, { recursive: true });
// Copy an asset file to the documents directory
fs.copyFileSync('asset://data/db.sqlite', `${Paths.document}/app.db`);
// Read a bundled configuration file
const config = fs.readFileSync('asset://config/settings.json', 'utf8');
// List files in an asset directory
const files = fs.readdirSync('asset://web-content');
// Check if an asset exists
if (fs.existsSync('asset://models/default.bin')) {
const stats = fs.statSync('asset://models/default.bin');
console.log(`Asset size: ${stats.size}`);
}Note: asset:// is supported on both Android and iOS.
- On Android, it accesses binary data directly from the APK via
AssetManager. - On iOS, it maps to the
Main Bundledirectory.
Includes first-class support for iOS bookmark:// URIs, allowing persistent access to security-scoped resources (like iCloud Drive files picked via Document Picker) without copying them.
import fs, { getBookmark, resolveBookmark } from 'react-native-nitro-file-system';
// 1. Convert specific path to bookmark
const bookmark = getBookmark('/path/to/file');
// 2. Access using bookmark URI
const stat = await fs.promises.stat(bookmark);
const content = await fs.promises.readFile(bookmark);
// 3. Resolve bookmark back to physical path
const path = resolveBookmark(bookmark);
console.log(path); // "/path/to/file"Nitro File System includes a high-performance native file and directory picker that integrates seamlessly with the library's path system.
import fs, { pickFiles, pickDirectory } from 'react-native-nitro-file-system';
// 1. Pick and Import multiple images (copies to app cache)
const importedFiles = await pickFiles({
multiple: true,
mode: 'import',
extensions: ['.jpg', '.png']
});
for (const file of importedFiles) {
console.log(`Imported to cache: ${file.path}`);
// No need for long-term access request for imported files
}
// 2. Pick and Open files in-place (persistent access)
const openedFiles = await pickFiles({
multiple: true,
mode: 'open',
requestLongTermAccess: true
});
for (const file of openedFiles) {
console.log(`Resource path: ${file.path}`);
if (file.bookmark) {
console.log(`Persistent bookmark: ${file.bookmark}`);
}
}
// 3. Pick a directory
const picked = await pickDirectory({
requestLongTermAccess: true
});
console.log(`Selected directory: ${picked.path}`);
if (picked.bookmark) {
console.log(`Persistent bookmark: ${picked.bookmark}`);
}
// List files in the picked directory (works with path or bookmark)
const entries = await fs.promises.readdir(picked.bookmark ?? picked.path);
console.log(entries);| Option | Type | Description |
|---|---|---|
multiple |
boolean |
Allow multiple file selection (Default: false). |
mode |
'open' | 'import' |
'open': Access file in-place (Original URI). 'import': Copy file to app cache and return local path. (Default: 'open') |
extensions |
string[] |
Specific file extensions to filter (e.g., ['.pdf', '.docx']). |
requestLongTermAccess |
boolean |
If true, Android will take persistable URI permission and iOS will return a bookmark:// URI. Recommended for 'open' mode. |
| Option | Type | Description |
|---|---|---|
requestLongTermAccess |
boolean |
If true, Android will take persistable URI permission and iOS will return a bookmark:// URI. |
pickFilesreturnsPromise<PickedFile[]>pickDirectoryreturnsPromise<PickedDirectory>
| Property | Type | Description |
|---|---|---|
path |
string |
The physical path to the file or directory. |
uri |
string |
The standard URI (e.g. file:///... or content://...) for interoperability. |
bookmark |
string |
(Optional) The bookmark:// (iOS) or content:// (Android) URI for persistent access. |
name |
string |
The display name of the file (Only for PickedFile). |
size |
number |
The size of the file in bytes (Only for PickedFile). |
- Android: Returns
content://URIs in bothpathandbookmark. IfrequestLongTermAccessis enabled, the library automatically callstakePersistableUriPermissionfor you. - iOS: Returns standard file paths in
path. IfrequestLongTermAccessis enabled, it returnsbookmark://URIs inbookmarkwhich provide security-scoped access. These URIs are natively supported by all otherfsmethods in this library.
On iOS and Android, accessing files outside of the application's sandbox (like external storage or specific folders) requires explicit user permission. The library provides a native picker and a way to maintain persistent access through security-scoped bookmarks (iOS) or persistable URI permissions (Android).
import fs from 'react-native-nitro-file-system';
// Pick files (returns PickedFile[])
const files = await fs.pickFiles({
multiple: true,
extensions: ['.txt', '.pdf'],
requestLongTermAccess: true // Recommended for persistent access
});
// Pick a directory (returns PickedDirectory)
const picked = await fs.pickDirectory({
requestLongTermAccess: true
});
console.log(picked.path); // Physical path
console.log(picked.bookmark); // bookmark://... on iOS, content://... on Android
// You can use the returned bookmark directly with any fs method
const items = fs.readdirSync(picked.bookmark ?? picked.path);When requestLongTermAccess: true is passed to pickFiles or when using pickDirectory, the library returns a special bookmark:// URI on iOS. This URI embeds a security-scoped bookmark.
- Seamless Access: The library automatically manages security-scoped resource access for every
fscall using abookmark://URI. - Path Joining: You can join paths with a bookmark URI (e.g., using
${uri}/file.txt), and the library will correctly resolve the path while maintaining security scoping. - Long-term Storage: These URIs can be saved (e.g., in MMKV) and reused across app restarts.
- Manual Bookmarks: Use
fs.getBookmark(path)to generate a bookmark for an existing file or directory.
On Android, pickFiles and pickDirectory return content:// URIs. The library automatically requests persistable URI permissions when possible, ensuring the app maintains access after reboot.
- Compatibility: The Node.js-compatible API handles
content://URIs for reading, writing, and directory listing. - SAF Tree Support:
readdirandstatare supported for directory tree URIs returned bypickDirectory.
ISC