How to Build a Web-Based Passport Scanner with React and Passport OCR
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 installWe'll use react-webcam for the camera interface. It's a lightweight wrapper around the browser's MediaDevices API.
npm install react-webcamStep 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:
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.- Converting Base64 to Blob:
react-webcamreturns the screenshot as a Data URI (data:image/jpeg;base64,...). The Passport OCR API expects amultipart/form-dataupload containing the raw file binaries. We useawait fetch(imageSrc).blob()as a succinct and modern way to convert the base64 string into a File/Blob object. - The API Call: We
POSTtheFormDatadirectly tohttps://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.