A browser-based MRZ (Machine Readable Zone) reader that uses webcam capture and OCR to extract document data. Supports passports, ID cards, and travel documents. All processing happens client-side for privacy.
https://eringen.com/blog/browser-based-passport-mrz-reader-with-tesseract-js
https://eringen.com/workbench/web-mrz-reader/
https://www.npmjs.com/package/web-mrz-reader
https://www.npmjs.com/package/web-mrz-reader-react
For React projects, use the official wrapper:
npm install web-mrz-reader-reactSee web-mrz-reader-react for docs.
npm install web-mrz-reader tesseract.jsCopy the trained model and Tesseract runtime files into your project's public directory:
mkdir -p public/model public/tesseract
# MRZ-trained OCR model
cp node_modules/web-mrz-reader/public/model/mrz.traineddata.gz public/model/
# Tesseract.js worker and WASM cores
cp node_modules/web-mrz-reader/public/tesseract/* public/tesseract/<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MRZ Reader</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, -apple-system, sans-serif; background: #0a0a0a; color: #e0e0e0; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 2rem 1rem; }
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 1.5rem; }
#mrz-reader { width: 100%; max-width: 888px; }
button { margin-top: 1.25rem; padding: 0.75rem 1.5rem; font-size: 0.95rem; font-weight: 500; color: #fff; background: #1a73e8; border: none; border-radius: 6px; cursor: pointer; }
button:hover { background: #1557b0; }
button:disabled { background: #333; color: #666; cursor: not-allowed; }
#mrzOutput { margin-top: 1.25rem; font-family: monospace; font-size: 0.85rem; line-height: 1.6; white-space: pre-wrap; color: #4ade80; max-width: 888px; width: 100%; }
#output { margin-top: 0.75rem; font-family: monospace; font-size: 0.8rem; line-height: 1.5; white-space: pre-wrap; color: #94a3b8; max-width: 888px; width: 100%; }
</style>
</head>
<body>
<h1>MRZ Reader</h1>
<div id="mrz-reader"></div>
<button id="cbutton">Capture & Read MRZ</button>
<p id="mrzOutput"></p>
<p id="output"></p>
<script type="module" src="./main.js"></script>
</body>
</html>import { initMRZReader } from 'web-mrz-reader';
const mrzReader = initMRZReader({
container: '#mrz-reader',
workerPath: '/tesseract/worker.min.js',
corePath: '/tesseract/',
langPath: '/model/',
onResult: (result) => {
if (!result) return;
document.getElementById('mrzOutput').innerText = result.raw;
document.getElementById('output').innerText = JSON.stringify(result.parsed, null, 2);
},
onError: (error) => {
document.getElementById('output').innerText = error;
},
});
document.getElementById('cbutton').addEventListener('click', async () => {
const btn = document.getElementById('cbutton');
btn.disabled = true;
try {
await mrzReader.capture();
} finally {
btn.disabled = false;
}
});Using Vite:
npm install -D vite
npx viteOr any static server that supports ES modules. Open the URL, allow camera access, and click the capture button.
const mrzReader = initMRZReader({
container: '#mrz-reader', // CSS selector or HTMLElement (required)
workerPath: '/tesseract/worker.min.js', // path to tesseract worker
corePath: '/tesseract/', // path to tesseract WASM cores
langPath: '/model/', // path to mrz.traineddata.gz
onResult: (result) => {}, // called when MRZ is detected
onError: (error) => {}, // called on camera or OCR errors
});
await mrzReader.capture(); // capture frame and wait for OCR
mrzReader.reset(); // clear canvas
mrzReader.stop(); // stop camera streamFor automatic reading, enable autoScan. A cropped scan region reduces the work
per frame; place all two or three MRZ lines inside the green guide:
const reader = initMRZReader({
container: '#mrz-reader',
autoScan: true,
scanRegion: { left: 0.05, top: 0.55, width: 0.9, height: 0.35 },
onResult: ({ parsed }) => console.log(parsed),
onError: (message) => console.error(message),
});
reader.pauseScanning(); // keep the camera and warm OCR worker ready
reader.startScanning(); // resume on the next available frame
reader.reset(); // clear feedback and allow the same document to emit again
reader.stop(); // final cleanup: release camera/worker and remove reader UIscanRegion uses fractions of the visible preview (0–1); omit it to scan the
whole preview. The example processes 799 × 175 pixels instead of 888 × 500,
about 69% fewer pixels, without reducing the character scale. Portrait and
landscape cameras use the same crop shown in the preview.
Continuous scanning keeps one OCR job in flight, takes a fresh frame after it
finishes, and pauses while the tab is hidden. It emits results only when every
check digit passes and suppresses repeated identical results until reset() or
a different valid MRZ is read. Check digits detect reading errors; they do not
authenticate a document. Manual captures retain the existing validation diagnostics.
Live scanning defaults to text-only OCR for speed. Set drawBoundingBoxes: true
to request word geometry and draw boxes; manual captures still enable them by
default. The preview stays live while OCR runs. Actual throughput depends on
the device, lighting, focus, and MRZ size. Reinitializing the same container
cleans up its previous reader. After stop(), create a new instance to restart.
If you already have MRZ text and just want to parse it:
import { isMRZ, extractMRZData, parseMrz } from 'web-mrz-reader';
const text = 'P<GBRNEGUS<<JOHN<<<<<<<<<<<<<<<<<<<<<<<<<<\n1234567890GBR8001015M3001015<<<<<<<<<<<<<<00';
if (isMRZ(text)) {
const result = extractMRZData(text);
console.log(result);
}
// Or parse directly if you know the format
const parsed = parseMrz(text);
console.log(parsed);- Real-time webcam capture
- Custom-trained Tesseract model optimized for MRZ recognition
- Supports TD1 (ID cards), TD2 (travel documents), and TD3 (passports)
- Check digit validation for all formats
- Structured data extraction (name, document number, dates, etc.)
- Visual bounding box feedback on recognized text
- Fully client-side processing (no data leaves the browser)
- TypeScript - Strict mode, modular architecture
- Vite - Dev server and production bundler (handles TS natively)
- Tesseract.js v5 - JavaScript OCR engine with WebAssembly (installed via npm)
- Custom MRZ Model - Trained specifically for MRZ text recognition
- Web APIs - MediaDevices, Canvas, Blob
web-mrz-reader/
├── index.html # Main HTML page
├── src/
│ ├── index.ts # Package entry point
│ ├── main.ts # initMRZReader function
│ ├── main.demo.ts # Demo app entry point
│ ├── types.ts # Interfaces: TD1/TD2/TD3 results, validation
│ ├── checkdigit.ts # Check digit calculation and validation
│ └── parsers.ts # MRZ parsing, extraction, format detection
├── tsconfig.json # TypeScript configuration (strict mode)
├── vite.config.ts # Vite library build config
├── package.json # Dependencies and scripts
├── public/
│ ├── model/
│ │ └── mrz.traineddata.gz # Custom Tesseract model for MRZ
│ └── tesseract/ # Tesseract.js runtime files (auto-copied)
└── model_training.md # Guide for training an improved model
| Format | Document Type | Structure |
|---|---|---|
| TD1 | ID cards | 3 lines x 30 chars |
| TD2 | Travel documents | 2 lines x 36 chars |
| TD3 | Passports | 2 lines x 44 chars |
All formats: Nationality, Surname, Given Names, Document Number, Issuing Country, Date of Birth, Gender, Expiration Date, Validation (check digits)
TD1 additionally: Document Type, Optional Data 1, Optional Data 2
TD2 additionally: Document Type, Optional Data
TD3 additionally: Passport Number, Personal Number
npm install
npm run dev- Open the local URL shown by Vite
- Allow camera access when prompted
- Position MRZ area within camera view
- Fit all MRZ lines inside the green guide; reading starts automatically
- View extracted data in JSON format
npm run typechecknpm testThe tests simulate camera frames and delayed worker operations to cover live scanning, cancellation, initialization failures, duplicate results, and cropping.
npm run buildThe output in dist/ can be deployed to any static host.
- Node.js (for build tooling)
- Modern browser with WebAssembly support
- Camera access permission
- HTTPS or localhost (required for camera API)
Tesseract.js is licensed under Apache-2.0.