How to Recognize MRZ from Passport and ID Card with Node.js

You can recognize the MRZ (Machine Readable Zone) on passports, ID cards, and visas in Node.js with the official dynamsoft-capture-vision-for-node package: a single CaptureVisionRouter.captureAsync(imagePath, 'ReadPassportAndId') call returns both the parsed document fields (surname, given names, document number, dates of birth and expiry) and the raw MRZ lines. The SDK ships precompiled binaries for Windows, Linux, and macOS, so — unlike the older C++ addon approach — there is no node-gyp build step and no OpenCV dependency.

Node.js MRZ recognition with Dynamsoft Capture Vision

What you’ll build: A command-line MRZ scanner for Node.js. npm install followed by node mrz.js <image> prints the document type, issuing state, nationality, holder name, document number, sex, date of birth, and expiry date parsed from the MRZ, plus the raw MRZ lines for verification. Five preset templates cover TD3 passports, TD1/TD2 ID cards, and MRV-A/MRV-B visas, and the CLI accepts jpg, png, bmp, gif, pdf, and tiff input — including multiple files in one run.

Key Takeaways

  • dynamsoft-capture-vision-for-node is the Dynamsoft Capture Vision SDK for Node.js; the companion dynamsoft-capture-vision-for-node-model package carries the AI models (MRZLocalization.data, MRZCharRecognition.data) and is a required dependency for MRZ recognition.
  • The recognition flow is four calls: LicenseManager.initLicense(key)CaptureVisionRouter.initSettings(...) to load the MRZ templates → CaptureVisionRouter.captureAsync(file, templateName)CaptureVisionRouter.terminateIdleWorkers() so the SDK worker pool lets the process exit.
  • Five MRZ templates ship with the SDK: ReadPassportAndId (the default, covering passports and ID cards), ReadPassport, ReadId, ReadVisa, and ReadMRZ for already-cropped MRZ regions.
  • Parsed results live in result.parsedResultItems[].parsed.ResultInfo as a per-line tree of ChildFields whose leaf nodes carry FieldName and Value. The raw, unparsed MRZ lines are available in result.textLineResultItems.
  • The MRZScanner.json template bundled with the npm package omits the MRZLocalization model from CaptureVisionModelOptions, which makes recognition fail with Model file is not found (dcvErrorCode -10078) — the sample injects the missing model declarations at startup.
  • MRZ dates are 6-digit YYMMDD strings per ICAO Doc 9303; the sample normalizes them to YYYY-MM-DD, mapping a YY of 60 or above to 19xx.

Common Developer Questions

How do you recognize MRZ from a passport image in Node.js?

Install dynamsoft-capture-vision-for-node and dynamsoft-capture-vision-for-node-model, call LicenseManager.initLicense() with a Dynamsoft Capture Vision license, then call CaptureVisionRouter.captureAsync(imagePath, 'ReadPassportAndId'). The resolved result’s parsedResultItems contain the structured passport fields and textLineResultItems the raw MRZ lines.

Does the Dynamsoft Node.js MRZ SDK require compiling C++ addons or OpenCV?

No. dynamsoft-capture-vision-for-node ships precompiled N-API binaries for Windows (x86/x64), Linux (x64/arm64), and macOS (x64/arm64), and MRZ localization and OCR run inside the SDK. Neither node-gyp nor OpenCV is needed — npm install is the entire setup.

Which MRZ document types can the Node.js scanner read?

The bundled templates cover ICAO Doc 9303 TD3 passports (ReadPassport), TD1 and TD2 ID cards (ReadId), MRV-A and MRV-B visas (ReadVisa), and pre-cropped MRZ regions (ReadMRZ). The default ReadPassportAndId template handles passports and ID cards without choosing a template.

Why do I get “MRZLocalization: Model file is not found” (dcvErrorCode -10078)?

The MRZScanner.json template that ships with the npm package does not declare the MRZLocalization model in CaptureVisionModelOptions, so the neural-network localization stage cannot find its model file. Load the template JSON, push { Name: 'MRZLocalization', MaxModelInstances: 4 } into CaptureVisionModelOptions (the sample also declares MRZCharRecognition and MRZTextLineRecognition when missing), and pass the patched object to CaptureVisionRouter.initSettings().

How do I get structured passport fields instead of raw MRZ text?

captureAsync() runs a code-parser stage for MRZ: each entry in result.parsedResultItems exposes parsed.ResultInfo, a per-line tree of ChildFields whose leaves carry FieldName/Value pairs such as primaryIdentifier, documentNumber, dateOfBirth, and dateOfExpiry. Flatten the tree to get a plain field map; date fields are container nodes whose Value is the raw 6-digit ICAO date.

Node.js MRZ Scanner Demo Video

Prerequisites

  • Node.js: Install the current LTS release from the Node.js website.
  • Dynamsoft Capture Vision license: Get a 30-day free trial license and set it as the DYNAMSOFT_LICENSE_KEY environment variable, or paste it into the LICENSE_KEY constant in mrz.js.
  • A passport, ID card, or visa image: The sample repository ships test images under images/ (a TD3 passport and TD1/TD2 ID cards), or use your own jpg, png, bmp, gif, pdf, or tiff file.

Step 1: Install the Capture Vision Packages

Create a project folder and install the two Dynamsoft packages:

npm init -y
npm install dynamsoft-capture-vision-for-node dynamsoft-capture-vision-for-node-model
Package Purpose
dynamsoft-capture-vision-for-node The Capture Vision SDK: recognition engine, preset templates, and MRZ parser resources
dynamsoft-capture-vision-for-node-model The AI models (MRZLocalization.data, MRZCharRecognition.data, …) required by the MRZ templates

No native build step is involved: the packages ship precompiled binaries for Windows (x86/x64), Linux (x64/arm64), and macOS (x64/arm64) and load them at runtime through N-API.

Step 2: Initialize the License and MRZ Templates

Import the SDK entry points and activate the license:

const {
  LicenseManager,
  CaptureVisionRouter,
} = require('dynamsoft-capture-vision-for-node');

LicenseManager.initLicense(
  process.env.DYNAMSOFT_LICENSE_KEY || 'LICENSE-KEY'
);

Next, load the MRZ templates. The MRZScanner.json template bundled with the package omits the MRZLocalization model from CaptureVisionModelOptions, which makes the neural-network localization stage fail with Model file is not found (dcvErrorCode -10078). Read the template JSON, inject the missing model declarations, and hand the patched settings to the router:

const fs = require('fs');
const path = require('path');

const PACKAGE_ROOT = path.dirname(
  path.dirname(require.resolve('dynamsoft-capture-vision-for-node'))
);

function loadMrzSettings() {
  const templatePath = path.join(PACKAGE_ROOT, 'Templates', 'MRZScanner.json');
  const template = JSON.parse(fs.readFileSync(templatePath, 'utf-8'));

  const models = (template.CaptureVisionModelOptions =
    template.CaptureVisionModelOptions || []);
  const declared = new Set(models.map((m) => m.Name));
  for (const name of ['MRZLocalization', 'MRZCharRecognition', 'MRZTextLineRecognition']) {
    if (!declared.has(name)) {
      models.push({
        Name: name,
        MaxModelInstances: name === 'MRZTextLineRecognition' ? 1 : 4,
      });
    }
  }

  return template;
}

CaptureVisionRouter.initSettings(loadMrzSettings());

Step 3: Recognize MRZ with captureAsync

Call captureAsync() with an image path and one of the MRZ templates. parsedResultItems holds the structured fields and textLineResultItems the raw MRZ lines:

const result = await CaptureVisionRouter.captureAsync(file, 'ReadPassportAndId');

if (result.errorCode !== 0) {
  console.error(`Error: ${result.errorCode} - ${result.errorString}`);
} else if (result.parsedResultItems.length === 0) {
  console.log('No MRZ found.');
} else {
  for (const item of result.parsedResultItems) {
    console.log(formatParsedMrz(item));
  }

  const rawLines = result.textLineResultItems.map((item) => item.text || '');
  console.log('Raw MRZ lines:');
  for (const line of rawLines) console.log(`  ${line}`);
}

// Release the SDK worker pool so the Node.js process can exit.
await CaptureVisionRouter.terminateIdleWorkers();

Choose the template that matches your document:

Template Use case
ReadPassportAndId Default — TD3 passports and TD1/TD2 ID cards
ReadPassport TD3 passports only
ReadId TD1/TD2 ID cards only
ReadVisa MRV-A / MRV-B visas
ReadMRZ Already-cropped MRZ regions

Step 4: Extract Structured Passport Fields

Each parsed item’s parsed.ResultInfo is a per-line tree of ChildFields. Leaf nodes carry a FieldName and a Value; date fields such as dateOfBirth are container nodes whose Value is the raw YYMMDD string with birthYear/birthMonth/birthDay children. Flatten the tree into a plain field map:

function flattenChildren(node, out) {
  if (!node || typeof node !== 'object' || Array.isArray(node)) return;

  // Leaf field: a FieldName together with a Value.
  if (node.FieldName && node.Value !== undefined && node.Value !== null) {
    out.push({ name: node.FieldName, value: node.Value });
  }

  if (Array.isArray(node.ChildFields)) {
    for (const group of node.ChildFields) {
      if (Array.isArray(group)) {
        for (const child of group) flattenChildren(child, out);
      } else {
        flattenChildren(group, out);
      }
    }
  }
}

Useful field names include documentCode, issuingState, nationality, primaryIdentifier (surname), secondaryIdentifier (given names), documentNumber, sex, personalNumber, dateOfBirth, and dateOfExpiry. Normalize the 6-digit ICAO dates for display — per ICAO Doc 9303, a YY of 60 or above belongs to the 1900s:

// Normalize a raw "YYMMDD" ICAO date to "YYYY-MM-DD".
function formatIcaoDate(raw) {
  if (typeof raw !== 'string' || !/^\d{6}$/.test(raw)) return raw;
  const yy = parseInt(raw.slice(0, 2), 10);
  const year = yy >= 60 ? `19${raw.slice(0, 2)}` : `20${raw.slice(0, 2)}`;
  return `${year}-${raw.slice(2, 4)}-${raw.slice(4, 6)}`;
}

Step 5: Run the MRZ Scanner

Point the CLI at a passport image:

node mrz.js ..\..\images\1.png

# Multiple files in one run
node mrz.js passport.png id-card.jpg visa.jpg

# A specific template, or the full parsed result as JSON
node mrz.js ..\..\images\1.png --template ReadPassport
node mrz.js ..\..\images\1.png --json

The TD3 passport sample prints:

Dynamsoft Capture Vision (Node) - MRZ command-line scanner
SDK version  : js:3.2.50.20251229;cpp:3.0.10.3895
Template     : ReadPassportAndId

File: ..\..\images\1.png
Code type   : MRTD_TD3_PASSPORT
Issuing st. : UTO
Nationality : UTO
Doc code    : P
Surname     : ERIKSSON
Given names : ANNA MARIA
Sex         : female
Personal #  : ZE184226B
DOB         : 1974-08-12
Expiry      : 2012-04-15

Raw MRZ lines:
  P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<
  L898902C36UTO7408122F1204159ZE184226B<<<<<10

Node.js MRZ scanner terminal output

Running the bundled ID-card samples through the same default template returns MRTD_TD2_ID (two-line MRZ) and MRTD_TD1_ID (three-line MRZ) with their parsed fields — no template change is required for the common document types.

Common Issues & Edge Cases

  • MRZLocalization: Model file is not found (dcvErrorCode -10078): The bundled MRZScanner.json does not declare the localization model in CaptureVisionModelOptions. Inject the missing declarations as shown in Step 2 before calling captureAsync().
  • The Node.js process never exits: The Capture Vision worker pool keeps the event loop alive. Always call await CaptureVisionRouter.terminateIdleWorkers() when recognition is done.
  • Missing model files at runtime: The dynamsoft-capture-vision-for-node-model package is a required dependency for MRZ recognition — install it explicitly alongside the main package.
  • No MRZ found on a real document photo: Use a front-on, well-lit, upright, high-resolution image of the document page. If you supply an already-cropped MRZ region, switch to the ReadMRZ template; supported input formats are jpg, png, bmp, gif, pdf, and tiff.

Conclusion

The official dynamsoft-capture-vision-for-node package turns Node.js MRZ recognition into a dependency install and a handful of API calls — no C++ addon, no node-gyp, and no OpenCV. CaptureVisionRouter.captureAsync() localizes the MRZ, recognizes the characters, and parses the result into typed document fields for passports, ID cards, and visas, while the raw lines remain available for checksum-level verification.

Source Code

Get the complete sample project source code on GitHub