---
title: Persistent Folder Access in Capacitor Apps
description: Let users pick a folder once and keep writing to it after restarts. Persist, restore and release folder access in Capacitor on Android and iOS.
date:
  created: 2026-10-06
  updated: 2026-10-06
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - SDKs
links:
  - Capacitor File Manager: sdks/capacitor/file-manager.md
  - Capacitor File Picker: sdks/capacitor/file-picker.md
faq: true
---

# Persistent Folder Access in Capacitor Apps

To remember a folder the user picked, so your app can still write to it after a restart, you have to persist the access grant the operating system hands out with the pick, not the folder path. Capacitor persistent folder access takes three calls. The user picks the folder with `pickDirectory()` from the free [Capacitor File Picker plugin](../../sdks/capacitor/file-picker.md), the [Capacitor File Manager plugin](../../sdks/capacitor/file-manager.md) persists the grant with `persistDirectoryAccess(...)`, and `getPersistedDirectories()` gives the folder back on every launch. This guide builds a backup folder setting on Android and iOS, from the first pick to revoked access and cleanup. The File Manager plugin is part of [Capawesome Insiders](../../insiders/index.md).

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Try Capawesome Cloud free for 14 days — iOS builds without a Mac" src="https://capawesome.io/assets/banners/cloud-free-trial-capacitor-apps.png" />
  </a>
</div>

**Key takeaways:**

- A picked folder stays accessible after a restart only if the app persists the grant, a persistable URI permission on Android or a security-scoped bookmark on iOS.
- `FilePicker.pickDirectory()` returns the `bookmark` that iOS needs from Capacitor File Picker plugin version 8.1.0 on, and `persistDirectoryAccess(...)` takes it next to the folder URI.
- `getPersistedDirectories()` returns the current folder URIs on each launch, refreshes stale entries and releases folders whose document no longer exists, so apps should never store the URI themselves.
- Android keeps at most 512 persisted URI grants per app (128 on Android 10), according to the AOSP source, and silently drops the oldest ones beyond that.
- Neither plugin documents an error code for revoked access, so an empty persisted list or a failed write should send the user back to the folder picker.

## Why access expires

Both Android and iOS tie access to a user-picked folder to a grant, and both end that grant early unless the app asks for more. On Android, the folder picker returns a `content://` tree URI. Google's [documentation on persisting permissions](https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions){:target="_blank"} describes how long the default grant lives:

> When your app opens a file for reading or writing, the system gives your app a URI permission grant for that file, which lasts until the user's device restarts.

An app that wants the folder after the next reboot calls [`takePersistableUriPermission()`](https://developer.android.com/reference/android/content/ContentResolver#takePersistableUriPermission%28android.net.Uri,%20int%29){:target="_blank"}, and only for a URI that the picker offered with `FLAG_GRANT_PERSISTABLE_URI_PERMISSION`. Even a persisted grant does not survive everything. The same Google page warns that the app loses access if the document is moved or deleted and has to ask the user again.

On iOS, the folder picker has returned a security-scoped URL since iOS 13. Apple's article [Providing access to directories](https://developer.apple.com/documentation/uikit/providing-access-to-directories){:target="_blank"} explains what that URL grants and how an app keeps it:

> In iOS 13, users can select a directory from any of the available file providers using a UIDocumentPickerViewController. The document picker returns a security-scoped URL for the directory [...] Your app can even save a bookmark for this URL, letting it access the directory the next time it launches.

The URL itself is only good for the current session. What survives a relaunch is bookmark data, an opaque blob the app resolves back into a URL on the next start.

So the folder path you see after a pick is not something you can save in your preferences and reuse. On Android it is a URI without a grant once the device restarts, and on iOS it is a URL the app can no longer open. The rest of this guide builds a settings screen for an app that writes its database backups to a folder the user chose once, and keeps that folder working across launches.

## Pick the folder

[`pickDirectory()`](../../sdks/capacitor/file-picker.md#pickdirectory) of the Capacitor File Picker plugin opens the system folder picker and resolves with the folder's `path`. It takes no options and is only available on Android and iOS. On Android, `path` is the `content://` tree URI from the Storage Access Framework, requested with the persistable flag. On iOS, `path` is a `file://` URL, and the result also carries `bookmark`, the base64-encoded security-scoped bookmark of the folder. The plugin returns `bookmark` from version 8.1.0 on, so update the File Picker plugin before you build on it.

When the user closes the picker without choosing a folder, the promise rejects, and the README documents no error code for that case. The backup settings screen treats a rejected pick as "no folder chosen" and keeps the current setting:

```typescript
import { FilePicker } from '@capawesome/capacitor-file-picker';

const pickBackupFolder = async () => {
  try {
    return await FilePicker.pickDirectory();
  } catch {
    return null;
  }
};
```

Android limits which folders the picker lets the user select. Apps targeting Android 11 or higher cannot get the internal storage root or the `Download` directory, for example. [Android Scoped Storage in Capacitor Apps, Explained](./android-scoped-storage-in-capacitor-apps.md#persisted-directories) lists those restrictions and walks through the Android side of the same flow, so this guide spends its Android sentences on the grant lifecycle instead.

## Persist the grant

[`persistDirectoryAccess(...)`](../../sdks/capacitor/file-manager.md#persistdirectoryaccess) of the Capacitor File Manager plugin turns the pick into access that survives app launches. Pass the picker's `path` as `uri` and its `bookmark` unchanged. The method resolves with a `directory` object that holds the folder's `name` and a `uri`, and from then on every method of the File Manager plugin accepts that URI and the URIs of the folder's contents. To install the Capacitor File Manager plugin, please refer to the [Installation](../../sdks/capacitor/file-manager.md/#installation) section in the plugin documentation. The backup settings screen then picks and persists in one step:

```typescript
import { FileManager } from '@capawesome-team/capacitor-file-manager';

const chooseBackupFolder = async () => {
  const picked = await pickBackupFolder();
  if (!picked) {
    return null;
  }
  const { directory } = await FileManager.persistDirectoryAccess({
    uri: picked.path,
    bookmark: picked.bookmark,
  });
  return directory;
};
```

The same TypeScript means different things per platform. On Android, persisting is the persistable URI permission on the tree URI, and the `bookmark` value is absent and not needed. On iOS, persisting means keeping the bookmark and resolving it into a security-scoped URL whenever the folder is used. Apple requires every access to that URL to be balanced. From the documentation of [`startAccessingSecurityScopedResource()`](https://developer.apple.com/documentation/foundation/url/startaccessingsecurityscopedresource%28%29){:target="_blank"}:

> You must balance each call to startAccessingSecurityScopedResource() with a call to stopAccessingSecurityScopedResource(). [...] If you fail to relinquish your access to file-system resources when you no longer need them, your app leaks kernel resources.

Apple adds that an app that leaks enough of these resources loses the ability to add file system locations to its sandbox until it is relaunched. The File Manager plugin exposes no start or stop methods, so this bookkeeping stays on the native side and your TypeScript code only passes URIs around. The three persistence methods, `persistDirectoryAccess(...)`, `getPersistedDirectories()` and `releaseDirectoryAccess(...)`, are only available on Android and iOS and are not implemented on the web.

## Restore on launch

[`getPersistedDirectories()`](../../sdks/capacitor/file-manager.md#getpersisteddirectories) returns every folder your app still has persisted access to, and it is the only place your app should get a folder URI from after a restart. The plugin documents that the URI of a persisted folder may change between app launches, so a URI saved in your own storage can point to nothing on the next start. Call the method at startup and use what it returns.

The method also maintains the list. It refreshes stale entries and releases folders whose document no longer exists, so your code never resolves a bookmark or checks whether it went stale.

A backup app persists exactly one folder, so the settings screen reads the first entry:

```typescript
import { FileManager } from '@capawesome-team/capacitor-file-manager';

const getBackupFolder = async () => {
  const { directories } = await FileManager.getPersistedDirectories();
  return directories[0] ?? null;
};
```

Show the returned `name` in the settings row, for example "Backups go to: Documents", with a "Change folder" button that runs `chooseBackupFolder()` again. A `null` result means there is no folder yet, or no longer one, and the row shows "Choose folder" instead.

[`PersistedDirectory`](../../sdks/capacitor/file-manager.md#persisteddirectory) holds only `name` and `uri`, with no stable identifier. An app that lets users persist several folders cannot rely on the URI to tell them apart across launches, and two folders can share a name. Keep to one persisted folder per purpose where you can, and release the previous folder before persisting a new one, as the cleanup section below shows.

## Read and write

Inside a persisted folder, [`getUri(...)`](../../sdks/capacitor/file-manager.md#geturi) builds the URI of a file from a relative `path` and the folder URI as `parentUri`, and the file does not need to exist yet. The backup job writes a JSON export of the database into a `backups` subfolder:

```typescript
import { Encoding, FileManager } from '@capawesome-team/capacitor-file-manager';

const writeBackup = async (folderUri: string, json: string) => {
  const date = new Date().toISOString().slice(0, 10);
  const { uri: fileUri } = await FileManager.getUri({
    path: `backups/backup-${date}.json`,
    parentUri: folderUri,
  });
  const { uri } = await FileManager.writeFile({
    uri: fileUri,
    data: json,
    encoding: Encoding.Utf8,
    recursive: true,
  });
  return uri;
};
```

`recursive: true` creates the `backups` subfolder on the first run. [`writeFile(...)`](../../sdks/capacitor/file-manager.md#writefile) resolves with the URI of the written file, which can differ from the one you built when the document provider renames the file to avoid a collision, so keep the returned value. To show the existing backups, pass the folder URI to [`readDirectory(...)`](../../sdks/capacitor/file-manager.md#readdirectory), which returns each entry with its name, size and modification date. For a backup that lives in the app sandbox as a file, `copyFile(...)` copies it into the folder instead.

On Android, building the URI of a file that does not exist yet only works for path-structured document providers such as the local device storage. A cloud provider shown in the picker may not support it. The scoped storage post covers that caveat and the bridge to plugins that only accept plain file paths in its [persisted directories section](./android-scoped-storage-in-capacitor-apps.md#persisted-directories).

## Handle revoked access

A persisted folder can stop working at any time for reasons outside your app, and neither the File Picker plugin nor the File Manager plugin documents an error code for that case. On iOS, the user decides. Apple's article on directory access describes the setting:

> After selecting a directory from the document picker, your app appears in Settings > Privacy > Files and Folders [...] users can revoke or restore permission for each app at any time. This means your app must be ready to handle failures [...] especially true when saving and resolving bookmarks

On Android, the grant ends when the document behind it is moved or deleted, as the Google page quoted above states. A second limit is not in the developer documentation at all. In the AOSP source of [`UriGrantsManagerService`](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/services/core/java/com/android/server/uri/UriGrantsManagerService.java){:target="_blank"}, `MAX_PERSISTED_URI_GRANTS` is 512 on the `android11-release` and `main` branches and 128 on `android10-release`. Once an app goes over that number, the system releases its oldest persisted grants without notifying it. An app with a single backup folder will not get near the cap, but an app that persists a new folder on every pick and never releases the old ones can.

`getPersistedDirectories()` is documented to drop folders whose document no longer exists. The documentation does not say that it detects a permission the user revoked on iOS, so a folder may still be in the list and fail on the next write. The defensive pattern covers both cases with one rule: an empty list or a failed write means "ask the user to pick again".

```typescript
const runBackup = async (json: string) => {
  const folder = await getBackupFolder();
  if (!folder) {
    return 'needs-folder';
  }
  try {
    await writeBackup(folder.uri, json);
    return 'done';
  } catch {
    return 'needs-folder';
  }
};
```

On `'needs-folder'`, show a notice with a "Choose folder" action instead of failing silently, since a backup that quietly stops is worse than one that asks. A failed write can also mean a full disk or an unavailable cloud provider, so word the notice as "We couldn't write to your backup folder" and let the user decide whether to pick a new one.

## Release and clear

[`releaseDirectoryAccess(...)`](../../sdks/capacitor/file-manager.md#releasedirectoryaccess) gives a persisted grant back to the system. Call it when the user turns backups off and before persisting a replacement folder, so the old grant does not stay in the list or count toward the Android cap:

```typescript
import { FileManager } from '@capawesome-team/capacitor-file-manager';

const changeBackupFolder = async () => {
  const current = await getBackupFolder();
  const next = await chooseBackupFolder();
  if (next && current) {
    await FileManager.releaseDirectoryAccess({ uri: current.uri });
  }
  return next;
};
```

The function releases the old folder only after the user picked a new one, so canceling the picker keeps the existing setting. If the user picks the same folder again, check the list afterwards rather than assuming two entries.

To delete all backups but keep the folder, use [`clearDirectory(...)`](../../sdks/capacitor/file-manager.md#cleardirectory) with the folder URI. It removes the contents and leaves the folder in place. Deleting the folder and creating it again looks equivalent but destroys the persisted grant, and the user has to pick the folder again.

## FAQ

### How do I remember a folder the user picked so my app can write to it later?

Persist the access grant instead of the path. Pick the folder with `pickDirectory()` from the free [Capacitor File Picker plugin](../../sdks/capacitor/file-picker.md), pass its `path` and `bookmark` to `persistDirectoryAccess(...)` of the Capacitor File Manager plugin, and call `getPersistedDirectories()` on every app start to get the folder's current URI. Never store that URI yourself, since it may change between launches.

### Does persistent folder access work on the web?

No. `pickDirectory()` is not implemented on the web, and the File Manager plugin documents that persisted directories are not available there. On the web, the File Manager plugin stores files in the browser's Origin Private File System, which is sandboxed per origin. [Announcing the Capacitor File Manager Plugin](./announcing-the-capacitor-file-manager-plugin.md) describes that implementation.

### What is the `bookmark` value from `pickDirectory()`?

It is the base64-encoded security-scoped bookmark of the picked folder, which iOS needs to reopen the folder after a relaunch. Only iOS returns it, and only from version 8.1.0 of the Capacitor File Picker plugin. Pass it to `persistDirectoryAccess(...)` unchanged; on Android the call works without it.

### How many folders can my app persist?

The File Manager plugin sets no limit, but Android does. The AOSP source caps persisted URI grants at 512 per app from Android 11 on and at 128 on Android 10, and drops the oldest grants once an app goes over. Release folders you no longer need with `releaseDirectoryAccess(...)`.

### Are the Capacitor File Manager and File Picker plugins free?

The Capacitor File Picker plugin is free and open source under the MIT license. The [Capacitor File Manager plugin](../../sdks/capacitor/file-manager.md), which owns persisting, restoring and releasing folder access, is part of the [Capawesome Insiders](../../insiders/index.md) subscription.

## Related posts

- [Android Scoped Storage in Capacitor Apps, Explained](./android-scoped-storage-in-capacitor-apps.md)
- [Announcing the Capacitor File Manager Plugin](./announcing-the-capacitor-file-manager-plugin.md)
- [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md)

## Try Capawesome Cloud

Upgrading the Capacitor File Picker plugin to 8.1.0 and adding the Capacitor File Manager plugin changes native code, so users only get the persisted folder after a new native build. Capawesome Cloud builds and signs that release for Android and iOS without a Mac, and [How to Sign & Build Capacitor Apps in the Cloud](./how-to-sign-and-build-your-capacitor-app-in-the-cloud.md) shows the setup.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

Treat the list from `getPersistedDirectories()` as the only record of which folder your app may use, and route every failure back to the picker rather than retrying in the background. If your app also writes into user folders on Android without a picker today, read [Android Scoped Storage in Capacitor Apps, Explained](./android-scoped-storage-in-capacitor-apps.md) before you plan the migration.

If you have any questions, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"}.
