Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Capacitor Filesystem: Directory Management

Recommended resources

The freely available resources this lesson will direct you to.

Welcome back! In the previous module, we gave your app the ability to communicate with the user through local notifications, and you learned how to handle interactions with those alerts to create a responsive experience. Now, we will shift our focus from user communication to data persistence by exploring the device's file system.

This lesson introduces the @capacitor/filesystem plugin, a powerful tool for reading and writing files. For many applications, the ability to save user-generated content, cache data for offline access, or manage application assets is critical. We will cover the installation of the plugin and, most importantly, explore the different sandboxed directories available on iOS and Android, each with its own specific purpose and lifecycle.

1. Installing the Filesystem Plugin

As with other Capacitor plugins, the first step is to add it to your project. Since you are using bun in your Turborepo setup, you'll run the installation command from the root of your monorepo and then sync the changes to your native projects.

From your project's root directory, run the following commands:




# Add the filesystem plugin to your workspace
bun add @capacitor/filesystem




# Sync the new plugin with your native projects
bunx cap sync

This installs the necessary web and native code for the plugin. The cap sync command ensures that your iOS and Android projects are aware of the new native capabilities.

You can find the installation instructions in the official documentation.

Filesystem Capacitor Plugin API

This is the main documentation page for the Filesystem plugin.

Confirm the installation commands. Note that the docs use npm, but you should continue using bun.

2. Understanding Sandboxed Directories

Unlike desktop environments where an application might have broad access to the file system, mobile operating systems like iOS and Android enforce a strict security model called sandboxing. Each app is given its own private storage area, and it cannot access the data of other apps unless explicitly permitted.

The Capacitor Filesystem plugin simplifies navigating this sandboxed environment by providing an enum called Directory. When you perform a file operation, you specify one of these Directory members to target the correct storage location. Understanding the differences between these directories is crucial for correct file management.

The primary directories you will work with are:

  • Documents: The place to store user-generated content. On iOS, this directory is private to your app but is backed up to iCloud. On Android, its behavior is more complex and has changed across OS versions; it generally maps to the public Documents folder, but access has become more restricted on Android 10+.
  • Data: The main directory for your app's private data. Use this for files that the user doesn't directly create or manage, such as database files, configuration, or downloaded content that shouldn't be user-facing. Files here are automatically deleted when the app is uninstalled.
  • Cache: For temporary files that can be easily recreated. The OS may automatically delete files in this directory when the device is low on storage. This is ideal for things like cached images or network responses.
  • External: On Android, this refers to a location on the primary shared storage where the app can place persistent files it owns. These files are internal to your app's functionality and are also deleted upon uninstallation. On iOS, this directory is an alias for Documents.

The following table from the Capacitor community provides a helpful summary of these directories and their platform-specific behavior.

A table summarizing the purpose and platform behavior of the main filesystem directories in Capacitor.

To see the complete list and detailed descriptions, you can refer to the official documentation.

Filesystem Capacitor Plugin API

The Directory enum is the cornerstone of the Filesystem plugin.

In the "Enums" section, carefully read the descriptions under the Directory enum. Pay close attention to the platform-specific notes for Documents and ExternalStorage, as these are common sources of confusion.

3. Basic File Operations: Write, Read, and Delete

The Filesystem plugin's API is designed to feel familiar, resembling parts of Node.js's fs module. The three fundamental operations you'll use most often are writeFile, readFile, and deleteFile.

Let's build a simple React component that demonstrates this cycle. This component will have buttons to write, read, and delete a small text file in the app's Data directory.

import React, { useState } from 'react';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';

const FileTester: React.FC = () => {
  const [fileContent, setFileContent] = useState<string | null>(null);
  const filePath = 'my-secret-file.txt';

  const writeTestFile = async () => {
    try {
      await Filesystem.writeFile({
        path: filePath,
        data: `Hello from Capacitor! Written at ${new Date().toLocaleTimeString()}`,
        directory: Directory.Data,
        encoding: Encoding.UTF8,
        recursive: true, // Create parent directories if they don't exist
      });
      console.log('File written successfully');
      setFileContent(null); // Clear previous content
    } catch (e) {
      console.error('Unable to write file', e);
    }
  };

  const readTestFile = async () => {
    try {
      const result = await Filesystem.readFile({
        path: filePath,
        directory: Directory.Data,
        encoding: Encoding.UTF8,
      });
      console.log('File read successfully');
      setFileContent(result.data as string);
    } catch (e) {
      console.error('Unable to read file', e);
      setFileContent('Error reading file.');
    }
  };

  const deleteTestFile = async () => {
    try {
      await Filesystem.deleteFile({
        path: filePath,
        directory: Directory.Data,
      });
      console.log('File deleted successfully');
      setFileContent(null);
    } catch (e) {
      console.error('Unable to delete file', e);
    }
  };

  return (
    <div>
      <h2>Filesystem Test</h2>
      <button onClick={writeTestFile}>Write File</button>
      <button onClick={readTestFile}>Read File</button>
      <button onClick={deleteTestFile}>Delete File</button>
      {fileContent && (
        <pre>
          <strong>File Content:</strong>
          <p>{fileContent}</p>
        </pre>
      )}
    </div>
  );
};

export default FileTester;

In this example:

  1. writeFile takes a path, the data to write, a target directory, and an encoding. We use Directory.Data because this is internal application information.
  2. readFile uses the same path and directory to retrieve the contents. The data is returned in the data property of the result object.
  3. deleteFile permanently removes the file.

The documentation provides similar examples and a full list of all available methods like mkdir, readdir, and copy.

Filesystem Capacitor Plugin API

The API reference is your guide to all file operations.

First, review the code example to see the basic write/read/delete pattern. Then, quickly scan the list of API methods to get a sense of the plugin's full capabilities. Finally, look at the "Interfaces" section, specifically the WriteFileOptions, to see parameters like recursive.

4. A Note on Performance and Large Files

As a seasoned front-end developer, you know that memory management is critical. A common mistake when working with files in a WebView context is to read a large file (like an image or video) into memory as a base64 string using readFile. This can quickly exhaust the WebView's memory and crash your application.

A much better practice is to work with file URIs. Instead of reading the file's content into JavaScript, you get a special URI that points to the native file on disk. You can then use this URI directly, for instance, as the src for an <img> tag. Capacitor provides a utility function, Capacitor.convertFileSrc(), to create a WebView-safe URL from a native file path.

The article from Capawesome provides an excellent overview of this best practice.

Capacitor File Handling: The Complete Guide - Capawesome

This guide highlights best practices for efficient file handling.

Read the "Problem" section to understand the out-of-memory issue. Then, study the "Best Practices" and the "Read a file" sections. The key takeaway is to avoid readFile for large binary files and instead use methods that provide a file URI, which you can then convert and use with fetch or set as an element's src.

We won't implement this advanced pattern now, but it's a vital concept to keep in mind as you start integrating more complex file-based features. For downloading files, the plugin's downloadFile method is also preferable as it streams the file directly to disk, bypassing the WebView's memory entirely.

Conclusion

You now have the foundational knowledge to manage files and directories in your Capacitor application. We've installed the @capacitor/filesystem plugin and explored the different sandboxed directories, each with a distinct purpose. You've also seen how to perform basic write, read, and delete operations and learned about the performance pitfalls of handling large files.

Here are the key takeaways from this lesson:

  • The @capacitor/filesystem plugin provides a familiar, NodeJS-like API for file I/O.
  • Mobile apps operate in a sandboxed environment, and the Directory enum (Documents, Data, Cache) is the key to targeting the correct storage location.
  • Choosing the right directory depends on the data's purpose, persistence requirements, and whether it's user-facing.
  • For basic text files, writeFile and readFile with Encoding.UTF8 are straightforward.
  • To avoid memory issues, avoid reading large binary files as base64 strings into the WebView.

In our next lesson, we will continue with the Filesystem plugin and address a critical requirement for Android: implementing runtime permission requests. While basic access to internal directories like Data doesn't require special permissions, accessing shared storage like Documents on modern Android versions requires explicitly asking the user for their consent.

Can't find a good explanation? Sign up and we'll make it for you

Sign up