DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

How to Build a React Native MRZ Passport Scanner for Android and iOS

Verifying travel documents manually is error-prone and slow. The Machine Readable Zone (MRZ) printed on passports, ID cards, and other ICAO-compliant travel documents encodes all key identity fields in a structured, machine-readable format — but extracting it on mobile requires both an accurate OCR engine and a live camera pipeline. The Dynamsoft MRZ Scanner SDK wraps both into a single React Native call that works on Android and iOS.

What you'll build: A React Native app (Android API 21+ / iOS 13+) that opens a live camera scanner, reads the MRZ from any TD1/TD2/TD3 travel document, and displays the parsed fields — name, document number, nationality, dates — using dynamsoft-mrz-scanner-bundle-react-native v3.2.5002.

Demo Video: React Native MRZ Scanner in Action

Prerequisites

Before you start, make sure you have the following:

  • Node.js ≥ 18
  • React Native 0.79 (uses the New Architecture)
  • Android Studio (latest) with a connected device or emulator running Android 5.0 (API 21)+
  • Xcode (latest, macOS only) for iOS 13.0+ builds
  • JDK 17
  • A Dynamsoft MRZ Scanner license key — the project ships with a time-limited trial key that requires a network connection.

Get a 30-day free trial license at dynamsoft.com/customer/license/trialLicense

Step 1: Install and Configure the SDK

Install the project dependencies with npm. The two Dynamsoft packages — the MRZ scanner bundle and the underlying Capture Vision engine — are declared in package.json and will be installed automatically.

npm install
Enter fullscreen mode Exit fullscreen mode

The relevant SDK dependencies in package.json:

"dependencies": {
  "dynamsoft-capture-vision-react-native": "3.2.5002",
  "dynamsoft-mrz-scanner-bundle-react-native": "3.2.5002",
  "react": "19.0.0",
  "react-native": "0.79.0"
}
Enter fullscreen mode Exit fullscreen mode

For iOS, install the CocoaPods dependencies after npm install:

cd ios && pod install && cd ..
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure the License and Launch the MRZ Scanner

Import MRZScanner, MRZScanConfig, and EnumResultStatus from the bundle package, then call MRZScanner.launch(config) with your license key. The SDK takes over the camera, presents its built-in scanning UI, and returns only when the user finishes or cancels.

import {
  EnumResultStatus,
  MRZScanConfig,
  MRZScanner,
} from 'dynamsoft-mrz-scanner-bundle-react-native';

const handleScan = async () => {
  setScanState({kind: 'scanning'});
  try {
    const config: MRZScanConfig = {
      // Trial license — network connection required.
      // Request an extension at:
      // https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
      license:
        'LICENSE-KEY',
    };
    const result = await MRZScanner.launch(config);
    // ... handle result
  } catch (e: unknown) {
    const message = e instanceof Error ? e.message : String(e);
    setScanState({kind: 'error', code: -1, message});
  }
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Handle Scan Result States

MRZScanner.launch() resolves with a result object whose resultStatus maps to one of three EnumResultStatus values. Check the status before accessing result.data to distinguish a successful scan from a user cancellation or an SDK-level error.

if (result.resultStatus === EnumResultStatus.RS_FINISHED) {
  setScanState({kind: 'result', data: result.data as Record<string, string>});
} else if (result.resultStatus === EnumResultStatus.RS_CANCELED) {
  setScanState({kind: 'cancelled'});
} else {
  setScanState({
    kind: 'error',
    code: result.errorCode ?? -1,
    message: result.errorString ?? 'An unknown error occurred.',
  });
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Display Parsed MRZ Fields in the UI

React Native MRZ Scanner powered by Dynamsoft

When the status is RS_FINISHED, result.data is a flat key-value map of parsed MRZ fields. The sample app promotes high-value fields — firstName, lastName, documentNumber, documentType — to a "hero" section and lists the rest in a scrollable card.

const HERO_FIELDS = ['firstName', 'lastName', 'documentNumber', 'documentType'];

function ResultCard({data}: ResultCardProps) {
  const heroEntries = HERO_FIELDS.map(k => [k, data[k]] as [string, string]).filter(
    ([, v]) => v != null && v !== '',
  );
  const otherEntries = Object.entries(data).filter(
    ([k, v]) => !HERO_FIELDS.includes(k) && v != null && v !== '',
  );

  return (
    <View style={styles.resultCard}>
      {heroEntries.length > 0 && (
        <View style={styles.heroSection}>
          {heroEntries.map(([key, value]) => (
            <View key={key} style={styles.heroRow}>
              <Text style={styles.heroLabel}>{formatLabel(key)}</Text>
              <Text style={styles.heroValue}>{value}</Text>
            </View>
          ))}
        </View>
      )}
      {/* other fields omitted for brevity */}
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Handle the Android Hardware Back Button

On Android the hardware back button exits the React Native app by default. The sample app intercepts it with BackHandler so users return to the idle state rather than closing the app mid-workflow.

useEffect(() => {
  const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
    if (scanState.kind !== 'idle') {
      setScanState({kind: 'idle'});
      return true; // event handled — prevent default (app exit)
    }
    return false; // let the system handle it (idle → exit as normal)
  });
  return () => subscription.remove();
}, [scanState.kind]);
Enter fullscreen mode Exit fullscreen mode

Source Code

https://github.com/yushulx/android-camera-barcode-mrz-document-scanner/tree/main/examples/react-native-mrz-scanner

Top comments (0)