maui-file-handling
.NET MAUI File Handling
FilePicker API
Use FilePicker.Default to let users select files from the device.
Single file
var result = await FilePicker.Default.PickAsync(new PickOptions
{
PickerTitle = "Select a file",
FileTypes = FilePickerFileType.Images
});
if (result is not null)
{
using var stream = await result.OpenReadAsync();
// process stream
}
Multiple files
var results = await FilePicker.Default.PickMultipleAsync(new PickOptions
{
PickerTitle = "Select files",
FileTypes = FilePickerFileType.Videos
});
foreach (var file in results)
{
// file.FileName, file.FullPath, file.ContentType
}
PickOptions
| Property | Type | Purpose |
|---|---|---|
PickerTitle |
string |
Title shown on the picker dialog |
FileTypes |
FilePickerFileType |
Restricts selectable file types |
FilePickerFileType
Built-in types
FilePickerFileType.Images— common image formatsFilePickerFileType.Png— PNG onlyFilePickerFileType.Jpeg— JPEG onlyFilePickerFileType.Videos— common video formatsFilePickerFileType.Pdf— PDF files
Custom per-platform type
var customFileType = new FilePickerFileType(
new Dictionary<DevicePlatform, IEnumerable<string>>
{
{ DevicePlatform.Android, new[] { "application/json", "text/plain" } }, // MIME types
{ DevicePlatform.iOS, new[] { "public.json", "public.plain-text" } }, // UTTypes
{ DevicePlatform.macOS, new[] { "public.json", "public.plain-text" } }, // UTTypes
{ DevicePlatform.WinUI, new[] { ".json", ".txt" } } // file extensions
});
Key rule: Android uses MIME types, iOS/macOS use UTType identifiers, Windows uses dot-prefixed file extensions.
FileResult
Returned by PickAsync and PickMultipleAsync.
| Property | Type | Notes |
|---|---|---|
FullPath |
string |
Platform-specific absolute path |
FileName |
string |
File name with extension |
ContentType |
string |
MIME type of the file |
OpenReadAsync() |
Task<Stream> |
Preferred way to read file contents |
Best practice: Always use
OpenReadAsync()instead of readingFullPathdirectly—some platforms return content URIs, not file system paths.
FileSystem Helpers
Access via FileSystem.Current.
Directory paths
| Property | Purpose | Writable |
|---|---|---|
CacheDirectory |
Temp/cache data | Yes |
AppDataDirectory |
Persistent app-private data | Yes |
Reading bundled files
using var stream = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
using var reader = new StreamReader(stream);
string contents = await reader.ReadToEndAsync();
Bundled Files (Resources/Raw)
Place files in the Resources/Raw folder. They receive the MauiAsset build action automatically.
- Files are read-only at runtime.
- Access via
OpenAppPackageFileAsync("filename.ext"). - Subdirectories are flattened on some platforms—use unique file names.
Copy bundled file to writable location
public async Task<string> CopyToAppDataAsync(string filename)
{
string targetPath = Path.Combine(FileSystem.Current.AppDataDirectory, filename);
if (!File.Exists(targetPath))
{
using var source = await FileSystem.Current.OpenAppPackageFileAsync(filename);
using var dest = File.Create(targetPath);
await source.CopyToAsync(dest);
}
return targetPath;
}
Use this pattern whenever you need to modify a bundled file at runtime.
Permissions
Android
| Android version | Permission required |
|---|---|
| ≤ 12 (API 32) | READ_EXTERNAL_STORAGE |
| ≥ 13 (API 33) | READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO (granular) |
Declare in Platforms/Android/AndroidManifest.xml. Request at runtime with Permissions.RequestAsync<Permissions.StorageRead>() or the granular media permissions.
iOS
- FilePicker works without special permissions for on-device files.
- For iCloud access, enable the iCloud capability and configure entitlements.
macOS (Mac Catalyst)
- Enable App Sandbox entitlements.
- Grant
com.apple.security.files.user-selected.read-only(or read-write) for picker access.
Windows
- Packaged apps have full picker access without extra declarations.
Platform Path Differences
| Platform | AppDataDirectory location |
CacheDirectory location |
|---|---|---|
| Android | /data/data/<package>/files |
/data/data/<package>/cache |
| iOS / macOS | <app-sandbox>/Library/ |
<app-sandbox>/Library/Caches/ |
| Windows | <LocalAppData>/<PackageName>/LocalState |
<LocalAppData>/<PackageName>/LocalCache |
Common Pitfalls
-
Cannot modify bundled files directly.
Resources/Rawassets are embedded read-only. Copy toAppDataDirectoryfirst. -
iOS app ID changes on rebuild. The sandbox path includes an app GUID that changes across clean builds. Never hard-code absolute paths—always use
FileSystem.Current.AppDataDirectory. -
Android: cannot get bundled stream length.
OpenAppPackageFileAsyncmay return a stream whereLengththrows. Read the stream fully or copy to aMemoryStreamif you need the size. -
Windows: virtualized file system for packaged apps. Writes to classic paths like
%AppData%are silently redirected. UseAppDataDirectoryandCacheDirectoryfor reliable cross-platform paths. -
FilePicker returns null on cancellation. Always null-check the result before accessing properties.