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.

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-nodeis the Dynamsoft Capture Vision SDK for Node.js; the companiondynamsoft-capture-vision-for-node-modelpackage 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, andReadMRZfor already-cropped MRZ regions. - Parsed results live in
result.parsedResultItems[].parsed.ResultInfoas a per-line tree ofChildFieldswhose leaf nodes carryFieldNameandValue. The raw, unparsed MRZ lines are available inresult.textLineResultItems. - The
MRZScanner.jsontemplate bundled with the npm package omits theMRZLocalizationmodel fromCaptureVisionModelOptions, which makes recognition fail withModel file is not found(dcvErrorCode -10078) — the sample injects the missing model declarations at startup. - MRZ dates are 6-digit
YYMMDDstrings per ICAO Doc 9303; the sample normalizes them toYYYY-MM-DD, mapping aYYof 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_KEYenvironment variable, or paste it into theLICENSE_KEYconstant inmrz.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 ownjpg,png,bmp,gif,pdf, ortifffile.
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

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 bundledMRZScanner.jsondoes not declare the localization model inCaptureVisionModelOptions. Inject the missing declarations as shown in Step 2 before callingcaptureAsync().- 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-modelpackage is a required dependency for MRZ recognition — install it explicitly alongside the main package. No MRZ foundon 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 theReadMRZtemplate; supported input formats arejpg,png,bmp,gif,pdf, andtiff.
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.