Skip to content

About

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.

Topics

Resources

Stars

10 stars

Watchers

0 watching

Forks

Repository files navigation

MRZ Reader

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.

Doc - Story

https://eringen.com/blog/browser-based-passport-mrz-reader-with-tesseract-js

Try it

https://eringen.com/workbench/web-mrz-reader/

npm

https://www.npmjs.com/package/web-mrz-reader

React Wrapper

https://www.npmjs.com/package/web-mrz-reader-react

For React projects, use the official wrapper:

npm install web-mrz-reader-react

See web-mrz-reader-react for docs.

NPM User Guide

1. Install

npm install web-mrz-reader tesseract.js

2. Copy Static Assets

Copy 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/

3. Add HTML

<!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 &amp; Read MRZ</button>
  <p id="mrzOutput"></p>
  <p id="output"></p>
  <script type="module" src="./main.js"></script>
</body>
</html>

4. Add JavaScript

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;
  }
});

5. Serve with a dev server

Using Vite:

npm install -D vite
npx vite

Or any static server that supports ES modules. Open the URL, allow camera access, and click the capture button.

API Reference

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 stream

Continuous scanning

For 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 UI

scanRegion 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.

Standalone Parsing (no camera)

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);

Features

  • 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)

Tech Stack

  • 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

Project Structure

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

Supported Formats

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

Extracted Data Fields

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

Local Development

npm install
npm run dev
  1. Open the local URL shown by Vite
  2. Allow camera access when prompted
  3. Position MRZ area within camera view
  4. Fit all MRZ lines inside the green guide; reading starts automatically
  5. View extracted data in JSON format

Type Check

npm run typecheck

Regression tests

npm test

The tests simulate camera frames and delayed worker operations to cover live scanning, cancellation, initialization failures, duplicate results, and cropping.

Production Build

npm run build

The output in dist/ can be deployed to any static host.

Requirements

  • Node.js (for build tooling)
  • Modern browser with WebAssembly support
  • Camera access permission
  • HTTPS or localhost (required for camera API)

License

Tesseract.js is licensed under Apache-2.0.

About

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.

Topics

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages