A fork of react-native-fs with a smaller footprint and fixes due to the upstream library seemingly being abandoned.
Now powered by Nitro Modules for superior performance and type safety! 🚀
This library intentional or not has become critical to the success of our mobile applications. We've noticed a few things that led to this fork:
- The original library continuing to expand beyond a basic file system library.
- Hundreds of open issues
- Pull requests go unmerged
- Tests that go untouched
- Some edge case bugs that stay unresolved
We debated a few paths, but we felt it best to fork the project and make some major changes that will upset some.
- We dropped Windows support.
- We dropped methods specific to platforms.
- We dropped upload support.
We will continue to support this library for as long as we use it.
- 🚀 High Performance: Powered by Nitro Modules with direct JSI bindings
- 📁 File System Operations: Complete file system access (read, write, copy, move, etc.)
- 🌊 File Streaming (Beta): Efficiently handle large files with streaming API
- 📱 MediaStore Support: Android MediaStore integration for media files
- ⬇️ Downloads: Background downloads with progress tracking
- 🔒 Type Safe: Full TypeScript support with end-to-end type safety
- 🎯 Cross Platform: iOS and Android support
npm i --save react-native-fs2
# Peer dependency required
npm i --save react-native-nitro-modules| react-native-fs2 | react-native |
|---|---|
| 4.x (nitro) | >=0.82 |
| 3.4.x | >=0.82 |
| 3.0.x | >=0.69 |
Changes can be found in CHANGELOG.md
Migrating from v3.x to v4.x? See UPGRADE.md.
- Nitro Modules Architecture: Complete rewrite using Nitro Modules for superior performance
- File Streaming API: New streaming capabilities for large file operations (see FILE_STREAM.md)
- Better Type Safety: End-to-end type safety from TypeScript to native code
- ArrayBuffer Built-in: Native ArrayBuffer support without additional dependencies
- Backward Compatible API: Most existing code works without changes!
Note: v4.x requires
react-native-nitro-modulesas a peer dependency. Coming from 3.x? Read UPGRADE.md.
import RNFS from 'react-native-fs2';
console.log(await RNFS.getFSInfo());// mkdir(filepath: string, options?: MkdirOptions): Promise<undefined>
await RNFS.mkdir(`FolderToCreate`);- Creates directory at
filepathlocation. - Optionally include
MkdirOptionswith properties:excludedFromBackup: boolean(iOS) - maps to NSURLIsExcludedFromBackupKey. Renamed in 4.x. Passfalseto un-exclude a directory.fileProtection: FileProtectionType(iOS) - maps to NSFileProtectionKey. Renamed in 4.x.
- Both options are iOS-only; Android has ignored them in every version.
// moveFile(filepath: string, destPath: string, options?: FileOptions): Promise<void>
await RNFS.moveFile('FileToMove', 'DestinationLocation')- Moves file from
filepathtodestPath - Optionally takes
FileOptions—fileProtection: FileProtectionType(iOS), applied to the destination. Renamed fromNSFileProtectionKeyin 4.x. Ignored on Android.
// copyFile(filepath: string, destPath: string, options?: FileOptions): Promise<void>
await RNFS.copyFile('FileToCopy', 'DestinationLocation')- Copies file from
filepathtodestPath - Optionally takes
FileOptions—fileProtection: FileProtectionType(iOS), applied to the destination. Renamed fromNSFileProtectionKeyin 4.x. Ignored on Android.
// getFSInfo(): Promise<FSInfoResult>
const fsInfo = await RNFS.getFSInfo()- Returns an
FSInfoResultobject that contains information on the device storage space FSInfoResult- totalSpace:
number-> Total internal storage on the device (in bytes). - freeSpace:
number-> Available internal storage on the device (in bytes). - totalSpaceEx:
number | undefined-> (Android) Total external storage, in bytes.undefinedon iOS and when no external volume is mounted. - freeSpaceEx:
number | undefined-> (Android) Available external storage, in bytes.
- totalSpace:
// getAllExternalFilesDirs(): Promise<string[]>
const externalFileDirs = await RNFS.getAllExternalFilesDirs()- Returns an
arraywith the absolute paths to application-specific directories on all shared/external storage devices where the application can place persistent files it owns. - Android only. On iOS it rejects with
ENOTSUP: getAllExternalFilesDirs is not supported on iOS.
// unlink(filepath: string): Promise<void>
await RNFS.unlink('FileToUnlink')- Unlinks the item at
filepath. If the item does not exist, an error will be thrown. Also recursively deletes directories (works like Linuxrm -rf).
// exists(filepath: string): Promise<boolean>
await RNFS.exists('File')- Check if the item exists at
filepath. If the item does not exist, return false. - (Android)
content://URIs are resolved to the underlying file, so a content URI that points at a real file now returnstrue. In 3.x it always returnedfalse.
// readDir(dirPath: string): Promise<ReadDirItem[]>
const dirItems = await RNFS.readDir('DirPath')- Returns an
arrayofReadDirItemwhich are items that are present in the directory ReadDirItem- ctime:
number | undefined-> The creation timestamp in milliseconds (iOS only) - mtime:
number-> The last modified timestamp in milliseconds - name:
string-> The name of the item - path:
string-> The absolute path to the item - size:
number-> Size in bytes - isFile:
() => boolean-> Is the item a file? An accessor, as in 3.x. - isDirectory:
() => boolean-> Is the item a directory? An accessor, as in 3.x.
- ctime:
// readFile(filepath: string, encodingOrOptions?: EncodingOrOptions): Promise<string | ArrayBuffer>
const fileData = await RNFS.readFile('DirPath', 'utf8')- Reads the
filepathand return the filecontents - Optionally includes
EncodingOrOptionswith values:'utf8'(default) |'base64'(for binary files) |'ascii'|'arraybuffer'
- Note:
arraybuffersupport is built-in via Nitro Modules (no additional dependencies required). In 3.x it required the optionalreact-native-blob-jsi-helperdependency.
/*
read(
filepath: string,
length: number = 0,
position: number = 0,
encodingOrOptions?: EncodingOrOptions
): Promise<string | ArrayBuffer>
*/
const fileData = await RNFS.read('FileToRead', 0, 0, 'utf8')- Reads bytes length from the given position of the file at
filepathand returns the filecontents. - Optionally includes
EncodingOrOptionswith values:'utf8'(default) |'base64'(for binary files) |'ascii'|'arraybuffer'
'arraybuffer'works here in 4.x. In 3.x it threwInvalid encoding type "arraybuffer".
// hash(filepath: string, algorithm: HashAlgorithm): Promise<string>
const fileChecksum = await RNFS.hash('FileToHash', 'md5')- Reads the
filepathand returns its checksum as determined by algorithm, which is one ofmd5|sha1|sha224|sha256|sha384|sha512. - The algorithm is typed as the
HashAlgorithmunion in 4.x, so an unsupported name is a compile error rather than a runtime rejection. Import it if you need to name the type:import type { HashAlgorithm } from 'react-native-fs2'.
// writeFile(filepath: string, contents: string, encodingOrOptions?: EncodingOrOptions): Promise<void>
await RNFS.writeFile('FileToWrite', 'ContentsToWrite', 'utf8')- Write the
contentstofilepath - Optionally includes
EncodingOrOptionswith values:'utf8'(default) |'base64'(for binary files) |'ascii'|'arraybuffer'
- This parameter also carries iOS
FileOptions, as it did in 3.x — pass{ encoding: 'utf8', fileProtection: 'NSFileProtectionComplete' }to set the protection class as the file is written. The key was renamed fromNSFileProtectionKeyin 4.x.
// appendFile(filepath: string, contents: string, encodingOrOptions?: EncodingOrOptions): Promise<void>
await RNFS.appendFile('FileToWrite', 'ContentsToAppend', 'utf8')- Append the
contentstofilepath - Optionally includes
EncodingOrOptionswith values:'utf8'(default) |'base64'(for binary files) |'ascii'|'arraybuffer'
// write(filepath: string, contents: string, position?: number, encodingOrOptions?: EncodingOrOptions): Promise<void>
await RNFS.write('FileToWrite', 'ContentsToWrite', -1, 'utf8')- Write the
contentstofilepathat the given random access position. When position is undefined or -1 the contents is appended to the end of the file - Optionally includes
EncodingOrOptionswith values:'utf8'(default) |'base64'(for binary files) |'ascii'|'arraybuffer'
// stat(filepath: string): Promise<StatResult>
const fileStats = await RNFS.stat('FilePath')- Returns a
StatResultobject with statistics of the file StatResult- path:
string-> The same as filepath argument - ctime:
number-> The creation timestamp in milliseconds - mtime:
number-> The last modified timestamp in milliseconds - size:
number-> Size in bytes - mode:
number-> UNIX file mode (iOS only) - originalFilepath:
string-> Android: In case of content uri this is the pointed file path, otherwise is the same as path - isFile: () =>
boolean-> Is the file just a file? - isDirectory: () =>
boolean-> Is the file a directory?
- path:
// downloadFile(options: DownloadFileOptions): { jobId: number, promise: Promise<DownloadResult> }
const downloadResults = await RNFS.downloadFile('FilePath')- Downloads file from
options.fromUrltooptions.toFile. Will overwrite any previously existing file. - Include
DownloadFileOptionswith properties- fromUrl:
string-> URL to download file from - toFile:
string-> Local filesystem path to save the file to - headers?:
Headers-> An object of headers to be passed to the server - background?:
boolean-> Continue the download in the background after the app terminates (iOS only) - discretionary?:
boolean-> Allow the OS to control the timing and speed of the download to improve perceived performance (iOS only) - cacheable?:
boolean - progressInterval?:
number - progressDivider?:
number - begin?:
(res: DownloadBeginCallbackResult) => void;-> Note: it is required when progress prop provided - progress?:
(res: DownloadProgressCallbackResult) => void; - resumable?:
() => void-> only supported on iOS - connectionTimeout?:
number-> only supported on Android - readTimeout?:
number-> supported on Android and iOS - backgroundTimeout?:
number-> Maximum time (in milliseconds) to download an entire resource (iOS only, useful for timing out background downloads)
- fromUrl:
- Returns
DownloadResult- jobId:
number-> The download job ID, required if one wishes to cancel the download. SeestopDownload. - statusCode:
number-> The HTTP status code - bytesWritten:
number-> The number of bytes written to the file
- jobId:
// stopDownload(jobId: number): void
await RNFS.stopDownload('JobID'): void- Abort the current download job with this ID. The partial file will remain on the filesystem.
// resumeDownload(jobId: number): void
await RNFS.resumeDownload('JobID'): void- Resume the current download job with this ID
// isResumable(jobId: number): Promise<bool>
if (await RNFS.isResumable('JobID')) {
RNFS.resumeDownload('JobID')
}- Check if the the download job with this ID is resumable.
// touch(filepath: string, mtime?: Date, ctime?: Date): Promise<void>
await RNFS.touch('FilePath', Date, Date)- Sets the modification timestamp
mtimeand creation timestampctimeof the file atfilepath. Settingctimeis only supported on iOS, Android always sets both timestamps tomtime.
// scanFile(path: string): Promise<string[]>
await RNFS.scanFile('FilePath')- Android only. On iOS it rejects with
ENOTSUP: scanFile is not supported on iOS. - Scan the file using Media Scanner.
- Resolves with the scanned paths. On iOS this is a no-op that resolves
[].
React-native-fs2 now provides powerful file streaming capabilities for efficiently reading and writing large files without loading entire content into memory.
import { createReadStream, listenToReadStreamData, listenToReadStreamProgress, listenToReadStreamEnd } from 'react-native-fs2';
const stream = await createReadStream('/path/to/large-file.dat', {
bufferSize: 8192 // 8KB chunks
});
// Listen for data chunks
const unsubData = listenToReadStreamData(stream.streamId, (event) => {
console.log(`Chunk ${event.chunk}: ${event.data.byteLength} bytes`);
});
// Listen for progress
const unsubProgress = listenToReadStreamProgress(stream.streamId, (event) => {
console.log(`Progress: ${event.progress * 100}%`);
});
// Listen for completion
const unsubEnd = listenToReadStreamEnd(stream.streamId, (event) => {
console.log('Stream finished');
unsubData();
unsubProgress();
unsubEnd();
});
await stream.start();import { createWriteStream, listenToWriteStreamProgress, listenToWriteStreamFinish } from 'react-native-fs2';
const stream = await createWriteStream('/path/to/output-file.dat', {
append: false,
createDirectories: true
});
// Listen for progress
const unsubProgress = listenToWriteStreamProgress(stream.streamId, (event) => {
console.log(`Written: ${event.bytesWritten} bytes`);
});
// Listen for completion
const unsubFinish = listenToWriteStreamFinish(stream.streamId, (event) => {
console.log('Write completed:', event.bytesWritten, 'bytes');
unsubProgress();
unsubFinish();
});
// Write data in chunks
await stream.write(chunk1);
await stream.write(chunk2);
await stream.close();For complete streaming API documentation, see FILE_STREAM.md
MediaStore is a named export, not a property of the default export:
import { MediaStore } from 'react-native-fs2';Upgrading from 3.x?
RNFS.MediaStoreis gone — see UPGRADE.md.
RNFS2 can now interact with the MediaStore on Android. This allows you to add, delete, and update media files in the MediaStore.
Inspiration for this feature came from react-native-blob-util
This feature is only available on Android targeting API 29 or higher. And may require the following permissions:
<!-- Required only if your app needs to access images or photos that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Required only if your app needs to access videos that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Required only if your app needs to access audio files that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />- Creates a new media file in the MediaStore with the given
mimeType. This will not create a file on the filesystem, but will create a reference in the MediaStore.
// createMediaFile(fileDescription: FileDescription, mediatype: MediaCollectionType): Promise<string>
const fileDescription = { name: 'sample', parentFolder: 'MyAppFolder', mimeType: 'image/png' }
const contentURI = await MediaStore.createMediaFile(fileDescription, MediaStore.MEDIA_IMAGE)- Updates the media file in the MediaStore
// updateMediaFile(uri: string, fileDescription: FileDescription, mediatype: MediaCollectionType): Promise<string>
const contentURI = 'content://media/external/images/media/123'
const fileDescription = { name: 'sample-updated-filename', parentFolder: 'MyAppFolder', mimeType: 'image/png' }
const contentURI = await MediaStore.updateMediaFile(contentURI, fileDescription, MediaStore.MEDIA_IMAGE)- Writes data to a media file in the MediaStore with the given
mimeType.
// writeToMediaFile((uri: string, path: string): Promise<void>
await MediaStore.writeToMediaFile('content://media/external/images/media/123', '/path/to/image/imageToWrite.png')- Copies the file at
filepathto the MediaStore with the givenmimeType.
// copyToMediaStore(fileDescription: FileDescription, mediatype: MediaCollectionType, path: string): Promise<string>
const fileDescription = { name: 'sample', parentFolder: 'MyAppFolder', mimeType: 'image/png' }
const contentURI = await MediaStore.copyToMediaStore(fileDescription, MediaStore.MEDIA_IMAGE, '/path/to/image/imageToCopy.png')- Queries the MediaStore for media files with the given
searchOptions.
// queryMediaStore(searchOptions: MediaStoreSearchOptions): Promise<MediaStoreFile | undefined>
// Query by URI
const result = await MediaStore.queryMediaStore({
uri: 'content://media/external/images/media/123',
mediaType: MediaStore.MEDIA_IMAGE
})
// or query by filename and path
const result = await MediaStore.queryMediaStore({
fileName: 'image.png',
relativePath: 'MyAppFolder',
mediaType: MediaStore.MEDIA_IMAGE
})
// result will be MediaStoreFile or undefined if not found
if (result) {
console.log(result.uri, result.name, result.size)
}- Deletes the media file at
urifrom the MediaStore.
// deleteFromMediaStore(uri: string): Promise<boolean>
await MediaStore.deleteFromMediaStore('content://media/external/images/media/123')type FileDescription = {
name: string;
parentFolder: string;
mimeType: string
};type MediaStoreSearchOptions = {
uri?: string;
fileName?: string;
relativePath?: string;
mediaType: MediaCollectionType
};type MediaStoreFile = {
uri: string;
name: string;
mimeType: string;
size: number;
dateAdded?: bigint;
dateModified?: bigint;
relativePath?: string;
};MediaStore.MEDIA_AUDIO- Audio media collectionMediaStore.MEDIA_IMAGE- Image media collectionMediaStore.MEDIA_VIDEO- Video media collectionMediaStore.MEDIA_DOWNLOAD- Download media collection
CachesDirectoryPath- Absolute path to cache directory.DocumentDirectoryPath- Absolute path to the document directory.TemporaryDirectoryPath- Absolute path to temporary directory (cache on Android).
ExternalCachesDirectoryPath- Absolute path to the external cache directory.ExternalDirectoryPath- Absolute path to external shared directory.ExternalStorageDirectoryPath- Absolute path to the external shared storage directory.DownloadDirectoryPath- Absolute path to the download directory.PicturesDirectoryPath- Absolute path to the shared pictures directory.
Please be sure to request needed permissions via PermissionsAndroid.
LibraryDirectoryPath- Absolute path to NSLibraryDirectoryMainBundlePath- Absolute path to main bundle directory.
Every constant is a string and is '' on the platform that does not provide it, so a plain
truthiness check works everywhere:
if (RNFS.ExternalStorageDirectoryPath) { }