
Maui Media Picker
- 31 installs
- 163 repo stars
- Updated July 6, 2026
- davidortinau/maui-skills
Picks photos and videos, captures from the camera, and handles multi-select and permissions in .NET MAUI apps.
About
Guides picking photos and videos, capturing from camera, multi-select, MediaPickerOptions, platform permissions and FileResult handling in .NET MAUI. A developer uses it when adding camera capture or media picking to a MAUI app.
- Pick or capture photos and videos with the camera
- Multi-select, MediaPickerOptions, and FileResult handling
Maui Media Picker by the numbers
- 31 all-time installs (skills.sh)
- Ranked #661 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-media-pickerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| repo stars | ★ 163 |
| Last updated | July 6, 2026 |
| Repository | davidortinau/maui-skills ↗ |
What it does
Picks photos and videos, captures from the camera, and handles multi-select and permissions in .NET MAUI apps.
Files
.NET MAUI Media Picker — Gotchas & Best Practices
Common Mistakes
1. Forgetting to check for cancellation
Single-select returns null, multi-select returns an empty list. Not checking causes NullReferenceException.
// ❌ Crashes when user cancels
var photo = await MediaPicker.Default.PickPhotoAsync();
using var stream = await photo.OpenReadAsync();
// ✅ Always check for null / empty
var photo = await MediaPicker.Default.PickPhotoAsync();
if (photo is null) return;
using var stream = await photo.OpenReadAsync();2. Calling from a background thread
All MediaPicker methods must run on the UI thread or they throw.
// ❌ Will throw on some platforms
await Task.Run(async () => await MediaPicker.Default.PickPhotoAsync());
// ✅ Call directly from a command or event handler on the UI thread
var photo = await MediaPicker.Default.PickPhotoAsync();3. Not checking IsCaptureSupported
Devices without cameras (emulators, some tablets) will throw if you call capture methods.
// ❌ Crashes on devices without a camera
var photo = await MediaPicker.Default.CapturePhotoAsync();
// ✅ Guard with capability check
if (MediaPicker.Default.IsCaptureSupported)
{
var photo = await MediaPicker.Default.CapturePhotoAsync();
}4. Not disposing streams
OpenReadAsync() returns a stream that must be disposed.
// ❌ Leaks the stream
var stream = await photo.OpenReadAsync();
var bytes = ReadAllBytes(stream);
// ✅ Dispose with using
using var stream = await photo.OpenReadAsync();Platform Pitfalls
⚠️ SelectionLimit is not enforced on Android/Windows
MediaPickerOptions.SelectionLimit is advisory. Always validate the count yourself:
var options = new MediaPickerOptions { SelectionLimit = 5 };
var results = await MediaPicker.Default.PickPhotosAsync(options);
if (results.Count() > 5)
{
// Warn user or take only the first 5
results = results.Take(5);
}⚠️ Android storage permissions split at API 33
- API ≤ 32: needs
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE - API ≥ 33: needs
READ_MEDIA_IMAGES/READ_MEDIA_VIDEO(broad storage permissions are ignored) - Use
android:maxSdkVersion="32"on the old permissions to avoid Play Store warnings
⚠️ iOS requires all four plist keys for full functionality
Missing any one of NSCameraUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription, or NSPhotoLibraryAddUsageDescription causes a runtime crash when that feature is accessed.
⚠️ Android requires <queries> for camera intents
Without the IMAGE_CAPTURE query in AndroidManifest.xml, CapturePhotoAsync may fail silently on Android 11+ due to package visibility restrictions.
Decision Framework
| Scenario | Method |
|---|---|
| User picks one photo | PickPhotoAsync() |
| User picks multiple (.NET 10+) | PickPhotosAsync() with SelectionLimit |
| App needs camera capture | Check IsCaptureSupported → CapturePhotoAsync() |
| Save picked file permanently | Copy stream to FileSystem.AppDataDirectory |
| Need photo metadata | Set PreserveMetaData = true in MediaPickerOptions |
Checklist
- [ ] All picker calls run on the UI thread
- [ ] Null/empty checks after every pick/capture call
- [ ]
IsCaptureSupportedguard before capture methods - [ ] Streams disposed with
using - [ ] Android manifest has both old and new storage permissions with
maxSdkVersion - [ ] Android manifest has
<queries>block for camera intent - [ ] iOS
Info.plisthas all four usage description keys - [ ]
SelectionLimitvalidated in code, not trusted from platform
Media Picker API Reference
Core API
Use MediaPicker.Default to pick or capture photos and videos. All methods must run on the UI thread.
Single-select methods (all .NET versions)
| Method | Purpose |
|---|---|
MediaPicker.Default.PickPhotoAsync() | Pick one photo from gallery |
MediaPicker.Default.PickVideoAsync() | Pick one video from gallery |
MediaPicker.Default.CapturePhotoAsync() | Capture a photo with the camera |
MediaPicker.Default.CaptureVideoAsync() | Capture a video with the camera |
All return Task<FileResult?>. A null result means the user cancelled.
Multi-select methods (.NET 10+)
| Method | Purpose |
|---|---|
MediaPicker.Default.PickPhotosAsync() | Pick multiple photos |
MediaPicker.Default.PickVideosAsync() | Pick multiple videos |
These return Task<IEnumerable<FileResult>>. An empty list means the user cancelled.
MediaPickerOptions (.NET 10+)
Pass MediaPickerOptions to any pick/capture method to control behavior:
var options = new MediaPickerOptions
{
Title = "Select photos", // Picker dialog title
SelectionLimit = 5, // Max items (0 = unlimited; multi-select only)
MaximumWidth = 1024, // Resize max width in pixels
MaximumHeight = 1024, // Resize max height in pixels
CompressionQuality = 80, // JPEG quality 0–100
RotateImage = true, // Auto-rotate per EXIF
PreserveMetaData = true // Keep EXIF/metadata
};
var photos = await MediaPicker.Default.PickPhotosAsync(options);FileResult Usage
var photo = await MediaPicker.Default.PickPhotoAsync();
if (photo is null)
return; // user cancelled
// Read the stream
using var stream = await photo.OpenReadAsync();
// Useful properties
string fullPath = photo.FullPath;
string fileName = photo.FileName;
string contentType = photo.ContentType;Save to app storage
async Task<string> SaveToAppDataAsync(FileResult fileResult)
{
var targetPath = Path.Combine(FileSystem.AppDataDirectory, fileResult.FileName);
using var sourceStream = await fileResult.OpenReadAsync();
using var targetStream = File.OpenWrite(targetPath);
await sourceStream.CopyToAsync(targetStream);
return targetPath;
}Check Availability Before Capture
if (MediaPicker.Default.IsCaptureSupported)
{
var photo = await MediaPicker.Default.CapturePhotoAsync();
// ...
}Multi-select Example (.NET 10+)
var options = new MediaPickerOptions { SelectionLimit = 10 };
var results = await MediaPicker.Default.PickPhotosAsync(options);
if (!results.Any())
return; // user cancelled
foreach (var file in results)
{
using var stream = await file.OpenReadAsync();
// process each selected photo
}Platform Permissions
Android
Add to Platforms/Android/AndroidManifest.xml:
<!-- Camera capture -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- Storage: API ≤ 32 (Android 12 and below) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Storage: API ≥ 33 (Android 13+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />Also add inside <application>:
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>iOS / Mac Catalyst
Add to Platforms/iOS/Info.plist (and Platforms/MacCatalyst/Info.plist):
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to record video</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to pick media</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs permission to save photos</string>Windows
No additional permissions required.