IBAN Validator — TypeScript 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 TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Pure IBAN validation logic - no React, no DOM, deterministic.
// Normalizes (uppercase, strip spaces/dashes), checks structure, validates the
// per-country length, and runs the ISO 13616 mod-97 checksum via BigInt.
// Never throws.
export interface IbanInfo {
input: string;
cleaned: string; // uppercase, spaces/dashes removed
countryCode: string | null; // 2 uppercase letters
valid: boolean; // structure + checksum + length all ok
checksumOk: boolean; // mod-97 === 1
lengthOk: boolean; // length matches country table
expectedLength: number | null;
formatted: string; // groups of 4
error: string | null; // human reason when !valid
}
// Per-country IBAN lengths (ISO 13616) - a representative subset.
export const IBAN_LENGTHS: Record<string, number> = {
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: string): boolean {
// 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
}
}
// BigInt handles arbitrarily long IBANs.
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 IbanInfo; never throws. */
export function validateIban(input: string): IbanInfo {
const cleaned = (input || '').toUpperCase().replace(/[\s-]/g, '');
const cc = /^[A-Z]{2}/.test(cleaned) ? cleaned.slice(0, 2) : null;
const base: IbanInfo = {
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 { ...base, error: 'Invalid IBAN format.' };
}
const lengthOk = base.expectedLength === null ? true : cleaned.length === base.expectedLength;
const checksumOk = mod97Check(cleaned);
if (!lengthOk) {
return { ...base, lengthOk: false, checksumOk, valid: false, error: `Length should be ${base.expectedLength} for ${cc}.` };
}
if (!checksumOk) {
return { ...base, lengthOk: true, checksumOk: false, valid: false, error: 'Checksum failed.' };
}
return { ...base, 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 →