DEV Community

Jonas Hämmerle
Jonas Hämmerle

Posted on

IBAN validation guide for developers

IBAN validation trips people up because it looks like it should be a length check plus a regex, and it's neither. Here's the actual structure, in the order you should check it.

1. Length and country format. Every IBAN starts with a 2-letter ISO country code and 2 check digits, followed by a country-specific BBAN whose length is fixed per country — Germany is always 22 characters, the Netherlands 18, Malta 31. A string can be the right shape (letters-then-digits) and still be the wrong length for the country it claims to be from.

2. The mod-97 checksum (ISO 7064). This is the part people get wrong or skip:

function isValidIBAN(iban) {
  const clean = iban.replace(/\s+/g, "").toUpperCase();
  if (!/^[A-Z]{2}\d{2}[A-Z0-9]+$/.test(clean)) return false;

  const rearranged = clean.slice(4) + clean.slice(0, 4);
  const numeric = rearranged.replace(/[A-Z]/g, c => (c.charCodeAt(0) - 55).toString());

  // IBANs convert to numbers far too large for JS's Number type (30+ digits),
  // so mod 97 has to be computed in chunks rather than in one BigInt/Number op.
  let remainder = numeric;
  while (remainder.length > 2) {
    const chunk = remainder.slice(0, 9);
    remainder = (parseInt(chunk, 10) % 97) + remainder.slice(chunk.length);
  }
  return parseInt(remainder, 10) % 97 === 1;
}
Enter fullscreen mode Exit fullscreen mode

Move the first 4 characters to the end, convert every letter to its numeric value (A=10 ... Z=35), and the resulting number mod 97 must equal 1. The chunked-modulo loop is the detail that trips up most from-scratch implementations — you can't just do BigInt(numeric) % 97n in older runtimes without a BigInt polyfill, and plain Number silently loses precision past 2^53.

3. What this does not tell you. A passing checksum means the IBAN is well-formed — not that the account exists, is open, or belongs to who the payer thinks it does. Confirming that needs a live lookup against the bank (or a service like the account-name-matching checks banks now run for fraud prevention), which is a separate, heavier operation than format validation.

For most apps — checkout forms, payout setup, onboarding — steps 1 and 2 are exactly the right amount of validation: fast, no external calls, no third-party uptime dependency. I wrapped both (plus the per-country BBAN structure check) into an endpoint on Validate if you'd rather not own the mod-97 edge cases yourself.

Top comments (0)