easy_mrz is a Flutter MRZ scanner for passports, visas, and identity documents
with machine-readable zones.
This package is a revamp of the original flutter_mrz_scanner by Oleksandr Leushchenko. The public Flutter API was kept familiar, while the native implementations were reworked for modern iOS and Android builds.
Agent and tooling notes live in AGENTS.md. Internal implementation notes live in doc/ARCHITECTURE.md.
- Local-only OCR on both iOS and Android
- Live camera scanning with Flutter platform views
- Parsed MRZ output through
mrz_parser - Optional overlay to guide document placement
- Flashlight control
- Still photo capture with optional crop
- Passport-focused Android heuristics for better TD3 MRZ handling
- iOS 13+
- Android 21+
easy_mrz uses:
AVFoundationfor camera preview/captureVision(VNRecognizeTextRequest) for OCR
Why:
- Apple Vision is fast and highly optimized on Apple hardware
- It keeps OCR fully local on-device
- It avoids shipping third-party OCR models on iOS
- In practice, it is the fastest and most stable path for passport MRZ on iPhone
easy_mrz uses:
CameraXfor preview, torch, lifecycle binding, and still captureTesseract4Androidwith bundledocrb.traineddata- Passport-specific OCR heuristics on top of Tesseract
Why:
- Android OCR quality was inconsistent with the previous generic text pipeline
on some devices, especially when
ImageAnalysisframes were negotiated at low resolution - MRZ text uses an OCR-B style character set, which Tesseract can target more directly with a whitelist and a bundled traineddata file
- The package needs to remain fully local and offline
- CameraX is the current Android camera stack that best fits Flutter plugin lifecycle handling
Tradeoff:
- iOS is generally faster out of the box
- Android requires more normalization and scoring logic because device camera behavior varies much more
OCR stays local on the device. The plugin does not send camera frames or MRZ text to a server.
dependencies:
easy_mrz: ^1.0.0Then run:
flutter pub getAdd camera usage text to Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan MRZ documents.</string>Declare camera permission in your app manifest if it is not already present:
<uses-permission android:name="android.permission.CAMERA" />import 'package:easy_mrz/easy_mrz.dart';
import 'package:flutter/material.dart';
class ScannerPage extends StatelessWidget {
const ScannerPage({super.key});
@override
Widget build(BuildContext context) {
return MRZScanner(
withOverlay: true,
onControllerCreated: (controller) {
controller.onParsed = (result) {
debugPrint('MRZ parsed: ${result.toJson()}');
};
controller.onError = (message) {
debugPrint('Scanner error: $message');
};
controller.startPreview();
},
);
}
}MRZScanner provides an MRZController.
Methods:
startPreview({bool isFrontCam = false})stopPreview()flashlightOn()flashlightOff()takePhoto({bool crop = true})
Callbacks:
onParsed(MRZResult result)onError(String message)
- Native code captures and OCRs candidate MRZ text.
- Native code sends normalized candidate lines to Dart through
onParsed. - Dart runs
mrz_parserto validate and parse the candidate intoMRZResult.
This split is intentional:
- Native code owns camera and OCR behavior
- Dart owns the final parsing contract
- The Flutter API remains stable even if native OCR changes later
Android scanning is tuned to stay local and reasonably fast while still being strict enough to reject obvious garbage OCR.
Current Android strategy:
- Prefer passport TD3 MRZ detection first
- Crop the lower MRZ band
- OCR each passport line separately
- Repair common OCR substitutions like
0/O,1/I,5/S - Score candidates using MRZ structure and check digits
- Require repeated weaker detections before emitting them
This is more complex than the iOS path because Android device camera behavior varies more across vendors and frame pipelines.
For best results:
- Use a real device
- Keep the document inside the overlay
- Use even lighting
- Avoid glare on laminated pages
- Hold the phone steady for a brief moment when Android is close to locking
Example identifiers in this repository:
- Plugin package:
com.mlabs.easy_mrz - Example Android applicationId:
com.example.easy_mrz - Example iOS bundle identifier:
com.example.easymrz
Run the example:
cd example
flutter runThe plugin does not force the same OCR stack on both platforms.
That is intentional:
- iOS has first-party Vision APIs that are better than shipping a separate OCR engine there
- Android benefits from a more controlled OCR-B-oriented approach for MRZ scanning, especially on devices where generic text OCR is noisy
The goal is not stack symmetry. The goal is reliable local MRZ scanning.
Original project and concept: Oleksandr Leushchenko
Revamp and maintenance: kingace2056
MIT. See LICENSE.