IBAN Validation in JavaScript (Node.js & Browser) — Code Examples
Complete guide to IBAN validation in JavaScript: calling the ibanchecker.cash API with fetch, implementing MOD-97 with BigInt for offline use, browser form integration, and the ibantools npm package.
IBAN validation in JavaScript runs in every environment where JS runs — browser forms, Node.js APIs, serverless functions, and React Native apps. This guide covers two production paths: calling the ibanchecker.cash REST API for results that include bank metadata, and implementing the full ISO 13616 MOD-97 algorithm yourself for offline or embedded use cases.
Why Does IBAN Validation Require More Than a Regex?
A regular expression can verify that a string looks like an IBAN — right country prefix, right character class — but it cannot verify the check digits. The two digits at positions 3–4 of every IBAN are computed via a MOD-97 algorithm over the entire account number. A single transposed digit (e.g. DE89 → DE98) will pass any regex but fail the MOD-97 check. Because typos and copy-paste errors are the most common source of invalid IBANs in payment forms, skipping layer 3 means accepting broken account numbers.
Three checks must run in order:
- Format: Starts with two uppercase letters, then two digits, then alphanumeric BBAN.
- Length: Each country has a fixed length. German IBANs are always 22 characters; French IBANs are always 27.
- MOD-97: Rearrange, replace letters with numbers, compute integer mod 97 — result must be 1.
How Do You Call the ibanchecker.cash API from JavaScript?
The ibanchecker.cash API accepts a single IBAN and returns validity, bank name, BIC, and country — all in a single round trip. The endpoint works from any environment that can make outbound HTTPS requests, including browser fetch, Node.js fetch, and Cloudflare Workers.
async function validateIban(iban) {
const res = await fetch("https://ibanchecker.cash/api/v1/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ iban }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
const result = await validateIban("DE89370400440532013000");
console.log(result);
// {
// "valid": true,
// "iban": "DE89370400440532013000",
// "country": "Germany",
// "bankName": "Deutsche Bank",
// "bic": "DEUTDEDB",
// "formatted": "DE89 3704 0044 0532 0130 00"
// }For bulk validation — checking up to 100 IBANs in one request — use the bulk endpoint:
async function validateIbansBulk(ibans) {
const res = await fetch("https://ibanchecker.cash/api/v1/validate/bulk", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ibans }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { results } = await res.json();
return results;
}
const results = await validateIbansBulk([
"DE89370400440532013000",
"GB29NWBK60161331926819",
"INVALID0000",
]);
results.forEach(({ iban, valid, bankName }) => {
console.log(`${iban}: ${valid ? bankName : "invalid"}`);
});How Do You Implement MOD-97 Validation in Pure JavaScript?
When you need offline validation — in a browser bundle with no external calls, in a service worker, or in a React Native app — implement the algorithm directly. The key challenge in JavaScript is that the numeric string produced by the MOD-97 algorithm can exceed Number.MAX_SAFE_INTEGER, so you must use BigInt.
IBAN Length Table
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,
};Full Validator
function normalizeIban(raw) {
return raw.trim().replace(/[s-]/g, "").toUpperCase();
}
function validateIbanLocal(raw) {
const iban = normalizeIban(raw);
if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(iban)) {
return { valid: false, error: "Invalid format" };
}
const country = iban.slice(0, 2);
const expected = IBAN_LENGTHS[country];
if (!expected) {
return { valid: false, error: `Unknown country: ${country}` };
}
if (iban.length !== expected) {
return { valid: false, error: `Wrong length: expected ${expected}, got ${iban.length}` };
}
const rearranged = iban.slice(4) + iban.slice(0, 4);
const numeric = rearranged
.split("")
.map((c) => (c >= "A" ? (c.charCodeAt(0) - 55).toString() : c))
.join("");
if (BigInt(numeric) % 97n !== 1n) {
return { valid: false, error: "Check digit validation failed (MOD-97)" };
}
return { valid: true };
}
function formatIban(raw) {
const iban = normalizeIban(raw);
return iban.match(/.{1,4}/g).join(" ");
}
console.log(validateIbanLocal("DE89 3704 0044 0532 0130 00"));
// { valid: true }
console.log(validateIbanLocal("DE89370400440532013001"));
// { valid: false, error: "Check digit validation failed (MOD-97)" }
console.log(formatIban("DE89370400440532013000"));
// "DE89 3704 0044 0532 0130 00"How Do You Add Real-Time IBAN Validation to a Browser Form?
Combine the local validator with an API call to show instant feedback as the user types, then enrich the result with bank details on blur or submit. Run the local MOD-97 check inline — it costs zero network round trips — and only hit the API when the local check passes.
const input = document.getElementById("iban-input");
const feedback = document.getElementById("iban-feedback");
input.addEventListener("input", () => {
const { valid, error } = validateIbanLocal(input.value);
feedback.textContent = valid ? "✓ Format valid — checking bank..." : error ?? "";
feedback.style.color = valid ? "green" : "red";
if (valid) {
validateIban(input.value).then((result) => {
if (result.valid) {
feedback.textContent = `✓ ${result.bankName} (${result.bic})`;
} else {
feedback.textContent = "IBAN not recognized";
feedback.style.color = "red";
}
});
}
});How Do You Use the ibantools npm Package?
If you prefer a maintained npm package over a hand-rolled implementation, ibantools is the most widely used TypeScript-first IBAN library on npm. It covers all 84 IBAN countries, handles the MOD-97 check, and ships tree-shakeable ESM exports.
npm install ibantoolsimport { isValidIBAN, electronicFormatIBAN, friendlyFormatIBAN } from "ibantools";
console.log(isValidIBAN("DE89370400440532013000"));
// true
console.log(electronicFormatIBAN("DE89 3704 0044 0532 0130 00"));
// "DE89370400440532013000"
console.log(friendlyFormatIBAN("DE89370400440532013000"));
// "DE89 3704 0044 0532 0130 00"Note that ibantools validates structure only — it cannot return bank names or BICs. Combine it with the ibanchecker.cash API when you need bank metadata.
How Do You Handle Rate Limits and Errors Gracefully?
The ibanchecker.cash free tier enforces a per-hour request cap. In production, wrap the API call with local pre-validation so obviously invalid strings never hit the network, add retry logic for transient errors, and cache results for IBANs you have already validated.
const cache = new Map();
async function validateIbanCached(raw) {
const iban = normalizeIban(raw);
const local = validateIbanLocal(iban);
if (!local.valid) return local;
if (cache.has(iban)) return cache.get(iban);
const result = await validateIban(iban);
cache.set(iban, result);
return result;
}
async function validateWithRetry(iban, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await validateIbanCached(iban);
} catch (err) {
if (i === retries - 1) throw err;
await new Promise((r) => setTimeout(r, 200 * 2 ** i));
}
}
}See the pricing page for paid plans with higher rate limits and SLA guarantees.
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