DEV Community

mnotr
mnotr

Posted on • Originally published at datacheck.dev

IBAN Validation: Complete Developer Guide (2026)

If your app handles international payments, money transfers, or banking integrations, you'll need to validate IBANs. The International Bank Account Number is used across 80+ countries to identify bank accounts for cross-border transfers.

Getting IBAN validation wrong means failed transfers, returned payments, and unhappy users. This guide shows you exactly how the validation works, with code you can drop into any project.

What's Inside an IBAN?

Every IBAN follows the same structure:

DE  89  3704 0044  0532 0130 00
──  ──  ─────────  ─────────────
 │   │      │            │
 │   │      │            └── Account number
 │   │      └── Bank code (BLZ)
 │   └── Check digits (MOD-97)
 └── Country code (ISO 3166-1)
Enter fullscreen mode Exit fullscreen mode
  1. Country code (2 letters) — DE = Germany, GB = UK, FR = France
  2. Check digits (2 digits) — computed using MOD-97 to catch typos
  3. BBAN (Basic Bank Account Number) — the rest, format varies by country

The total length depends on the country. Germany is 22 characters, the UK is 22, France is 27, and Norway is just 15.

IBAN Lengths by Country

Country Code Length Example
Germany DE 22 DE89 3704 0044 0532 0130 00
United Kingdom GB 22 GB29 NWBK 6016 1331 9268 19
France FR 27 FR76 3000 6000 0112 3456 7890 189
Spain ES 24 ES91 2100 0418 4502 0005 1332
Netherlands NL 18 NL91 ABNA 0417 1643 00
Italy IT 27 IT60 X054 2811 1010 0000 0123 456
Switzerland CH 21 CH93 0076 2011 6238 5295 7
Norway NO 15 NO93 8601 1117 947
Poland PL 28 PL61 1090 1014 0000 0712 1981 2874
Belgium BE 16 BE68 5390 0754 7034

The MOD-97 Checksum Algorithm

IBAN validation uses the MOD-97 algorithm defined in ISO 7064:

  1. Move the first 4 characters to the end: DE89... becomes ...DE89
  2. Convert letters to numbers: A=10, B=11, ..., Z=35. So D=13, E=14
  3. Calculate the remainder when divided by 97
  4. If the remainder is 1, the IBAN is valid

Walk-Through: DE89370400440532013000

Step 1: Move first 4 to end
  370400440532013000DE89
  → 370400440532013000131489  (D=13, E=14)

Step 2: MOD-97
  370400440532013000131489 % 97 = 1

Result: 1 → Valid ✓
Enter fullscreen mode Exit fullscreen mode

The challenge: this is a 24+ digit number, which exceeds JavaScript's Number.MAX_SAFE_INTEGER (2^53). You need to process it in chunks.

JavaScript Implementation

function validateIBAN(input) {
  // 1. Clean and uppercase
  const iban = input.replace(/\s/g, '').toUpperCase();

  // 2. Basic format check
  if (!/^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/.test(iban)) {
    return { valid: false, reason: 'Invalid IBAN format' };
  }

  // 3. Check country-specific length
  const lengths = {
    DE: 22, GB: 22, FR: 27, ES: 24, IT: 27, NL: 18,
    BE: 16, AT: 20, CH: 21, SE: 24, NO: 15, DK: 18,
    FI: 18, PL: 28, PT: 25, IE: 22, LU: 20, CZ: 24,
  };

  const country = iban.substring(0, 2);
  if (lengths[country] && iban.length !== lengths[country]) {
    return { valid: false, reason: `${country} IBAN must be ${lengths[country]} characters` };
  }

  // 4. MOD-97 check
  // Move first 4 chars to end
  const rearranged = iban.substring(4) + iban.substring(0, 4);

  // Convert letters to numbers
  let numeric = '';
  for (const char of rearranged) {
    if (char >= 'A' && char <= 'Z') {
      numeric += (char.charCodeAt(0) - 55).toString();
    } else {
      numeric += char;
    }
  }

  // MOD-97 on large number (process in chunks)
  let remainder = 0;
  for (let i = 0; i < numeric.length; i++) {
    remainder = (remainder * 10 + parseInt(numeric[i])) % 97;
  }

  if (remainder !== 1) {
    return { valid: false, reason: 'Checksum failed (MOD-97)' };
  }

  return {
    valid: true,
    country,
    check_digits: iban.substring(2, 4),
    bban: iban.substring(4),
    formatted: iban.replace(/(.{4})/g, '$1 ').trim(),
  };
}

validateIBAN('DE89 3704 0044 0532 0130 00');
// { valid: true, country: "DE", check_digits: "89",
//   bban: "370400440532013000",
//   formatted: "DE89 3704 0044 0532 0130 00" }
Enter fullscreen mode Exit fullscreen mode

The key trick is the chunk-based MOD-97 on lines 33-36. Instead of converting the entire string to a BigInt, we process digit by digit, taking the modulus at each step. This keeps the number small and works in any JavaScript environment.

Extracting the Bank Code

The BBAN contains the bank code, but its position varies by country:

Country Bank Code Position Example
Germany (DE) Characters 5-12 (8 digits) 37040044
UK (GB) Characters 5-8 (4 letters) NWBK
France (FR) Characters 5-9 (5 digits) 30006
Netherlands (NL) Characters 5-8 (4 letters) ABNA
Spain (ES) Characters 5-8 (4 digits) 2100
function extractBankCode(iban, country) {
  const bankCodeLengths = {
    DE: 8, GB: 4, FR: 5, NL: 4, ES: 4,
    IT: 5, BE: 3, AT: 5, CH: 5, SE: 3,
  };

  const len = bankCodeLengths[country];
  if (!len) return null;

  return iban.substring(4, 4 + len);
}
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

1. Not handling spaces
IBANs are often displayed with spaces (DE89 3704 0044...) but stored without them. Always strip spaces before validation.

2. Case sensitivity
IBANs can be entered in lowercase. Always convert to uppercase before processing — the letter-to-number conversion depends on it.

3. Skipping the length check
An IBAN that passes MOD-97 but has the wrong length for its country is still invalid. A German IBAN must be exactly 22 characters — no more, no less.

4. Using parseInt on the full number
parseInt("370400440532013000131489") loses precision because it exceeds 2^53. Always use the chunk method or BigInt.

Using the DataCheck API

If you'd rather not maintain country-specific length tables and bank code extraction logic:

const res = await fetch(
  'https://datacheck.dev/api/validate?input=DE89370400440532013000&type=iban'
);
const data = await res.json();

// {
//   valid: true,
//   formatted: "DE89 3704 0044 0532 0130 00",
//   country: "DE",
//   details: {
//     country: "Germany",
//     check_digits: "89",
//     bban: "370400440532013000",
//     bank_code: "37040044"
//   }
// }
Enter fullscreen mode Exit fullscreen mode

Or with the npm package:

npm install datacheck-api
Enter fullscreen mode Exit fullscreen mode
import { validateIBAN } from "datacheck-api";
const result = await validateIBAN("DE89370400440532013000");
Enter fullscreen mode Exit fullscreen mode

Test IBANs for Development

Country Valid Test IBAN
Germany DE89 3704 0044 0532 0130 00
UK GB29 NWBK 6016 1331 9268 19
France FR76 3000 6000 0112 3456 7890 189
Spain ES91 2100 0418 4502 0005 1332
Netherlands NL91 ABNA 0417 1643 00
Switzerland CH93 0076 2011 6238 5295 7
Belgium BE68 5390 0754 7034
Norway NO93 8601 1117 947

Wrapping Up

IBAN validation comes down to three checks: format (letters + digits), country-specific length, and MOD-97 checksum. The tricky part is the big-number arithmetic, which the chunk-based approach solves cleanly.

For apps that just need to verify IBANs and extract bank codes, the 40-line implementation above works perfectly. For production use with 45+ countries and always-current formats, consider using an API like DataCheck.

Top comments (0)