KhueApps
Home/React Native/React Native Expo: Move and Copy Files and Folders with expo-file-system

React Native Expo: Move and Copy Files and Folders with expo-file-system

Last updated: October 08, 2025

Overview

This guide shows how to move and copy files and folders in an Expo (React Native) app using expo-file-system. You will learn how to:

  • Work inside your app’s sandbox (document and cache directories)
  • Copy or move individual files
  • Copy entire folders
  • Verify results and handle errors

All examples target managed Expo projects, but APIs are the same in bare apps.

Quickstart

  1. Install the dependency
  • Run: npx expo install expo-file-system
  1. Import and pick directories
  • Use FileSystem.documentDirectory for persistent storage
  • Use FileSystem.cacheDirectory for temporary data
  1. Copy or move files/folders
  • copyAsync({ from, to }) to duplicate
  • moveAsync({ from, to }) to rename/relocate
  1. Verify
  • readDirectoryAsync and getInfoAsync help confirm results

Minimal working example (copy and move)

import React, { useState } from 'react';
import { Button, Text, View, StyleSheet } from 'react-native';
import * as FileSystem from 'expo-file-system';

const root = FileSystem.documentDirectory!; // e.g., file:///data/... or file:///var/... 

export default function App() {
  const [log, setLog] = useState<string>('');

  const append = (line: string) => setLog(prev => prev + (prev ? '\n' : '') + line);

  const run = async () => {
    try {
      append('Preparing directories...');
      const srcDir = root + 'demo/';
      const dstDir = root + 'backup/';
      const copyDir = root + 'demo-copy/';

      // Ensure directories exist
      await FileSystem.makeDirectoryAsync(srcDir, { intermediates: true });
      await FileSystem.makeDirectoryAsync(dstDir, { intermediates: true });

      // Create a source file
      const srcFile = srcDir + 'note.txt';
      await FileSystem.writeAsStringAsync(srcFile, 'Hello from Expo!');
      append(`Created: ${srcFile}`);

      // Copy the file into backup
      const copiedFile = dstDir + 'note.txt';
      await removeIfExists(copiedFile);
      await FileSystem.copyAsync({ from: srcFile, to: copiedFile });
      append(`Copied file to: ${copiedFile}`);

      // Move/rename the original file
      const movedFile = srcDir + 'note-moved.txt';
      await removeIfExists(movedFile);
      await FileSystem.moveAsync({ from: srcFile, to: movedFile });
      append(`Moved file to: ${movedFile}`);

      // Copy the entire folder (recursive)
      await removeIfExists(copyDir);
      await FileSystem.copyAsync({ from: srcDir, to: copyDir });
      append(`Copied folder to: ${copyDir}`);

      // Verify results
      const srcList = await FileSystem.readDirectoryAsync(srcDir);
      const dstList = await FileSystem.readDirectoryAsync(dstDir);
      const copyList = await FileSystem.readDirectoryAsync(copyDir);

      append('Src contents: ' + JSON.stringify(srcList));
      append('Backup contents: ' + JSON.stringify(dstList));
      append('Demo-copy contents: ' + JSON.stringify(copyList));
    } catch (e: any) {
      append('Error: ' + e?.message);
    }
  };

  return (
    <View style={styles.container}>
      <Button title="Run file ops" onPress={run} />
      <Text style={styles.log}>{log}</Text>
    </View>
  );
}

async function removeIfExists(uri: string) {
  const info = await FileSystem.getInfoAsync(uri);
  if (info.exists) {
    await FileSystem.deleteAsync(uri, { idempotent: true });
  }
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, paddingTop: 64 },
  log: { marginTop: 12 },
});

What it does:

  • Creates a demo directory and note.txt
  • Copies note.txt to backup/
  • Renames/moves the original file
  • Recursively copies demo/ to demo-copy/
  • Lists contents to verify

Copy a whole folder (with optional fine-grained control)

copyAsync supports copying directories recursively. If you need custom filtering or progress, implement a manual walk:

import * as FileSystem from 'expo-file-system';

export async function copyDirRecursive(fromDir: string, toDir: string) {
  const info = await FileSystem.getInfoAsync(fromDir);
  if (!info.exists || !info.isDirectory) throw new Error('Source is not a directory');

  await FileSystem.makeDirectoryAsync(toDir, { intermediates: true });
  const entries = await FileSystem.readDirectoryAsync(fromDir);

  for (const name of entries) {
    const from = fromDir + name;
    const to = toDir + name;
    const child = await FileSystem.getInfoAsync(from);
    if (child.isDirectory) {
      await copyDirRecursive(from + '/', to + '/');
    } else {
      await FileSystem.copyAsync({ from, to });
    }
  }
}

Use this when you need to skip files, rename during copy, or implement conflict rules.

Key APIs you’ll use

  • FileSystem.documentDirectory: persistent app sandbox
  • FileSystem.cacheDirectory: temporary storage (system may wipe)
  • FileSystem.copyAsync({ from, to })
  • FileSystem.moveAsync({ from, to })
  • FileSystem.makeDirectoryAsync(uri)
  • FileSystem.readDirectoryAsync(uri), FileSystem.getInfoAsync(uri)
  • FileSystem.deleteAsync(uri)

Platform and permission notes

  • App sandbox only by default
    • iOS and Android allow free read/write in your app’s documentDirectory and cacheDirectory without extra permissions.
  • External/shared storage (Android)
    • To work outside the sandbox, use StorageAccessFramework to request a user-selected directory. Direct paths generally won’t work on modern Android.
  • iOS bundle to sandbox
    • To copy bundled assets, use expo-asset: download the asset, then copy from asset.localUri into documentDirectory.
  • Web
    • expo-file-system has limited/no support on web. Prefer platform checks before calling APIs.

Common pitfalls and how to avoid them

  • Destination already exists
    • copyAsync/moveAsync may fail if the destination exists. Delete first or generate a unique name.
  • Parent directory missing
    • Ensure the destination’s parent directory exists (makeDirectoryAsync with intermediates: true).
  • Mixing string paths and URIs
    • Use the file:// URIs that expo-file-system returns (e.g., documentDirectory). Don’t assume OS-native path separators.
  • Trailing slash consistency
    • For directories, keep a trailing slash when building nested paths to avoid accidental name concatenation issues.
  • Large reads into memory
    • Avoid reading whole files as strings/base64 unless necessary. Prefer streaming where possible (e.g., downloadAsync) or work with files directly.
  • Moving across volumes
    • moveAsync typically works within the same storage area. If crossing areas fails, copy then delete.

Performance notes

  • Prefer move over copy for renames; move is O(1) when staying in the same volume.
  • Batch operations
    • When copying many small files, queue a reasonable number at once (e.g., Promise.all with chunks of 5–10) to reduce overhead without overwhelming the bridge.
  • Avoid unnecessary JSON/base64
    • Keep operations at the file level; converting to base64 inflates memory and CPU usage.
  • Use cacheDirectory for transient data
    • Faster lifecycle and safe to purge; use documentDirectory for user data that must persist.
  • Measure
    • Time your operations on device builds; the emulator/simulator can be slower or behave differently with I/O.

Tiny FAQ

  • How do I copy from the app bundle to documentDirectory?

    • Load the asset with expo-asset (Asset.fromModule(require(...))). Call downloadAsync() and then copyAsync from asset.localUri.
  • Do I need permissions to copy/move inside documentDirectory?

    • No. Your app has full access to its sandbox on iOS and Android.
  • How can I access Downloads or shared folders on Android?

    • Use StorageAccessFramework to request user access to a directory, then read/write through the provided URIs.
  • Can I copy directories recursively?

    • Yes. copyAsync supports directories. For filtering or custom logic, implement a recursive copy like in the example above.
  • Is this supported on web?

    • Not fully. Guard calls with Platform.OS checks or provide web-specific alternatives.

Series: React Native Intermediate tutorials

React Native