Back to blog
March 25, 20264 min read

How to Build a Web-Based Passport Scanner with React and Passport OCR

reacttutorialweb-ocrmrzjavascript

Building a reliable passport scanner in the browser used to be a nightmare involving WebAssembly, complex OpenCV builds, and heavy client-side ML models. Today, you can build a highly accurate, lightweight web-based passport scanner using React and a cloud OCR API in less than 100 lines of code.

In this tutorial, we'll build a React component that captures an image from the user's webcam, sends it to the Passport OCR API, and displays the parsed Machine Readable Zone (MRZ) data.

Prerequisites

  • Node.js installed
  • Basic knowledge of React (Hooks, state management)
  • A webcam (for testing)

Step 1: Project Setup

Let's scaffold a new React project using Vite (or Next.js if you prefer).

npm create vite@latest react-passport-scanner -- --template react-ts
cd react-passport-scanner
npm install

We'll use react-webcam for the camera interface. It's a lightweight wrapper around the browser's MediaDevices API.

npm install react-webcam

Step 2: The Scanner Component

Create a new file called PassportScanner.tsx. We need three main states: the camera connection, the loading state while the OCR is processing, and the result data.

import React, { useRef, useState, useCallback } from 'react';
import Webcam from 'react-webcam';
 
// Define the expected OCR response based on the Passport OCR API docs
interface OcrResult {
  documentType: string;
  documentNumber: string;
  lastName: string;
  firstName: string;
  nationality: string;
  dateOfBirth: string;
  sex: string;
  expirationDate: string;
}
 
export default function PassportScanner() {
  const webcamRef = useRef<Webcam>(null);
  const [isProcessing, setIsProcessing] = useState(false);
  const [result, setResult] = useState<OcrResult | null>(null);
  const [error, setError] = useState<string | null>(null);
 
  const capture = useCallback(async () => {
    // 1. Capture the image as a base64 string
    const imageSrc = webcamRef.current?.getScreenshot();
    
    if (!imageSrc) {
        setError("Could not capture image from camera.");
        return;
    }
 
    setIsProcessing(true);
    setError(null);
 
    try {
      // 2. Convert the base64 WebP/JPEG to a Blob for multipart/form-data
      const res = await fetch(imageSrc);
      const blob = await res.blob();
 
      // 3. Prepare the form data
      const formData = new FormData();
      formData.append('image', blob, 'passport.jpg');
 
      // 4. Send the image to the Passport OCR API
      const ocrResponse = await fetch('https://passport-ocr.com/v1/ocr', {
        method: 'POST',
        body: formData,
        // No authentication required for the first 10 requests/day!
      });
 
      const data = await ocrResponse.json();
 
      if (!ocrResponse.ok || !data.success) {
        throw new Error(data.error || "Failed to process passport");
      }
 
      setResult(data.data);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setIsProcessing(false);
    }
  }, [webcamRef]);
 
  // UI Code
  return (
    <div style={{ maxWidth: '600px', margin: '0 auto', textAlign: 'center' }}>
      <h2>Scan Passport</h2>
      
      {!result ? (
        <>
          <div style={{ borderRadius: '12px', overflow: 'hidden', marginBottom: '20px' }}>
            <Webcam
              audio={false}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{ facingMode: "environment" }} // Use the back camera on mobile
              width="100%"
            />
          </div>
          
          <button 
            onClick={capture} 
            disabled={isProcessing}
            style={{ padding: '12px 24px', fontSize: '18px', cursor: 'pointer' }}
          >
            {isProcessing ? 'Processing...' : 'Capture & Read MRZ'}
          </button>
          
          {error && <p style={{ color: 'red' }}>{error}</p>}
        </>
      ) : (
        <div style={{ textAlign: 'left', background: '#f5f5f5', padding: '20px', borderRadius: '8px' }}>
          <h3>Success!</h3>
          <p><strong>Name:</strong> {result.firstName} {result.lastName}</p>
          <p><strong>Document Number:</strong> {result.documentNumber}</p>
          <p><strong>Nationality:</strong> {result.nationality}</p>
          <p><strong>DOB:</strong> {result.dateOfBirth}</p>
          <p><strong>Expires:</strong> {result.expirationDate}</p>
          
          <button onClick={() => setResult(null)}>Scan Another</button>
        </div>
      )}
    </div>
  );
}

Step 3: Understanding the Code

Let's break down the important parts:

  1. videoConstraints={{ facingMode: "environment" }}: This tells mobile devices (phones/tablets) to use the rear-facing camera, which is much better for scanning documents than the selfie camera.
  2. Converting Base64 to Blob: react-webcam returns the screenshot as a Data URI (data:image/jpeg;base64,...). The Passport OCR API expects a multipart/form-data upload containing the raw file binaries. We use await fetch(imageSrc).blob() as a succinct and modern way to convert the base64 string into a File/Blob object.
  3. The API Call: We POST the FormData directly to https://passport-ocr.com/v1/ocr. Since the API provides a generous free tier without API keys, we don't need to put an Authorization header or route this through a proxy for initial testing. (Note: For production, you should route this request through your own backend to handle billing logs, private API keys if you upgrade, or to persist the returned data securely).

Conclusion

By offloading the heavy lifting of machine learning to the cloud, building a passport MRZ scanner in the browser becomes a trivial exercise in UI and API fetching.

This architecture allows your React app to remain lightweight, load instantly without heavy WebAssembly bundles, and guarantees high accuracy against complex MRZ formats.

Ready to test it out? Check the full API documentation to learn more about advanced configurations, or experiment with the endpoint today using [Try the Passport OCR API](/docs).

Ready to extract passport data?

Try Passport OCR free — 10 requests per day, no signup required.