
Maui File Handling
- 29 installs
- 163 repo stars
- Updated July 6, 2026
- davidortinau/maui-skills
Covers file picker, file system helpers, bundled assets, and app data storage in .NET MAUI apps including FilePicker APIs and FileResult handling.
About
Guides file picking, file system helpers, bundled assets and app data storage in .NET MAUI apps including FilePicker APIs and FileResult handling. A developer uses it when reading, writing, or picking files in a MAUI app.
- FilePicker APIs and FileResult handling
- File system helpers, bundled assets, and app data storage
Maui File Handling by the numbers
- 29 all-time installs (skills.sh)
- Ranked #672 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davidortinau/maui-skills --skill maui-file-handlingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 29 |
|---|---|
| repo stars | ★ 163 |
| Last updated | July 6, 2026 |
| Repository | davidortinau/maui-skills ↗ |
What it does
Covers file picker, file system helpers, bundled assets, and app data storage in .NET MAUI apps including FilePicker APIs and FileResult handling.
Files
.NET MAUI File Handling
Critical: Always Use OpenReadAsync(), Not FullPath
Some platforms (especially Android) return content URIs, not file system paths. Reading FullPath directly will throw or return empty data.
// ❌ Breaks on Android — FullPath may be a content:// URI
var result = await FilePicker.Default.PickAsync();
var bytes = File.ReadAllBytes(result.FullPath);
// ✅ Works on all platforms
var result = await FilePicker.Default.PickAsync();
if (result is not null)
{
using var stream = await result.OpenReadAsync();
// process stream
}---
Platform File-Type Format Gotcha
Each platform uses a different format for custom FilePickerFileType. Mixing them up causes the picker to show no files or crash.
| Platform | Format | Example |
|---|---|---|
| Android | MIME types | "application/json" |
| iOS / macOS | UTType identifiers | "public.json" |
| Windows | Dot-prefixed extensions | ".json" |
// ❌ Using file extensions for Android — picker shows nothing
{ DevicePlatform.Android, new[] { ".json", ".txt" } }
// ✅ Correct MIME types for Android
{ DevicePlatform.Android, new[] { "application/json", "text/plain" } }---
Common Pitfalls
Bundled files are read-only
Resources/Raw assets cannot be modified at runtime. Attempting to write throws an exception (or silently fails on some platforms).
// ❌ Trying to write to a bundled file
var path = "data.json"; // inside Resources/Raw
File.WriteAllText(path, newContent); // fails
// ✅ Copy to AppDataDirectory first, then modify
string targetPath = Path.Combine(FileSystem.Current.AppDataDirectory, "data.json");
if (!File.Exists(targetPath))
{
using var source = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
using var dest = File.Create(targetPath);
await source.CopyToAsync(dest);
}
// Now safe to read/write targetPathBundled subdirectories are flattened
On some platforms, Resources/Raw/subdir/file.txt becomes just file.txt. Use unique file names regardless of subdirectory structure.
iOS sandbox path changes on rebuild
The iOS sandbox path includes an app GUID that changes across clean builds. Hard-coded absolute paths break silently.
// ❌ Hard-coded path — breaks after clean rebuild
var path = "/var/mobile/.../Documents/data.json";
// ✅ Always use the FileSystem helper
var path = Path.Combine(FileSystem.Current.AppDataDirectory, "data.json");Android: bundled stream has no Length
OpenAppPackageFileAsync may return a stream where .Length throws NotSupportedException. Copy to a MemoryStream if you need the size.
// ❌ Throws on Android
using var stream = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
var size = stream.Length; // NotSupportedException
// ✅ Copy first if you need the length
using var stream = await FileSystem.Current.OpenAppPackageFileAsync("data.json");
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var size = ms.Length;Windows: virtualized file system
Packaged apps silently redirect writes to classic paths like %AppData%. Always use AppDataDirectory and CacheDirectory for reliable cross-platform paths.
FilePicker returns null on cancellation
// ❌ NullReferenceException when user cancels
var result = await FilePicker.Default.PickAsync();
using var stream = await result.OpenReadAsync();
// ✅ Always null-check
var result = await FilePicker.Default.PickAsync();
if (result is null) return;
using var stream = await result.OpenReadAsync();---
Android Permissions (API 33+ change)
Android 13 replaced READ_EXTERNAL_STORAGE with granular media permissions. Using the old permission on API 33+ silently grants nothing.
| Android version | Required permission |
|---|---|
| ≤ 12 (API 32) | READ_EXTERNAL_STORAGE |
| ≥ 13 (API 33) | READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO |
---
Checklist
- [ ] Use
OpenReadAsync()— never readFullPathdirectly - [ ] Null-check
FilePickerresult before accessing properties - [ ] Custom
FilePickerFileTypeuses correct format per platform (MIME / UTType / extension) - [ ] Bundled files copied to
AppDataDirectorybefore modification - [ ] Unique file names in
Resources/Raw(subdirectories are flattened) - [ ] Android manifest declares correct permission for target API level
- [ ] macOS: App Sandbox entitlement includes file access
File Handling API Reference
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
});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 |
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;
}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 |