| name | expo-file-system-class-api-migration |
| description | Fix for expo-file-system migration errors when upgrading to Expo SDK 52+. Use when:
(1) TypeScript error "Property 'cacheDirectory' does not exist on type",
(2) "Module has no exported member 'EncodingType'" error,
(3) "Property 'deleteAsync' does not exist" or similar FileSystem function errors,
(4) file.info returns a function instead of file info object,
(5) Upgrading Expo SDK and expo-file-system code breaks.
Covers migration from legacy function-based API (FileSystem.deleteAsync, getInfoAsync)
to new class-based API (File, Directory, Paths classes).
|
| author | Claude Code |
| version | 1.0.0 |
| date | "2025-01-23T00:00:00.000Z" |
Expo File System Class API Migration
Problem
After upgrading to Expo SDK 52+ (expo-file-system 18+), the legacy function-based API
no longer works. Code using FileSystem.deleteAsync(), FileSystem.getInfoAsync(),
FileSystem.cacheDirectory, etc. will fail with TypeScript errors or runtime issues.
Context / Trigger Conditions
You'll encounter this when:
- Upgrading Expo SDK from 51 or earlier to 52+
- TypeScript errors like:
Property 'cacheDirectory' does not exist on type 'typeof import("expo-file-system")'
Module '"expo-file-system"' has no exported member 'EncodingType'
Property 'deleteAsync' does not exist
Property 'getInfoAsync' does not exist
- Runtime issues where
file.info returns a function instead of file metadata
- Any code importing
import * as FileSystem from 'expo-file-system'
Solution
1. Change Import Style
import * as FileSystem from 'expo-file-system';
import { File, Directory, Paths } from 'expo-file-system';
2. Replace Common Operations
| Legacy API | New Class-Based API |
|---|
FileSystem.cacheDirectory | Paths.cache |
FileSystem.documentDirectory | Paths.document |
FileSystem.deleteAsync(uri, { idempotent: true }) | new File(uri).delete() |
FileSystem.getInfoAsync(uri) | new File(uri).exists (for existence check) |
FileSystem.writeAsStringAsync(uri, content, { encoding: 'base64' }) | new File(path, name).write(content, { encoding: 'base64' }) |
FileSystem.readAsStringAsync(uri) | new File(uri).text() |
3. File Creation Pattern
const filePath = `${FileSystem.cacheDirectory}voice_${Date.now()}.mp3`;
await FileSystem.writeAsStringAsync(filePath, base64Data, {
encoding: FileSystem.EncodingType.Base64
});
const fileName = `voice_${Date.now()}.mp3`;
const file = new File(Paths.cache, fileName);
await file.write(base64Data, { encoding: 'base64' });
const uri = file.uri;
4. File Deletion Pattern
await FileSystem.deleteAsync(uri, { idempotent: true });
try {
const file = new File(uri);
if (file.exists) {
await file.delete();
}
} catch {
}
5. File Info Pattern
const info = await FileSystem.getInfoAsync(uri);
if (info.exists && info.modificationTime) {
}
const file = new File(uri);
if (file.exists) {
}
6. Directory Listing
const files = await FileSystem.readDirectoryAsync(cacheDir);
const cacheDir = Paths.cache;
const contents = cacheDir.list();
for (const item of contents) {
if (item instanceof File) {
console.log(item.name, item.uri);
}
}
Verification
After migration:
- Run
npx tsc --noEmit - should pass with no expo-file-system errors
- Test file operations in the app to ensure they work correctly
Example
Complete before/after for a voice message cache cleanup:
import * as FileSystem from 'expo-file-system';
export async function cleanupAudioCache(maxAgeMs: number): Promise<void> {
const cacheDir = FileSystem.cacheDirectory;
if (!cacheDir) return;
const files = await FileSystem.readDirectoryAsync(cacheDir);
const now = Date.now();
for (const fileName of files) {
if (fileName.startsWith('voice_') && fileName.endsWith('.mp3')) {
const filePath = `${cacheDir}${fileName}`;
const info = await FileSystem.getInfoAsync(filePath);
if (info.exists && info.modificationTime) {
const age = now - info.modificationTime * 1000;
if (age > maxAgeMs) {
await FileSystem.deleteAsync(filePath, { idempotent: true });
}
}
}
}
}
import { File, Paths } from 'expo-file-system';
export async function cleanupAudioCache(maxAgeMs: number): Promise<void> {
const cacheDir = Paths.cache;
const contents = cacheDir.list();
const now = Date.now();
for (const item of contents) {
if (item instanceof File &&
item.name.startsWith('voice_') &&
item.name.endsWith('.mp3')) {
const match = item.name.match(/voice_(\d+)\.mp3/);
if (match) {
const timestamp = parseInt(match[1], 10);
const age = now - timestamp;
if (age > maxAgeMs) {
await item.delete();
}
}
}
}
}
Notes
- The new API uses classes (File, Directory) rather than functions
Paths object provides standard directories (cache, document, etc.)
- File metadata access has changed -
info is now a method, not a property
- The new API is more object-oriented and TypeScript-friendly
- Some operations that were async are now sync (like
exists, list())
References