DEV Community

Matt Rybin
Matt Rybin

Posted on

I Built a Polish ID Number Generator — Here's the Math

I built a free tool that generates valid Polish ID card numbers for software testing. Each number passes the same ICAO 9303 checksum validation used in real passport machine-readable zones worldwide.

Why This Exists

Polish ID card numbers (dowód osobisty) aren't random strings. They have a strict format — three letters, six digits — and the last digit is a checksum. If you're building anything that touches Polish identity data (banks, insurance, government forms, KYC flows), random strings won't pass validation. And you can't use real numbers in test environments because GDPR exists.

The Format

Every Polish ID number looks like this: ABC123456

  • ABC — the series (three uppercase letters)
  • 12345 — serial digits
  • 6 — check digit

Not all 26 letters show up in the series. O and Q are excluded to avoid confusion with 0. So the valid alphabet is 24 letters.

The Interesting Part: ICAO 9303 Checksums

The check digit uses the same algorithm found in passport MRZs globally. It's simple and kind of elegant:

  1. Convert each character to a number. Digits stay as-is. Letters map to A=10, B=11, ... Z=35.
  2. Multiply each value by a repeating weight pattern: 7, 3, 1, 7, 3, 1, 7, 3, 1...
  3. Sum everything up.
  4. The check digit is sum mod 10.

In JavaScript, the core logic looks roughly like this:

const WEIGHTS = [7, 3, 1];

function charValue(ch) {
  const code = ch.charCodeAt(0);
  // digits: face value, letters: A=10..Z=35
  return code >= 48 && code <= 57
    ? code - 48
    : code - 55;
}

function checkDigit(chars) {
  const sum = chars.reduce(
    (acc, ch, i) => acc + charValue(ch) * WEIGHTS[i % 3],
    0
  );
  return sum % 10;
}
Enter fullscreen mode Exit fullscreen mode

The generator picks three random letters from the valid set, generates five random digits, concatenates them, runs the checksum over all eight characters, and appends the result as the ninth character. Every output is structurally valid.

Deduplication

One small detail I liked solving: the tool keeps a history set of the last 1,000 generated numbers and rerolls if it hits a duplicate. Overkill for a 24×10⁵ keyspace? Probably. But it means you can generate batches of 100 without worrying about collisions in your test fixtures.

while (attempts < 100) {
  const candidate = generateIdNumber();
  if (!idHistorySet.has(candidate)) {
    idNum = candidate;
    break;
  }
  attempts++;
}
Enter fullscreen mode Exit fullscreen mode

The Stack

The tool runs inside a Phoenix LiveView app. The generation itself is pure client-side JavaScript — no server round-trip needed. The Elixir/Phoenix side handles rendering, i18n (the tool works in both English and Polish), and the colocated hook pattern that wires up the JS behavior to the LiveView DOM.

Everything runs in the browser. No ID numbers are sent to or stored on any server.

Try It

The tool also supports output formatting (raw ABC123456 vs. spaced ABC 123456) and batch generation of 1, 5, 10, or 100 numbers at a time.

Other Generators

Poland.gg has the same kind of generators for PESEL numbers, NIP tax IDs, and mikrorachunek tax payment accounts — if you're building test data for Polish systems, you probably need more than one of these.

What's Next

I'm considering adding a companion validator tool that breaks down an existing ID number and shows the checksum math step by step. Could be useful for debugging validation logic. Open to suggestions.

Top comments (0)