Document Scanning in Flutter: Capture, Enhance, and OCR
Add on-device document scanning with ML Kit or VisionKit - edge detection, auto-capture, PDF output - then run OCR with google_mlkit_text_recognition.
Published on • November 10, 2026
AI Assistant

Users expect a phone app to turn a crumpled receipt into a clean PDF, complete with perspective correction, and ideally extract the text afterwards. Getting there means choosing between Google’s ML Kit document scanner and lighter plugins, then chaining the result into an OCR pass. This post compares the real pub.dev options, shows the scanning APIs, and wires up a scan -> enhance -> OCR pipeline.
Four scanner packages, four support stories
| Package | Platforms | Backing tech | Status |
|---|---|---|---|
| google_mlkit_document_scanner 0.7.1 | Android only (per its README: beta, “only available for Android”) | ML Kit via Google Play services | Active, published days ago |
| cunning_document_scanner 3.1.0 | Android + iOS | ML Kit on Android, Vision on iOS | Active, published days ago |
| flutter_document_scanner 1.1.2 | Android + iOS | camera plugin + OpenCV (Android) / VisionKit (iOS) | Marked “Under Development” |
| doc_scanner 0.0.2 | Android + iOS | FFI, Pakistani ID documents | Discontinued |
dependencies:
flutter:
sdk: flutter
google_mlkit_document_scanner: ^0.7.1
google_mlkit_text_recognition: ^1.0.1
Pick the ML Kit wrapper when you want Google’s polished flow, or cunning_document_scanner when you need iOS today.
What the ML Kit scanner does on device
Google’s scanner delivers automatic capture with document detection, accurate edge detection for cropping, automatic rotation detection so pages come out upright, and in-flow editing (crop, filters, shadow/stain removal). Because the UI flow, ML models, and large resources ship through Google Play services, the plugin’s APK impact stays low and - unusually for a camera feature - your app needs no camera permission: the scanner runs outside your app and users choose what to share back.
const options = DocumentScannerOptions(
documentFormats: {DocumentFormat.jpeg, DocumentFormat.pdf},
mode: ScannerMode.filter, // ScannerMode.full / filter / base
pageLimit: 3,
isGalleryImport: true,
);
final scanner = DocumentScanner(options: options);
final DocumentScanningResult result = await scanner.scanDocument();
final pdf = result.pdf; // pdf?.uri, pdf?.pageCount
final images = result.images; // List<String> of page image paths
scanner.close(); // release the native resources
ScannerMode.base stops after capture and crop, filter adds the filter step, and full runs the whole edit flow - useful when you want fewer screens. Requirements are minSdkVersion 21 with target/compile SDK 36, and note the plugin authors’ disclaimer that using it in production is the consuming developer’s responsibility.
Permissions and platform setup
The ML Kit scanner needs no camera permission on Android, but the alternatives do. cunning_document_scanner (min Android API 24, iOS 13.0) requires:
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- ios/Runner/Info.plist - iOS terminates apps that omit this key -->
<key>NSCameraUsageDescription</key>
<string>Access to the camera is required to scan documents.</string>
The plugin requests camera permission itself through AVCaptureDevice only when the camera flow opens; gallery-only flows prompt for nothing. Its Android edge detection comes from ML Kit (with a built-in fallback cropper on devices without Google Play services), and its iOS side uses Apple’s document camera (VNDocumentCameraViewController). flutter_document_scanner instead leans on the camera plugin, so you handle camera permissions the standard way.
Getting PDFs and page images out
cunning_document_scanner returns a ScanResult (or null when the user cancels):
final result = await CunningDocumentScanner.scanDocuments(
noOfPages: 5,
output: ScanOutput.imagesAndPdf, // images, pdf, or both
scannerSource: ScannerSource.cameraAndGallery,
iosScannerOptions: IosScannerOptions(
imageFormat: IosImageFormat.jpg, // PNG is the iOS default
jpgCompressionQuality: 0.5, // much smaller files
),
);
if (result == null) return; // user backed out
print(result.images); // one path per page, in order
print(result.pdf); // null unless a PDF was requested
if (result.discardedPages > 0) {
// pages over noOfPages were dropped - tell the user
}
Error handling is explicit: ArgumentError for invalid noOfPages or compression values, and CunningDocumentScannerException (codes such as ALREADY_ACTIVE, NO_ACTIVITY, UNAVAILABLE) when permission is denied or the native flow fails. Also mind file lifetime: iOS output lives in Library/Caches and can be reclaimed by the system, while Android keeps it in the app’s private pictures directory - copy anything durable into your own storage and call CunningDocumentScanner.cleanCache() when done.
The scan -> enhance -> OCR pipeline
Enhancement mostly happens inside the scanner UI (filters, grayscale, B&W). The last stage is text extraction with google_mlkit_text_recognition:
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
final recognizer = TextRecognizer(script: TextRecognitionScript.latin);
for (final path in result.images) {
final input = InputImage.fromFilePath(path); // from google_mlkit_commons
final RecognizedText recognized = await recognizer.processImage(input);
print(recognized.text);
for (final block in recognized.blocks) {
for (final line in block.lines) {
// line.text, line.confidence and line.angle are Android-only (null on iOS)
print(line.text);
}
}
}
recognizer.close();
The structure is RecognizedText -> blocks -> lines -> elements -> symbols, which lets you keep layout instead of dumping one string. Two platform caveats: Android bundles Latin only by default, so add implementation 'com.google.mlkit:text-recognition-chinese:16.0.1' (or japanese/korean/devanagari) to build.gradle for other scripts, while iOS currently supports only the Latin set and needs its SwiftPM setup (-ObjC and -All_load linker flags plus the LatinOCRResources.bundle). Always close() the recognizer and the scanner when the flow ends - both hold native resources.
Takeaway: Choose google_mlkit_document_scanner for a polished Android-only ML Kit flow or cunning_document_scanner when you need Android and iOS with PDF output, declare the camera permissions the alternative scanners require, handle cancellations and ScanResult file paths deliberately, and finish with google_mlkit_text_recognition per page - remembering iOS is Latin-only.
Sources
- https://pub.dev/packages/google_mlkit_document_scanner
- https://pub.dev/packages/cunning_document_scanner
- https://pub.dev/packages/flutter_document_scanner
- https://pub.dev/packages/doc_scanner
- https://pub.dev/packages/google_mlkit_text_recognition
- https://developers.google.com/ml-kit/vision/doc-scanner