skip to content

Expo File System

Expo File System reads, writes and downloads files in the app's sandbox through File, Directory and Paths objects. Interviewers ask which directory survives updates and which the OS may clear.

on this pageshow

explore

questions

5

In expo-file-system, what is the difference between Paths.document and Paths.cache, and which files belong in each?

level: juniorimportance: must knowfreq 52%

answer

  1. two sandbox folders, two promises
  2. one may be purged when storage runs low
  3. re-downloadable data goes in cache
  4. irreplaceable user data goes in document
  5. both vanish on uninstall

basics

~20 s

Paths.document is the app's persistent documents directory, which the system does not clear on its own; Paths.cache is the caches directory, which the OS may purge when storage runs low. Keep irreplaceable data in document and re-creatable data in cache.

solid answer

~40 s

`expo-file-system` exposes the app sandbox through the static `Paths` class, and each getter returns a `Directory` object. `Paths.document` maps to the iOS `Documents` folder and the Android internal files directory; the library describes it as safe from deletion by the system, so files stay until the app deletes them or the user uninstalls. `Paths.cache` maps to the caches directory, which the OS may empty when the device is low on storage, at a moment the app does not choose. So I put user-created or user-requested content that cannot be recreated offline in `document`, and anything I can download or regenerate, such as browsed map tiles or thumbnails, in `cache`, and every read from `cache` must handle a missing file.

code

typescript · 12 lines
typescript
import { Directory, File, Paths } from 'expo-file-system';

const tileCache = new Directory(Paths.cache, 'tiles');
const savedRegions = new Directory(Paths.document, 'offline-regions');

tileCache.create({ intermediates: true, idempotent: true });
savedRegions.create({ intermediates: true, idempotent: true });

export function cachedTile(z: number, x: number, y: number): File | null {
  const tile = new File(tileCache, `${z}-${x}-${y}.png`);
  return tile.exists ? tile : null; // the OS may have purged it
}

go deeper

for a junior

Recall the contract: document is kept until the app deletes it, cache may be purged by the OS under storage pressure, and both disappear on uninstall.

for a middle

Explain the decision rule of whether the app can recreate the file without the user, and show defensive reads from the cache with file.exists and a network fallback.

for a senior

Show judgement on edge cases such as user-requested offline regions that are re-downloadable in principle but belong in document, and who owns cleanup of each directory.

for a principal

Frame storage as a product promise: which data the user expects to survive offline and after storage pressure, and how the app communicates and lets the user reclaim that space.

## What the two directories are Every iOS and Android app runs in a **sandbox**: a private area of storage that only that app can read and write. `expo-file-system` exposes the useful locations through the static **`Paths`** class (`import { Paths } from 'expo-file-system'`). Each getter returns a `Directory` object, not a string: - **`Paths.document`** is the app's documents directory: the `Documents` folder on iOS and the app's internal files directory on Android. The library documents it as a place for files that are safe from being deleted by the system. - **`Paths.cache`** is the app's caches directory. The library documents it as a place for files that can be deleted by the system when the device runs low on storage. - **`Paths.bundle`** is where assets shipped inside the app binary live. It is something to read from, not a place to save downloads. The difference between the first two is a **contract with the operating system**, not a difference in speed or format. Both are ordinary folders on the same disk; only one of them may be emptied behind the app's back. ## Side by side | | `Paths.document` | `Paths.cache` | |---|---|---| | Cleared by the OS under storage pressure | No | Yes, possibly without warning | | Survives an app restart | Yes | Yes, unless purged meanwhile | | Removed when the app is uninstalled | Yes | Yes | | Included in iOS device backups | Yes | No | | Who is responsible for cleanup | The app, always | The app, with the OS as a last resort | | Typical contents | drafts, user exports, offline regions the user asked for | browsed tiles, thumbnails, temporary exports | A common misconception is that the cache directory is a temp folder emptied on every launch. It is not: files there persist across restarts and usually across updates. What changes is who may delete them. ## The one question that picks the directory Ask: **"If this file vanished right now, could the app get it back without the user's help?"** 1. If yes, because the file is a copy of something on a server or can be regenerated, it belongs in `Paths.cache`. 2. If no, because the user created it or explicitly asked to keep it, it belongs in `Paths.document`. The offline-map scenario shows the nuance. Tiles fetched while the user pans around a map are a pure cache: losing them costs a re-download. A region the user tapped **"save for offline use"** before a hike is different: it is re-downloadable in principle but not at the moment of need, in a valley without signal. That pack belongs in `document`, and the app owns its lifecycle, offering a "remove offline region" action. ## Consequences for the code - **Treat every read from the cache as optional.** Check `file.exists` (a synchronous property) or catch the error, and fall back to the network. - **The document directory never shrinks by itself.** Anything written there counts as the app's data in the system storage settings until the app deletes it. - **Build paths with the constructors.** `new File(Paths.cache, 'tiles', '12-2048-1361.png')` joins the segments; creating the object does not touch the disk. - **Check free space before a large download.** `Paths.availableDiskSpace` and `Paths.totalDiskSpace` report bytes on the device's internal storage. - **Exposing documents to the user is opt-in.** On iOS the `Documents` folder appears in the Files app only when the `expo-file-system` config plugin's `enableFileSharing` option is set and a new binary is built. ## Version notes The `Paths`, `File` and `Directory` object API became the default export of `expo-file-system` in SDK 54. Older code uses the string constants `documentDirectory` and `cacheDirectory` (each ending in `/`), which now come only from `expo-file-system/legacy`. In Expo Go on Android, `Paths.cache` and `Paths.document` point to directories isolated per project; a development or production build uses the app's own directories.

  • Does a file in Paths.cache survive an app restart?
    Yes. The caches directory is not emptied on launch; files persist across restarts and normally across updates. The only difference from `Paths.document` is that the OS may remove cache contents when storage runs low, at a time the app does not control, so the code must tolerate a missing file.
  • How can an iOS user see files the app saved to Paths.document?
    Only if the app opts in. The `expo-file-system` config plugin's `enableFileSharing` option sets `UIFileSharingEnabled` in Info.plist, which makes the Documents folder visible in the Files app. It is a native setting, so it needs a new build, not an over-the-air update.

Paths.document is a locked desk drawer: only you empty it. Paths.cache is a shared whiteboard the cleaners may wipe when the office runs out of space, so anything written there must be easy to write again.

saying these in an interview costs you the question

  • The cache directory is emptied every time the app restarts.
  • Files in the document directory survive uninstalling the app.
  • The OS frees space by deleting old files from the document directory.
  • Paths.bundle is a good place to save downloaded files.
  • Cache and document differ in read speed, so pick by performance.
open as a page

In expo-file-system's File class, how do you write and read file contents, and which of those calls block the JavaScript thread?

level: middleimportance: must knowfreq 42%

basics

~20 s

expo-file-system's file.write(content) is synchronous; reads come as text(), base64() and bytes() Promises plus textSync(), base64Sync() and bytesSync(). Synchronous calls run native I/O on the JavaScript thread and block it, so large files need the async reads.

open as a page

In Expo SDK 57, how does expo-file-system's File.downloadFileAsync handle an existing destination, a 404 response, progress and cancellation?

level: middleimportance: should knowfreq 38%

basics

~20 s

File.downloadFileAsync(url, destination, options) saves a URL into a File or Directory and resolves to a File. It rejects if the target exists unless idempotent is true, rejects on a non-2xx status, reports onProgress and cancels through an AbortSignal.

open as a page

After upgrading an Expo app to SDK 57, readAsStringAsync imported from expo-file-system throws at runtime; why, and how do you migrate the code?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Since SDK 54 the root expo-file-system import is the File/Directory/Paths API, and old names like readAsStringAsync are stubs that warn and throw. Import them from expo-file-system/legacy to unblock, then migrate each call, for example to new File(uri).text().

open as a page

An Expo app caches offline map tiles with expo-file-system and its storage keeps growing; how do you measure and clean the tile cache safely?

level: seniorimportance: should knowfreq 24%

basics

~20 s

The OS purges Paths.cache only under storage pressure, so the app enforces its own budget: one Directory per region, measured with Directory.size and removed with delete() inside try/catch, run deferred because these calls block the JavaScript thread.

open as a page