A Flutter plugin for picking a directory across all platforms using native system dialogs. Returns a PickedLocation? — null if the user cancelled, and can list entries from a previously picked directory with DirPicker.listEntries().
- 📁 All Platforms – Android, iOS, macOS, Windows, Linux, and Web
- ⚡ Native Performance – Powered by FFI (iOS/macOS/Windows) and JNI (Android) for near-zero overhead
- 🎨 Customizable Dialogs – Platform-specific options for title, button labels, and more
- 🔗 Persistent Permissions – Android SAF persistent URI access across reboots
- 🐧 Linux Portals – XDG Desktop Portal with zenity/kdialog fallback
If you want to say thank you, star us on GitHub or like us on pub.dev.
First, follow the package installation instructions and add
dir_picker to your app.
Android Configuration
Supported: API 21+ (Android 5.0+)
No configuration needed. The plugin uses the Storage Access Framework (Intent.ACTION_OPEN_DOCUMENT_TREE), which grants URI access through the system picker UI — no manifest permissions required.
iOS Configuration
Supported: iOS 13.0+
No configuration needed. The plugin uses UIDocumentPickerViewController, which is available without extra entitlements.
macOS Configuration
Supported: macOS 10.15+
Add the following entitlement to macos/Runner/Release.entitlements and macos/Runner/DebugProfile.entitlements:
<key>com.apple.security.files.user-selected.read-write</key>
<true/>Windows Configuration
Supported: Windows 10+
No configuration needed. The plugin uses the native IFileOpenDialog COM API.
Linux Configuration
The plugin tries the following dialog backends in order:
- XDG Desktop Portal – Works on all modern desktop environments via D-Bus (
org.freedesktop.portal.FileChooser) - zenity – GNOME fallback
- kdialog – KDE fallback
If none are available, the pick call throws. To install a fallback manually:
# GNOME
sudo apt install zenity
# KDE
sudo apt install kdialogWeb Configuration
No configuration needed. The plugin uses the File System Access API.
Browser support: Chrome 86+ and Edge 86+. Not supported in Firefox or Safari.
Note: On web,
pick()returns aWebPickedLocationwrapping aFileSystemDirectoryHandle. Use.handleto access directory contents via the File System Access API.PickedLocation.uriis alwaysnullon web — browsers do not expose full filesystem paths. Requirespackage:webin your app's dependencies to work with the handle directly.
import 'package:dir_picker/dir_picker.dart';
final PickedLocation? location = await DirPicker.pick();
if (location == null) {
print('Cancelled');
} else if (location is WebPickedLocation) {
// Web: use handle to access directory contents via File System Access API
// Requires package:web in your app's dependencies
final handle = location.handle; // FileSystemDirectoryHandle
print('Selected directory: ${location.name}');
} else {
// Native (Android, iOS, macOS, Windows, Linux)
print('Selected: ${location.uri}');
}final location = await DirPicker.pick();
if (location == null) return;
final entries = await DirPicker.listEntries(location, recursive: true);
for (final entry in entries) {
print('${entry.relativePath} (dir: ${entry.isDirectory})');
}Pass a PickOptions to DirPicker.pick() to customize the dialog for the current platform:
final location = await DirPicker.pick(
options: PickOptions.android(shouldPersist: true),
);
final location = await DirPicker.pick(
options: PickOptions.macos(acceptLabel: 'Choose', message: 'Select a project folder'),
);
final location = await DirPicker.pick(
options: PickOptions.linux(title: 'Select Folder', acceptLabel: 'Choose'),
);
final location = await DirPicker.pick(
options: PickOptions.windows(title: 'Select Folder', acceptLabel: 'Choose'),
);Options for other platforms are silently ignored — only the one matching the current platform is applied.
| Parameter | Type | Default | Description |
|---|---|---|---|
shouldPersist |
bool |
true |
Take persistable URI permission so the app retains access across reboots (SAF takePersistableUriPermission). |
| Parameter | Type | Default | Description |
|---|---|---|---|
acceptLabel |
String |
'Select' |
Label for the confirmation button (NSOpenPanel.prompt). |
message |
String |
'Choose a directory' |
Descriptive text shown inside the panel (NSOpenPanel.message). |
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
String |
'Select Directory' |
Window title of the dialog. |
acceptLabel |
String |
'Select' |
Label for the confirmation button (XDG Portal and zenity only). |
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
String |
'Select Directory' |
Window title of the dialog (IFileDialog::SetTitle). |
acceptLabel |
String |
'Select' |
Label for the confirmation button (IFileDialog::SetOkButtonLabel). |
| Result | Meaning |
|---|---|
IOPickedLocation |
Native platforms — use .uri to get the selected directory URI. |
WebPickedLocation |
Web — use .handle (FileSystemDirectoryHandle) to access directory contents. .uri is null. |
null |
The user cancelled. |
| Platform | Mechanism |
|---|---|
| Android | SAF (Intent.ACTION_OPEN_DOCUMENT_TREE) via JNI |
| iOS | UIDocumentPickerViewController via FFI |
| macOS | NSOpenPanel via FFI |
| Windows | IFileOpenDialog (COM) via pure Dart FFI |
| Linux | XDG Desktop Portal → zenity → kdialog |
| Web | window.showDirectoryPicker() (JS interop) |
final location = await DirPicker.pick();
if (location != null) {
print('Selected: ${location.uri}');
}import 'package:dir_picker/dir_picker.dart';
import 'package:web/web.dart' as web; // required to use FileSystemDirectoryHandle
final location = await DirPicker.pick();
if (location is WebPickedLocation) {
final web.FileSystemDirectoryHandle handle = location.handle;
// list files, read contents, etc.
}final location = await DirPicker.pick(
options: PickOptions.macos(
acceptLabel: 'Use This Folder',
message: 'Select the folder to import from',
),
);
// or on Linux/Windows:
final location = await DirPicker.pick(
options: PickOptions.linux(
title: 'Import Folder',
acceptLabel: 'Use This Folder',
),
);final location = await DirPicker.pick(
options: PickOptions.android(shouldPersist: true),
);static Future<PickedLocation?> pick({PickOptions? options})Returns a PickedLocation (either IOPickedLocation or WebPickedLocation), or null if the user cancelled.
options is a PickOptions sealed class — pass the platform-specific subclass using its factory constructor (e.g. PickOptions.android(...), PickOptions.macos(...)). Options for other platforms are ignored.
static Future<List<FileSystemEntry>> listEntries(
PickedLocation location, {
bool recursive = false,
})Lists files and directories inside a previously picked location.
Behavior:
- Returns a flat list of descendants under the picked root.
- Excludes the picked root itself.
- Lists only immediate children when
recursiveisfalse. - Lists all nested descendants when
recursiveistrue. - Does not guarantee output ordering.
Base type returned by DirPicker.pick().
| Type | Platform | Notes |
|---|---|---|
IOPickedLocation |
Android, iOS, macOS, Windows, Linux | Provides a non-null uri. |
WebPickedLocation |
Web | Provides a FileSystemDirectoryHandle; uri is always null. |
Model returned by DirPicker.listEntries().
| Field | Type | Description |
|---|---|---|
name |
String |
Basename of the entry. |
relativePath |
String |
Path relative to the picked root, always using / separators. |
isDirectory |
bool |
Whether the entry is a directory. |
uri |
Uri? |
Native URI when available; always null on web. |
size |
int? |
File size in bytes; null for directories or when unavailable. |
lastModified |
DateTime? |
Last modified timestamp when available. |
Platform-specific options passed to DirPicker.pick().
| Factory | Platform | Purpose |
|---|---|---|
PickOptions.android() |
Android | Controls whether SAF URI permission should be persisted. |
PickOptions.macos() |
macOS | Customizes the open panel accept label and message. |
PickOptions.linux() |
Linux | Customizes dialog title and accept label. |
PickOptions.windows() |
Windows | Customizes dialog title and accept label. |
| Android | iOS | macOS | Windows | Linux | Web |
|---|---|---|---|---|---|
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Minimum versions:
- Flutter ≥ 3.3.0
- Dart SDK ≥ 3.6.0
- Kotlin 2.1.0
- Swift 5.9
- Android API 21+
- iOS 13.0+
- macOS 10.15+
- Windows 10+
- Web: Chrome/Edge 86+
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License — see LICENSE file for details.