At some point, most developers working on payments hit the same wall: they need valid IBANs for tests, and there is no good way to type them by hand. Copy-pasting the same handful of examples leads to brittle suites, and using a real IBAN is a compliance problem waiting to happen. The clean solution is to generate them programmatically — structurally correct, checksum-valid, and as many as you need.
This guide walks through exactly how to do that in Python, JavaScript, and PHP. By the end you will have a small, dependency-light function you can drop into a factory, a seed script, or a fixtures module. If you just need a handful of numbers right now without writing code, the Random IBAN generator produces them instantly and exports to CSV, JSON, or TXT.
What "Generating an IBAN" Actually Means
An IBAN is not a single opaque number — it is three concatenated parts:
ES 76 21000418450200051332
│ │ └── BBAN (country-specific: bank + branch + account)
│ └───── Check digits (calculated, positions 3–4)
└──────── Country code (ISO 3166-1, positions 1–2)
So generating one means doing three things in order:
- Pick a country and look up its IBAN length and BBAN layout.
- Build a random BBAN that matches that layout (the right number of digits, and letters where the spec allows them).
- Calculate the two check digits with the MOD-97 algorithm so the whole thing validates.
The first step needs a small lookup table. The second is plain random generation. The third is the only part with real logic, and it is the same routine you would use to validate an IBAN, run in reverse.
The Core: Calculating Check Digits
Every language below shares the same check-digit routine. The rule from ISO 7064 / ISO 13616 is:
- Take the BBAN, append the country code, then append
00. - Replace each letter with two digits (
A=10,B=11, …Z=35). - Read the result as one big integer and compute
98 - (integer mod 97). - Zero-pad that result to two digits.
That padded number is your check-digit pair. Drop it between the country code and the BBAN, and the finished IBAN passes MOD-97 validation. Let's implement it.
Generating an IBAN in Python
Python's arbitrary-precision integers make this almost trivial — no big-integer library required.
import random
# Minimal layout table: country -> (total length, BBAN length)
IBAN_SPECS = {
"DE": 22, "ES": 24, "FR": 27, "GB": 22,
"NL": 18, "IT": 27, "PT": 25, "BE": 16,
}
def _check_digits(country: str, bban: str) -> str:
rearranged = bban + country + "00"
numeric = "".join(
str(ord(c) - 55) if c.isalpha() else c
for c in rearranged
)
check = 98 - (int(numeric) % 97)
return f"{check:02d}"
def generate_iban(country: str = "ES") -> str:
total_len = IBAN_SPECS[country]
bban_len = total_len - 4 # minus country code + check digits
bban = "".join(random.choices("0123456789", k=bban_len))
return country + _check_digits(country, bban) + bban
# Usage
print(generate_iban("DE")) # e.g. DE21 ... (valid checksum)
print([generate_iban("ES") for _ in range(5)])
This keeps the BBAN numeric for simplicity, which is correct for the countries above. A few countries (the UK, for instance) include letters in the bank code — for those you would randomise the letter positions too. For test data, an all-numeric BBAN is usually fine because it still passes MOD-97 and length checks.
Generating an IBAN in JavaScript
The only wrinkle in JS is that the rearranged number is too large for a normal Number. The standard fix is a running-remainder loop, which keeps the value under 97 the whole time.
const IBAN_SPECS = {
DE: 22, ES: 24, FR: 27, GB: 22,
NL: 18, IT: 27, PT: 25, BE: 16,
};
function mod97(numericString) {
let remainder = 0;
for (const ch of numericString) {
remainder = (remainder * 10 + Number(ch)) % 97;
}
return remainder;
}
function checkDigits(country, bban) {
const rearranged = bban + country + '00';
const numeric = rearranged.replace(/[A-Z]/g, c =>
(c.charCodeAt(0) - 55).toString()
);
const check = 98 - mod97(numeric);
return String(check).padStart(2, '0');
}
function generateIban(country = 'ES') {
const bbanLen = IBAN_SPECS[country] - 4;
let bban = '';
for (let i = 0; i < bbanLen; i++) {
bban += Math.floor(Math.random() * 10);
}
return country + checkDigits(country, bban) + bban;
}
// Usage
console.log(generateIban('FR'));
console.log(Array.from({ length: 5 }, () => generateIban('NL')));
If you need cryptographically stronger randomness (rarely necessary for test data), swap Math.random() for crypto.getRandomValues(). For fixtures and demos, the simple version is perfectly adequate.
Generating an IBAN in PHP
PHP needs the bcmath extension for the large modulo, which ships with most installations.
<?php
const IBAN_SPECS = [
'DE' => 22, 'ES' => 24, 'FR' => 27, 'GB' => 22,
'NL' => 18, 'IT' => 27, 'PT' => 25, 'BE' => 16,
];
function checkDigits(string $country, string $bban): string {
$rearranged = $bban . $country . '00';
$numeric = '';
foreach (str_split($rearranged) as $ch) {
$numeric .= ctype_alpha($ch) ? (string)(ord($ch) - 55) : $ch;
}
$check = 98 - (int) bcmod($numeric, '97');
return str_pad((string) $check, 2, '0', STR_PAD_LEFT);
}
function generateIban(string $country = 'ES'): string {
$bbanLen = IBAN_SPECS[$country] - 4;
$bban = '';
for ($i = 0; $i < $bbanLen; $i++) {
$bban .= random_int(0, 9);
}
return $country . checkDigits($country, $bban) . $bban;
}
// Usage
echo generateIban('IT'), PHP_EOL;
Note the use of random_int() rather than rand() — it is the modern, unbiased choice and costs nothing extra here.
Using Generated IBANs in a Test Suite
Once you have a generator, wire it into your tests rather than scattering literals. A few patterns that work well:
- Factories and fixtures. Call
generateIban()inside your factory (FactoryBoy, Faker provider, Laravel factory) so every test run gets fresh, valid data. - Seed data for staging. Populate demo databases with a few hundred IBANs across several countries to exercise country-specific formatting.
- Boundary cases. Keep a separate set of deliberately broken strings (wrong length, bad checksum, unsupported country) to test that your validation rejects them.
- Determinism when you need it. Seed your RNG at the start of a test if you want reproducible IBANs; leave it unseeded for fuzz-style coverage.
Whatever you generate, label it clearly as synthetic. These numbers are structurally valid and pass MOD-97, but they are not real accounts and must never reach a production payment rail. See our guide on storing IBANs safely in payment systems for how to keep test and production data from mixing.
When Not to Write Your Own Generator
Rolling your own is great for tight integration, but it is not always worth it. Skip the code when:
- You need a quick batch right now — paste them in seconds from the Random IBAN generator and export to CSV/JSON.
- You need broad country coverage with correct BBAN sub-structures (national check digits, letter positions) that a minimal table does not capture.
- You are working outside a codebase — preparing a spreadsheet, a Postman collection, or documentation.
And whichever route you take, validate the output. Run a sample through the IBAN validator to confirm your check-digit logic matches the reference implementation. If anything fails MOD-97, the bug is almost always in the letter-to-number conversion or an off-by-one in the BBAN length.
FAQ
Are programmatically generated IBANs safe to use?
Yes, for testing, QA, demos, and documentation. They are structurally valid and pass checksum validation, but they are not linked to any real bank account. Never use generated IBANs for real transactions or in a production payment system.
Do I need an external library to generate an IBAN?
No. The examples above use only the standard library in each language — Python's built-in big integers, a running-remainder loop in JavaScript, and PHP's bundled bcmath. Libraries can help with full country coverage, but the core algorithm is a few lines.
Why is my generated IBAN failing validation?
The most common causes are an incorrect letter-to-number mapping (it must be A=10 through Z=35), forgetting to append the country code and 00 before the modulo step, or using the wrong BBAN length for the country. Compare a single failing case against the validator to isolate it.
How do I generate IBANs for countries with letters in the BBAN?
Extend the random-BBAN step to place letters where the country's spec allows them — for example, the UK bank code is four letters. Keep a per-country pattern (which positions are letters versus digits) instead of a single length, then fill each position accordingly before calculating the check digits.
Can I generate thousands of IBANs at once?
Yes. The generator functions are cheap to call in a loop, so producing thousands is instant. If you would rather not run code, the Random IBAN generator supports bulk generation and one-click export to CSV, JSON, and TXT.
What is the difference between generating and validating an IBAN?
Validation checks an existing number by confirming mod 97 == 1. Generation runs the same arithmetic in reverse: you build the BBAN first, then compute 98 - (mod 97) to find the check digits that make the number valid. They share the same core routine.