---
title: Expo File System gets a major upgrade in SDK 54
authors: Aleksander Mikucki
published: September 16, 2025
categories: Product, Development
tags: file system, React Native, Expo modules, API Routes
---

Last year on this blog, we shared more information on [how Shared Objects help both the Expo team and the wider community](https://expo.dev/blog/the-real-world-impact-of-shared-objects) ship modules that are both performant and easy to use for developers.

In that article (worth a read if you haven’t had a chance yet) we briefly described an early release of the new file system module that came out in SDK 52 as `expo-file-system/next`.

Now, with [SDK 54](https://expo.dev/changelog/sdk-54) we’re finally promoting `expo-file-system/next` to stable and dropping the `next` entrypoint. While the old filesystem API is still available under `expo-file-system/legacy` if you need more time to migrate, we encourage you to migrate in [this SDK](https://expo.dev/changelog/sdk-54). As always, we appreciate it if you report issues, any features you’re missing, and share general feedback.

## Motivation (on why we even do breaking changes)

Mobile apps don’t rely on direct access to files and directories as much as your typical desktop program, but it’s still a pretty common and important SDK module.

I personally remember working on react-native apps circa 2019-2020. While the general experience was great, sometimes things that seemed easy turned out to be rather painful, way more than they would be on the underlying native platform.

A lot of these problems originated from dealing with binary or other data that wasn’t serializable over the bridge.

That’s why, back then, we relied heavily on base64 for passing data between native modules (I sometimes sent user avatars as base64 to the backend just because it was the easiest option to set up).

Since then a lot has changed – [new architecture dropped the bridge](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here) entirely, we heavily improved a lot of the Expo SDK, we introduced SharedObjects and [SharedRefs](https://docs.expo.dev/versions/latest/sdk/expo/#classes) that are essentially pointers to native instances, data structures, and way more.

We have so many new module capabilities and concepts, so it only makes sense to update the APIs to use them. We want Expo in general, and the Expo SDK, to lead users down a wide path towards success.

Important functions and use-cases should be easy, and making mistakes or using APIs in non-performant ways really shouldn’t be.

## Key concepts (how the module works now)

There are a few assumptions we made quite early when rewriting the module:

### It’s 100% more object based now

The previous API always accepted a `uri` parameter for each method.

While it was easy and convenient for us as library authors (back then the only things we could pass as a parameter to the native code were strings and numbers anyway), it doesn’t really make for an ergonomic API when you need to do more complicated operations on the filesystem.

Here’s what creating a new directory in your documents looked like (sure there are probably options you can use to simplify that, but this is an obvious solution):

```javascript
const newPath = `${FileSystem.documentDirectory}/myNewFolderName`
const info = await FileSystem.getInfoAsync(newPath)
if(!info.exists) {
	FileSystem.makeDirectoryAsync(newPath)
}
```

This complexity is why we introduced `File` and `Directory` classes to make these operations easier. They do store URIs under the hood, but they improve the DX in a couple of different ways:

- No need to manually concatenate path strings
- Code suggestions that promote methods that make sense in the current context (that’s also why we’ve split off files and directories into separate classes)
- Ability to chain some function calls

Here’s what that example looks like in the modern API:

```javascript
const directory = Directory(Paths.document, "myNewFolderName")
if (!directory.exists) {
	directory.create()
}
```

Another benefit is that listing directory contents now returns actual File and Directory instances!

```javascript
const directory = Directory(Paths.document, "myNewFolderName")
for (const item of directory.list()) {
    if (item instanceof Directory) {
      console.log(`DIRECTORY: ${item.name}`)
    } else {
      console.log(`FILE: ${item.name}`)
    }
}
```

### No more serialization (unless you actually want to)

Different modules need different inputs (kinda like Factorio), so we’ve built plenty of functions to read and write data into a file in many different ways.

Strings and TypedArrays just work, both as synchronous and asynchronous functions:

```javascript
const file = new File(Paths.document, "myFile.txt")

// data is either a string or a Uint8Array
file.write(data)

file.text() // Promise<string>
file.textSync() // string

file.bytes() // Promise<Uint8Array>
file.bytesSync() // Uint8Array

file.base64() // Promise<string>
file.base64Sync() // string
```

We provide readable and writable streams as well as a custom FileHandle class to support applications like loggers, reading large files, seeking, ect.

```javascript
file.readableStream() // ReadableStream<Uint8Array>
file.writableStream() // WritableStream<Uint8Array>

// Read 10 bytes from the center of a file
const fileHandle = file.open()
fileHandle.offset = file.size / 2
fileHandle.readBytes(10) // Uint8Array
fileHandle.close()
```

In short, we want you to have flexibility when reading and writing files with any type of string, base64, or binary data.

### It’s a blob and it works with expo/fetch

The `File` instances also implement the `Blob` interface, which not only makes them play nice with a lot of existing ecosystem packages, but also makes it really easy to use `expo/fetch` :

```javascript
import { fetch } from 'expo/fetch';
import { File } from 'expo-file-system';

const src = new File(testDirectory, 'file.txt');
file.write('Hello, world!');

const response = await fetch('https://example.com', {
  method: 'POST',
  body: file,
});
```

In fact, we’re thinking of [expo/fetch as the default](https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api) way to download and upload files from remote URLs.

### SAF + security scoped resources built in

Android uses something called SAF for accessing files from cloud providers and when selected by the user using the system picker.

Our new API supports SAF URIs directly inside the `File` and `Directory` classes where possible, so you should be able to reuse code that deals with resources always available to your app (those that live in your cache or documents folder) and those that you are explicitly granted access to via SAF.

We’ve also added support for iOS’s Security Scoped Resources, again inside the core `File` and `Directory` classes, and added built-in pickers to make it easier to add file selection on both platforms. Still, we have full compatibility with `expo-document-picker` , so you’re free to keep your existing code.

```javascript
File.pickFileAsync(): Promise<File>
```

## What about Web?

The Web platform doesn’t really directly expose file access at arbitrary URIs, so most of the methods available on iOS and Android don’t really make as much sense on the Web.

On the Web, the only entry is from a user-presented picker for files (as a web `File` interface that is essentially a `Blob` or via the new `FileSystemHandle` API).

We’re exploring exposing one of these APIs under Expo FileSystem at some point, but we’d love to gauge community interest in a common File API covering all 3 platforms and use-cases for it first.

In the meantime for most applications that handle files on all three platforms you can rely on our `File` matching the `Blob` interface to build common code accepting either Expo’s `File` on native or the `File` from `<input type="file">` elements on Web.

### Feedback is always welcome!

We really hope the new API covers all your use-cases for file system access in Expo apps. As always, we’re open and extremely grateful for any feedback, Github issues, feature requests or tweets you want to send our way.

More examples, API specification and more is available [in our docs](https://docs.expo.dev/versions/v54.0.0/sdk/filesystem/).   
  
And please join the livestream tomorrow if you have questions or would like to see live demos from SDK 54:

[Video: Join the team on a live stream about SDK 54 on September 17 at 10:00am PDT](https://www.youtube.com/live/KBlbkjqxNbM?si=v5TOzKEHAF3FNeii)