ibanchecker.cash
Developer ResourcesJune 3, 2026 · 7 min read

IBAN Regex Patterns That Actually Work (And Why Simple Regex Isn't Enough)

Country-specific regex patterns for DE, GB, FR, NL plus a generic IBAN regex — and why regex is only step 1 of validation, not the full solution.

Share

IBAN regex patterns are useful as a first-pass filter — they can quickly reject strings that are obviously not IBANs. But regex alone cannot validate an IBAN. The ISO 13616 check digit algorithm (MOD-97) requires integer arithmetic that no regular expression can perform. This guide covers the patterns that actually work, where they fall short, and how to combine them correctly with a check digit calculation.

Why a Simple Regex Is Never Enough

Consider this string: DE89370400440532013001. It is 22 characters, starts with a valid country code, and contains only alphanumeric characters. Every regex you could write would accept it. But it is invalid — the last digit has been changed from 0 to 1, which breaks the MOD-97 check.

The check digits at positions 3–4 of every IBAN encode a mathematical commitment to the rest of the string. Verifying them requires computing int(rearrangedString) % 97 and confirming the result is 1. No regex engine evaluates arithmetic — this is a fundamental limitation of the formalism, not a practical workaround.

Regex is step 1 of IBAN validation, not the full solution. Use it to reject malformed strings early and cheaply. Then compute MOD-97 on the survivors.

Generic IBAN Regex (All Countries)

A generic pattern that matches any structurally plausible IBAN across all 84 supported countries:

// JavaScript
const IBAN_GENERIC = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/;

# Python
import re
IBAN_GENERIC = re.compile(r'^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$')

This pattern requires:

  • Exactly 2 uppercase letters (country code)
  • Exactly 2 digits (check digits)
  • 11 to 30 alphanumeric characters (BBAN — varies by country)

Total length range: 15 (Norway, NO15) to 34 (Saint Lucia, LC32). The generic regex captures this range. It will also accept structurally plausible strings with invalid country codes or wrong lengths — country-specific patterns tighten this.

Always strip whitespace and convert to uppercase before applying any regex:

// JavaScript
function preprocess(raw) {
  return raw.replace(/[s-]/g, '').toUpperCase();
}

# Python
def preprocess(raw: str) -> str:
    return raw.replace(' ', '').replace('-', '').upper()

Country-Specific Regex Patterns

Country-specific patterns constrain the total length and, in some cases, the internal structure. The BBAN format (the national portion after the check digits) varies by country.

Germany (DE) — 22 Characters

German IBANs contain only digits in the BBAN. The Bankleitzahl (8 digits) and account number (10 digits) are all numeric.

// JavaScript
const DE_IBAN = /^DE[0-9]{20}$/;

# Python
DE_IBAN = re.compile(r'^DE[0-9]{20}$')

// Valid: DE89370400440532013000
// Invalid: DE89370400440532013X00  (letter in BBAN)

United Kingdom (GB) — 22 Characters

GB IBANs have a 4-letter bank code followed by a 6-digit sort code and 8-digit account number — all defined structure.

// JavaScript
const GB_IBAN = /^GB[0-9]{2}[A-Z]{4}[0-9]{14}$/;

# Python
GB_IBAN = re.compile(r'^GB[0-9]{2}[A-Z]{4}[0-9]{14}$')

// Valid:   GB29NWBK60161331926819
// Invalid: GB29NW1K60161331926819  (digit in bank code)

France (FR) — 27 Characters

French IBANs embed a 5-digit bank code, 5-digit branch code, 11-character account number, and 2-digit national check digits. The account characters can be alphanumeric.

// JavaScript
const FR_IBAN = /^FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}$/;

# Python
FR_IBAN = re.compile(r'^FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}$')

// Valid: FR7630006000011234567890189

Netherlands (NL) — 18 Characters

Dutch IBANs have a 4-letter bank code followed by exactly 10 digits.

// JavaScript
const NL_IBAN = /^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/;

# Python
NL_IBAN = re.compile(r'^NL[0-9]{2}[A-Z]{4}[0-9]{10}$')

// Valid: NL91ABNA0417164300

Multi-Country Dispatch Pattern

In JavaScript, you can build a lookup table of country-specific patterns and fall back to the generic pattern for countries not explicitly listed:

const COUNTRY_PATTERNS = {
  DE: /^DE[0-9]{20}$/,
  GB: /^GB[0-9]{2}[A-Z]{4}[0-9]{14}$/,
  FR: /^FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}$/,
  NL: /^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/,
  AT: /^AT[0-9]{18}$/,
  CH: /^CH[0-9]{19}$/,
  ES: /^ES[0-9]{22}$/,
  IT: /^IT[0-9]{2}[A-Z][0-9]{10}[A-Z0-9]{12}$/,
  BE: /^BE[0-9]{14}$/,
  PL: /^PL[0-9]{26}$/,
};

const IBAN_GENERIC = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/;

function matchesStructure(iban) {
  const country = iban.slice(0, 2);
  const pattern = COUNTRY_PATTERNS[country] ?? IBAN_GENERIC;
  return pattern.test(iban);
}

Regex as Step 1: The Full Validation Pipeline

Here is the complete two-step pipeline in JavaScript. Regex gates the MOD-97 computation — no point running the arithmetic on obvious garbage.

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

function validateIban(raw) {
  const iban = raw.replace(/[s-]/g, '').toUpperCase();

  // Step 1a: format check
  if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(iban)) {
    return { valid: false, error: 'INVALID_FORMAT' };
  }

  // Step 1b: country + length check
  const country = iban.slice(0, 2);
  const expected = IBAN_LENGTHS[country];
  if (!expected) return { valid: false, error: 'UNKNOWN_COUNTRY' };
  if (iban.length !== expected) return { valid: false, error: 'WRONG_LENGTH' };

  // Step 2: MOD-97 check digit (cannot be done with regex)
  const rearranged = iban.slice(4) + iban.slice(0, 4);
  const numeric = rearranged.replace(/[A-Z]/g, c => String(c.charCodeAt(0) - 55));

  // BigInt required — the number is too large for Number
  if (BigInt(numeric) % 97n !== 1n) {
    return { valid: false, error: 'INVALID_CHECK_DIGITS' };
  }

  return { valid: true };
}

Notice the BigInt requirement for the MOD-97 step. The numeric string for a 32-character IBAN (Saint Lucia) can be 50+ digits — well beyond JavaScript's safe integer range. Always use BigInt in JavaScript, or Python's native arbitrary-precision integers.

What Regex Cannot Do

To be explicit about the limits of pattern matching for IBAN validation:

  • Cannot verify check digits. This is the primary limitation — a regex that accepts all valid IBANs also accepts most invalid ones with a single character changed.
  • Cannot verify bank existence. An IBAN can pass MOD-97 and reference a bank code that does not exist. Bank data lookup requires a BIC database.
  • Cannot verify account existence. No client-side technique can confirm that the account is open and funded — that requires the bank's systems.

For full validation including bank metadata, use the ibanchecker.cash API — it performs all three layers: format, MOD-97, and BIC database lookup — and returns the bank name and BIC alongside the validity result.

Last updated: June 2026

Validate an IBAN instantly

Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.

Open IBAN Checker →

Related Articles