MRZ Parserwith auto-correction

Enter or paste MRZ data above to parse

ICAO 9303 · TD3 passports

TD3 machine-readable zone (MRZ) deep dive

TD3 is the ICAO 9303 travel document format used by most ePassports: two lines of 44 characters designed for fast, interoperable optical reading. This guide explains why TD3 exists, how it is structured, what each position means, and how check digits enforce integrity.

Scope: ICAO Doc 9303, Part 4 (TD3), MRZ character set, padding rules, field semantics, check digit math, and common parsing pitfalls.

Why TD3 exists

  • Interoperability: standard layout ensures any state can read passports from any other state.
  • Speed and robustness: fixed-width OCR-B text, constrained alphabet, and fillers reduce ambiguity.
  • Data integrity: per-field and composite check digits detect transcription/OCR errors and tampering.
  • Backups the chip: even ePassports rely on MRZ for bootstrap and verification.

Physical + character set

Form factor

  • TD3 size: 125 mm × 88 mm identity page; MRZ sits in the Visual Inspection Zone (VIZ).
  • Two MRZ lines, 44 characters each, OCR-B font.
  • Lines are fixed-width; every position has meaning.

Character rules

  • Allowed: A–Z, 0–9, and filler <.
  • Spaces become <; diacritics removed (Ü → U, ß → SS).
  • Padding: unused characters become < to fill the line.

Layout map

Line 1 (44 chars)
Doc codeIssuerName block
Columns: 1–2 | 3–5 | 6–44
Line 2 (44 chars)
Doc # + checkNat.DOB + checkSexExpiry + checkOptional + checkComposite
Cols: 1–9 | 10 | 11–13 | 14–19 | 20 | 21 | 22–27 | 28 | 29–42 | 43 | 44

Field-by-field

Line 1, cols 1–2
Document code

Type of document. TD3 passports use P (e.g., P<).

Non-passport codes exist (e.g., V for visas) but TD3 expects P.

Line 1, cols 3–5
Issuing state/organization

Three-letter ICAO code of issuing state or authority.

Line 1, cols 6–44
Name

Primary surname(s) followed by << then given/secondary names separated by <.

Only A–Z and < allowed; diacritics stripped; pad with < to fill.

Line 2, cols 1–9
Document number

Passport number, alphanumeric; padded with < if short.

Col 10 is its check digit.

Line 2, cols 11–13
Nationality

Three-letter ICAO country code of holder nationality.

Line 2, cols 14–19
Date of birth

YYMMDD with implied century; col 20 is DOB check digit.

Line 2, col 21
Sex

M, F, or < (unspecified).

Line 2, cols 22–27
Date of expiry

YYMMDD expiry; col 28 is expiry check digit.

Line 2, cols 29–42
Optional data

Issuer-defined personal number / optional data; col 43 is its check digit.

Often used for national ID or internal control numbers.

Line 2, col 44
Composite check digit

Check digit over concatenation: document number + digit, DOB + digit, expiry + digit, optional data + digit.

Validates cross-field integrity.

Check digit algorithm

Basics

  • Weights repeat 7, 3, 1 over each character.
  • Char → value: 0–9 = 0–9; A = 10 ... Z = 35; < = 0.
  • Digit = (Σ(value × weight)) mod 10.

Worked example (document number)

Input: L00000000

Values: L=21, 0=0 ...

Weights: 7,3,1,7,3,1,7,3,1

Sum: (21×7) + (0×3) + ... = 147

147 mod 10 = 7 → check digit

Composite check digit

Concatenate: document number + its digit, DOB + digit, expiry + digit, optional data + digit. Apply the same weighting and modulus. This binds the MRZ into one integrity value.

Examples

Reference TD3 sample (valid)
Valid
P<UTODOE<<JANE<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<L000000007UTO9001011F3001019AA0000000<<<<<02

All check digits valid; uses filler < to pad names and optional data.

  • Doc code P< at cols 1–2; issuing state UTO at 3–5.
  • Name uses << between surname and given names; < as intra-name separator.
  • Document number L00000000 at cols 1–9 with check digit 7 at col 10.
  • DOB 900101 with check digit 1 at col 20; expiry 300101 with digit 9 at col 28.
  • Optional data AA0000000<<<<< with digit 0 at col 43; composite digit 2 at col 44.
Check digit failure example
Invalid
P<GBRTESTER<<ALICIA<<<<<<<<<<<<<<<<<<<<<<<<<C987654320GBR7505205F2905202CC0000000<<<<<05

Composite digit intentionally wrong (should be 4). Prior field digits still valid.

  • Passport number C98765432 with digit 0 passes.
  • DOB 750520 with digit 5 passes; expiry 290520 with digit 2 passes.
  • Optional data CC0000000<<<<< with digit 0 passes.
  • Composite digit 5 fails—signals tamper or OCR error even though sub-digits passed.

Parsing and validation tips

  • Normalize input to uppercase; replace spaces with <; trim to 44 chars per line.
  • Validate per-field digits first, then the composite digit; report which fields failed.
  • Names: split on << for primary vs secondary; remaining < are intra-name separators.
  • Unknown sex may be < — do not reject solely for that.
  • Century inference: DOB and expiry are YYMMDD; use issuance rules or expiry cutoff heuristics.
  • OCR noise: watch for 0/O and 1/I; check digits help disambiguate.

References

  • ICAO Doc 9303, Part 4 — Machine Readable Passports (TD3).
  • ISO/IEC 7501-1 — Machine readable passports.
  • For parser libraries, ensure they implement 7-3-1 weighting and composite validation.

mrz-fast

MRZ Fast

A high-performance, zero-dependency TypeScript library for parsing and generating passport MRZ codes (TD3 format). Supports ICAO 9303 TD3—the standard MRZ used in international passports.

MRZ format, explained
  • Two lines × 44 chars (TD3)
  • Full check-digit validation
  • Optional OCR/AI error correction
  • Parse + generate in pure TS

Features

  • Zero dependencies, pure TypeScript.
  • Passport-only: ICAO 9303 TD3 focus.
  • Fast: >200k parses/sec with optimized tables.
  • Validation: per-field + composite check digits with error reporting.
  • Error correction: common OCR/AI confusions (O→0, I→1, S→5, etc.).
  • Parse & create: turn MRZ text into fields, or build MRZ from structured data.

Installation

npm install mrz-fast

Library ships typed ESM/CJS; no runtime deps.

Quick start · parse

import { parseMRZ } from 'mrz-fast';

const mrz: [string, string] = [
  'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<',
  'L898902C36UTO7408122F1204159ZE184226B<<<<<10'
];

const result = parseMRZ(mrz);
console.log(result.valid); // true
console.log(result.fields.firstName); // "ANNA MARIA"

Quick start · create

import { createMRZ } from 'mrz-fast';

const data = {
  documentCode: 'P',
  issuingState: 'UTO',
  lastName: 'ERIKSSON',
  firstName: 'ANNA MARIA',
  documentNumber: 'L898902C3',
  nationality: 'UTO',
  birthDate: { year: 1974, month: 8, day: 12 },
  sex: 'female',
  expirationDate: { year: 2012, month: 4, day: 15 },
  personalNumber: 'ZE184226B', // optional
};

const [line1, line2] = createMRZ(data);

Error correction for OCR/AI

Handles O↔0, I↔1↔l, S↔5, B↔8, Z↔2, G↔6; pads short lines, trims long ones, fixes leading fillers, and searches likely combinations.

import { parseMRZ } from 'mrz-fast';

const messy: [string, string] = [
  'P<UTOERIKSSON<<ANNA<MARIA', // too short, padded
  'L8989O2C36UTO74O8I22FI2O4I59ZEI84226B<<<<<IO'
];

const result = parseMRZ(messy, { errorCorrection: true });
console.log(result.valid, result.corrected); // true true
console.log(result.correctionMetrics);
Smart search can test ~2M combinations; still clears >100k parses/sec with correction enabled.

API snapshot

  • parseMRZ(mrz, options?) → fields, lines (corrected if enabled), details, optional correction metrics.
  • createMRZ(data)[line1, line2] with check digits.
  • Options: errorCorrection?: boolean (default false).
  • Returns typed ParseResult with per-field errors and check digits.

Performance

  • Clean parsing: ~0.005 ms/parse (>200k ops/sec).
  • Error correction: ~0.006–0.009 ms/parse (>100k ops/sec).
  • Batch: 156k+ MRZs/sec.
  • Optimizations: precomputed tables, smart ordering, minimal allocations.

TD3 format reminder

  • Line1: doc code (2), issuer (3), names (39).
  • Line2: doc # (9)+check, nationality (3), DOB (6)+check, sex (1), expiry (6)+check, optional (14)+check, composite.
  • Check digits: ICAO 9303 weights [7,3,1], modulus 10.