Skip to content

IBAN Validator — JavaScript source

Validate International Bank Account Numbers (IBAN) with the mod-97 checksum, verify the country-specific length, and format the result. 100% client-side, no network.

This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

/**
 * Pure IBAN validation logic - ISO 13616 mod-97 checksum.
 *
 * Language: JavaScript (ES module)
 * CosmoDev polyglot showcase port of the `iban-validator` tool.
 * Ported from src/lib/iban.ts - display source, part of CosmoDev's
 * polyglot tool pages.
 *
 * Normalizes the input (uppercase, strip spaces/dashes), checks the
 * structural regex (country code + 2 check digits + 1-30 BBAN chars),
 * verifies the per-country length, then runs the ISO 13616 mod-97
 * checksum: move the first 4 chars to the end, map A=10..Z=35, and
 * confirm the resulting integer is congruent to 1 mod 97. BigInt (built
 * into the language) keeps the arithmetic exact for any length. Never
 * throws - always returns an info object.
 */

// Per-country IBAN lengths (ISO 13616) - a representative subset.
export const IBAN_LENGTHS = {
  AL: 28, AD: 24, AT: 20, AZ: 28, BH: 22, BY: 28, BE: 16, BA: 20, BR: 29,
  BG: 22, CR: 22, HR: 21, CY: 28, CZ: 24, DK: 18, DO: 28, EE: 20, FO: 18,
  FI: 18, FR: 27, GE: 22, DE: 22, GI: 23, GR: 27, GL: 18, GT: 28, HU: 28,
  IS: 26, IE: 22, IL: 23, IT: 27, JO: 30, KZ: 20, XK: 20, KW: 30, LV: 21,
  LB: 28, LI: 21, LT: 20, LU: 20, MK: 19, MT: 31, MR: 27, MU: 30, MC: 27,
  MD: 24, ME: 22, NL: 18, NO: 15, PK: 24, PS: 29, PL: 28, PT: 25, QA: 29,
  RO: 24, LC: 32, SM: 27, ST: 25, SA: 24, RS: 22, SC: 31, SK: 24, SI: 19,
  SG: 19, ES: 24, SE: 24, CH: 21, TL: 23, TN: 24, TR: 26, UA: 29, AE: 23,
  GB: 22, VG: 24,
};

/** mod-97 checksum over a CLEANED iban (no spaces, uppercase). */
export function mod97Check(cleaned) {
  // Move the first 4 chars (country + check) to the end.
  const rearranged = cleaned.slice(4) + cleaned.slice(0, 4);
  let numeric = '';
  for (const ch of rearranged) {
    const code = ch.charCodeAt(0);
    if (code >= 48 && code <= 57) {
      numeric += ch;
    } else if (code >= 65 && code <= 90) {
      numeric += String(code - 55); // A=10 .. Z=35
    } else {
      return false; // invalid character
    }
  }
  // Fold mod 97 one decimal digit at a time (BigInt mirrors the TS lib).
  let rem = 0n;
  for (const ch of numeric) {
    rem = (rem * 10n + BigInt(ch.charCodeAt(0) - 48)) % 97n;
  }
  return rem === 1n;
}

/** Validate an IBAN. Always returns an info object; never throws. */
export function validateIban(input) {
  const cleaned = String(input || '').toUpperCase().replace(/[\s-]/g, '');
  const cc = /^[A-Z]{2}/.test(cleaned) ? cleaned.slice(0, 2) : null;

  const info = {
    input: input ?? '',
    cleaned,
    countryCode: cc,
    valid: false,
    checksumOk: false,
    lengthOk: false,
    expectedLength: cc ? IBAN_LENGTHS[cc] ?? null : null,
    formatted: cleaned.replace(/(.{4})(?=.)/g, '$1 ').trim(),
    error: null,
  };

  if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/.test(cleaned)) {
    return { ...info, error: 'Invalid IBAN format.' };
  }

  const lengthOk = info.expectedLength === null ? true : cleaned.length === info.expectedLength;
  const checksumOk = mod97Check(cleaned);

  if (!lengthOk) {
    return { ...info, lengthOk: false, checksumOk, valid: false, error: `Length should be ${info.expectedLength} for ${cc}.` };
  }
  if (!checksumOk) {
    return { ...info, lengthOk: true, checksumOk: false, valid: false, error: 'Checksum failed.' };
  }
  return { ...info, lengthOk: true, checksumOk: true, valid: true, error: null };
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →