Native document scanning for .NET MAUI — no paid SDK required.
dotnet add package Plugin.Maui.DocumentScanner
- Android: ML Kit document scanner (full scanner UI, auto-crop, filters; models downloaded via Google Play services)
- iOS: VisionKit document camera for scanning, plus Vision document segmentation with a built-in corner editor for cropping already-taken photos
Supports Android API 23+ (Google Play services 23.39+, device RAM 1.7 GB+) and iOS 15+.
Register the plugin in MauiProgram.cs:
builder
.UseMauiApp<App>()
.UseDocumentScanner();UseDocumentScanner() registers IDocumentScanner in dependency injection and hooks the Android activity-result plumbing — no MainActivity changes needed.
ScanAsync opens the camera, so add a usage description to Platforms/iOS/Info.plist — iOS terminates the app without it:
<key>NSCameraUsageDescription</key>
<string>Scan documents with the camera.</string>ScanFromPhotosAsync uses the system photo picker and needs no photo-library permission.
No permissions or manifest changes are required. ML Kit downloads its scanner module through Google Play services on first use, so the first scan on a device can take a few seconds longer.
Inject IDocumentScanner (or use DocumentScanner.Default without DI):
// Camera scan — returns file paths of cropped pages, empty list if the user cancels
IReadOnlyList<string> pages = await scanner.ScanAsync();
// iOS only: pick already-taken photos, then adjust each crop in the corner editor.
// On Android this throws NotSupportedException, so guard it.
if (OperatingSystem.IsIOS())
pages = await scanner.ScanFromPhotosAsync();
// With options
pages = await scanner.ScanAsync(new DocumentScanOptions
{
PageLimit = 3,
Mode = DocumentScannerMode.Base, // Android only: Full, BaseWithFilter, or Base
});
// With cancellation — dismisses the native UI and throws OperationCanceledException
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
pages = await scanner.ScanAsync(cancellationToken: cts.Token);Check scanner.IsSupported first — on Android it also verifies Google Play services is 23.39 or newer, which the scanner requires.
If a device slips past it (too little RAM, under-spec hardware), ScanAsync throws NotSupportedException.
Returned files are JPEGs written to the app's cache directory — move or copy them if you need them to persist.
| Android | iOS | |
|---|---|---|
ScanAsync |
ML Kit scanner UI, with an import-from-gallery button | VisionKit document camera |
ScanFromPhotosAsync |
Not supported — ML Kit cannot start in the gallery, so the API throws NotSupportedException |
Photo picker + auto-detected corners + manual corner editor |
PageLimit |
Applies to the scanner | ScanFromPhotosAsync only (VisionKit has no limit) |
Mode |
Full / BaseWithFilter / Base | Ignored |
The samples/ScanTest app exercises both scan paths and shows page sizes and timings. To deploy it to a physical iPhone, copy ScanTest.local.props.example to ScanTest.local.props and fill in your signing identity.
See CHANGELOG.md.