This free MRZ calculator generates ICAO 9303-compliant Machine Readable Zone strings with valid check digits for passports, ID cards and visas, and previews them as a mock document image — entirely in your browser. Test image dataset mode builds a seeded set of synthetic documents with blur, motion, rotation, perspective, glare, noise and JPEG degradations, keeps the ground-truth fields for every image, and exports the whole thing as a ZIP your MRZ reader can be scored against.
Build a set of synthetic passport, ID card and visa images with known difficulty and known expected fields. The seed drives every random choice — identities, document types and degradation values — so the same seed rebuilds the same test cases. Export the ZIP and score a reader against it in the MRZ scanner benchmark.
| On | Degradation | Min | Max | Unit |
|---|
A stress image is synthesised, not photographed. The degradations model the capture problems that make an MRZ hard to read — blur, motion, rotation, perspective, uneven lighting, glare, noise, JPEG artifacts and a small final scale — but they are generated, so absolute accuracy figures are not a claim about real camera footage. What the dataset measures reliably is the relative behaviour of a reader: which conditions it survives, and where it starts to lose characters.
The MRZ is never cut. The frame crop trims the border around the
document and stops before the edge would reach the MRZ band — a partial MRZ cannot be
parsed by any reader, so it would be a guaranteed miss that measures nothing. The
value actually used is recorded as crop in the annotations, with the
request beside it as crop_requested.
Every value is synthetic. Names, numbers and dates come from fixed lists and generated dates, and the document is a mock rendered on a canvas — it is not a real travel document and must not be treated as one.
Dates in the ground truth are YYMMDD exactly as encoded in the MRZ; the
century a reader assumes is its own convention, so it is not scored. The per-image
degradation values are recorded in the annotations, so a miss can be read against
the condition that caused it.
It builds the machine-readable zone of an ICAO 9303 travel document — two or three fixed-width lines of OCR-B text — from the fields you type, and computes every check digit the layout calls for, including the composite one. The result is drawn on a mock data page at the character pitch the standard specifies. The second mode turns the same writer into a seeded image set with ground truth.
An MRZ is not free-form text. Every layout fixes how many lines there are, how many characters each line holds, and which field sits in which character position — so a string written for one layout cannot be read as another, even when the fields are identical. This generator covers the five ICAO 9303 layouts:
| Layout | Printed on | Lines × characters | Name field | Check digits |
|---|---|---|---|---|
| TD3 | Passport booklet (ID-3) | 2 × 44 | Line 1, 39 characters | Document number, birth date, expiry date, optional data, composite |
| TD2 | ID card (ID-2) | 2 × 36 | Line 1, 31 characters | Document number, birth date, expiry date, composite |
| TD1 | ID card (ID-1, credit-card size) | 3 × 30 | Line 3, 30 characters | Document number, birth date, expiry date, composite |
| MRV-A | Visa, full-page sticker | 2 × 44 | Line 1, 39 characters | Document number, birth date, expiry date — no composite |
| MRV-B | Visa, smaller sticker | 2 × 36 | Line 1, 31 characters | Document number, birth date, expiry date — no composite |
Values are padded to their field with the filler character <, and the separator
between the primary and secondary identifier is two of them — NELSON<<CALLIE.
A value longer than its field is cut, not wrapped: TD1 gives the name 30 characters, so a long
surname loses its tail. One code is not ISO 3166: ICAO prints Germany as D, and
this generator normalizes DEU to D so the field reads
D<<, as a German document does.
The record in the screenshot is a synthetic passport: NELSON, Callie, document
422016774, born 17 October 1988, expires 23 December 2027, optional data
572615374<5859. Written as TD3 it comes out as two 44-character lines:
P<USANELSON<<CALLIE<<<<<<<<<<<<<<<<<<<<<<<<<<<
4220167749USA8810171F2712239572615374<585990
The lower line is a fixed set of slices, and reading it means reading those slices rather than splitting on anything visible:
| Positions | Field | In this example |
|---|---|---|
| 1–9 | Document number | 422016774 |
| 10 | Check digit over positions 1–9 | 9 |
| 11–13 | Nationality | USA |
| 14–19 | Date of birth | 881017 |
| 20 | Check digit over positions 14–19 | 1 |
| 21 | Sex | F |
| 22–27 | Date of expiry | 271223 |
| 28 | Check digit over positions 22–27 | 9 |
| 29–42 | Optional data | 572615374<5859 |
| 43 | Check digit over positions 29–42 | 9 |
| 44 | Composite check digit | 0 |
Every check digit uses the same arithmetic. Each character is given a value — 0–9 keep their value, A is 10 through Z is 35, and the filler is 0 — then multiplied by the repeating weights 7, 3, 1 and the total taken modulo 10. For the document number:
| Character | 4 | 2 | 2 | 0 | 1 | 6 | 7 | 7 | 4 |
|---|---|---|---|---|---|---|---|---|---|
| Value | 4 | 2 | 2 | 0 | 1 | 6 | 7 | 7 | 4 |
| Weight | 7 | 3 | 1 | 7 | 3 | 1 | 7 | 3 | 1 |
| Product | 28 | 6 | 2 | 0 | 3 | 6 | 49 | 21 | 4 |
The products add to 119, and 119 modulo 10 is 9 — the character at position 10. The composite digit at the end is the same sum over a longer input: the document number with its check digit, the birth date with its check digit, the expiry date with its check digit and the optional data with its check digit, 39 characters in all, which gives 0. Nationality and sex are not in that input, which is why a wrong nationality can pass a composite check.
It proves the characters were copied without a transcription error. It does not prove the document is genuine: the algorithm is public, so a forgery carries correct digits too, and a reader that reports "MRZ check digits valid" has said nothing about security features, the chip, or whether the issuing state ever printed that number. That distinction matters when a demo like this one offers to produce a valid string from any input — the string is arithmetic, not authority.
| Capability | This tool |
|---|---|
| Layouts | TD1, TD2, TD3, MRV-A and MRV-B, with the check digits each one defines. Not a template for any specific country's document — no national field layout, no document-specific optional-data convention. |
| Character set | A–Z, 0–9 and the filler <. Input is uppercased and spaces become
fillers; accented letters are not transliterated, so MÜLLER
stays invalid instead of becoming MUELLER. |
| Document preview | A mock data page on an HTML5 canvas — 900 × 640 for a passport, 900 × 580 for an ID card or visa — with the MRZ in OCR-B, a drawn portrait, a signature and a security rosette. It is a stand-in for a document, not a reproduction of one. |
| Test image dataset | Up to 100 images from one seed, mixed or single document type, twelve degradations
with per-image ranges, ground truth for every image, ZIP plus
annotations.json in the mrz-benchmark/1.0 schema. |
| Scoring a reader | Not here — the ZIP is scored in the MRZ scanner benchmark, which reports accuracy per field and per image. |
| Reading an MRZ | Not offered. This page writes strings and images; it does not recognise anything, and it does not check that a string it wrote would be read back correctly. |
| Chip, barcode, MRZ on a real template | Not offered. The preview draws a generic data page; there is no RFID data group, no PDF417 or QR symbol, and no document-template library. |
| Privacy and network | No SDK and no license key: the page is plain JavaScript. Nothing you type is uploaded, and there is no server to upload it to. The page fetches its own CSS, fonts and demo analytics only. |
Open the page and type. There is no account, no key and no installation, because the generator needs no SDK at all.
The generator is arithmetic and a canvas — it carries no license of its own. The reader you point at its output does.
Ü is none of those — it is written through as-is. Type the ASCII form
(MUELLER) for a string that will read back.881017 rather than 1988-10-17?