Validate, parse, normalize, format and mask Pakistani IBANs — plus a
hand-curated bank-code directory — for fintech, wallets, payroll,
e-commerce checkout, accounting and banking-dashboard code. Written in
TypeScript, ships with type declarations, and has zero runtime
dependencies. Works unchanged in TypeScript, in plain JavaScript with
import (ESM), and in plain JavaScript with require (CommonJS) — no
build step and no TypeScript toolchain required by the consumer. Everything
runs offline; there are no network calls at runtime.
Badges
Why
Every Pakistani fintech, wallet, payroll or e-commerce checkout ends up
re-implementing the same handful of things: is this IBAN well-formed, what
bank does it belong to, and how do I show it in a UI or a log without
leaking the whole account number. handles the boring,
easy-to-get-subtly-wrong parts — ISO 7064 MOD-97 checksums, structural
parsing, masking — so you don't have to.
Requires Node.js >= 18. No peer dependencies, no runtime dependencies.
Quick start
The package publishes a dual ESM + CommonJS build with .d.ts / .d.cts
type declarations. Use whichever module system your project already uses —
you do not need TypeScript, a bundler, or a build step to consume it.
TypeScript
import {
validateIBAN,
parseIBAN,
normalizeIBAN,
formatIBAN,
maskIBAN,
maskAccountNumber,
getBank,
getBankFromIBAN,
searchBanks,
getBanks,
type IBANValidationResult,
type Bank,
} from 'pk-bank-utils';
Structural validation (length, country prefix, field character classes)
followed by an ISO 7064 MOD-97 checksum. Never throws — malformed input
returns { valid: false, reason: '...' }.
The 16-character account field must now be digits only. Every
SBP-licensed bank issues purely numeric account fields, so a letter there is
a typo — and when its MOD-97 checksum happened to pass, the package
previously reported a wrong IBAN as valid. This is a breaking change for
the small number of callers that relied on the ISO registry's 16!c format.
Pass {allowAlphanumericAccount: true} as the second argument to restore
that behaviour.
validateIBAN('PK36SCBL0000001123456702');
// { valid: true, country: 'PK', checkDigits: '36', bankCode: 'SCBL', accountNumber: '0000001123456702' }
validateIBAN('PK36SCBL0000001123456701');
// { valid: false, reason: 'Invalid check digits', country: 'PK', checkDigits: '36', bankCode: 'SCBL', accountNumber: '0000001123456701' }
validateIBAN('not an iban');
// { valid: false, reason: 'IBAN must be 24 characters, got 9' }
// The letter O is a typo
validateIBAN('PK36SCBLO000001123456702');
// {
// valid: false,
// reason: 'Account number must be 16 digits',
// country: 'PK',
// checkDigits: '36',
// bankCode: 'SCBL',
// accountNumber: 'O000001123456702',
// hints: [{ position: 8, found: 'O', expected: '0' }],
// suggestion: 'PK36SCBL0000001123456702'
// }
// Restore the old lenient behaviour with the opt-out
validateIBAN('PK36SCBLO000001123456702', {allowAlphanumericAccount: true});
// { valid: true, country: 'PK', checkDigits: '36', bankCode: 'SCBL', accountNumber: 'O000001123456702' }
Accepts lowercase, spaced, and dashed input, Urdu and Arabic-Indic digits,
and text carrying invisible characters from a copy-paste — it normalizes
internally before validating.
Bank-code recognition is not part of validity: an IBAN with an unknown
4-letter bank code can still be valid: true. Use getBankFromIBAN to
look up the name separately.
Alias for validateIBAN — the same function, offered under the name
you'll also reach for when the goal is extracting fields rather than
checking validity.
explainIBAN(iban: string): IBANExplanation
Reports which characters in the account field are letters that were almost
certainly meant to be digits, and, when substituting all of them yields a
checksum-valid IBAN, the IBAN the user meant. Never throws.
Uppercases and strips whitespace and any dash (hyphen, en dash,
non-breaking hyphen, minus). Also maps Arabic-Indic and Eastern-Arabic-Indic
(Urdu) digits and full-width characters to ASCII, and removes every
invisible Unicode format character — zero-width spaces and joiners, the BOM,
bidi marks and isolates, the Arabic Letter Mark, soft hyphens — the invisible
passengers that ride along when an IBAN is pasted from WhatsApp or a bank
app's share sheet. Does not validate.
Keeps the country code, check digits, bank code and last 4 account digits
visible; masks the rest. Falls back to a shorter reveal on abnormally short
input rather than throwing.
Extracts the bank code from an IBAN and looks it up. Works even when the
IBAN's checksum is wrong, as long as the bank-code position is
well-formed — null only when the IBAN is structurally malformed or the
code isn't in the registry.
searchBanks(query: string): Bank[]
Case-insensitive substring match on bank name. An empty query returns []
(use getBanks() for the full list).
Returns the full registry (a defensive copy — mutating the result doesn't
affect the package's internal data).
IBANValidationResult
Field
Type
Notes
valid
boolean
true only if structure and checksum both pass
country
'PK' | undefined
Present once the IBAN is structurally well-formed
checkDigits
string | undefined
The 2-digit check portion
bankCode
string | undefined
The 4-letter bank code — present even on a checksum failure
accountNumber
string | undefined
The remaining 16 characters
reason
string | undefined
Present only when valid: false
hints
IBANHint[] | undefined
Account-field positions holding a likely mistyped digit
suggestion
string | undefined
The intended IBAN, when substituting every hint checksums
IBANExplanation
Field
Type
Notes
normalized
string
The uppercased, space/dash-stripped form
valid
boolean
true if the IBAN passed structure and checksum
reason
string | undefined
Present only when valid: false
hints
IBANHint[]
Always an array; empty when there is nothing to point at
suggestion
string | undefined
Present only when substituting every hint yields a checksum-valid IBAN
message
string
A ready-to-log sentence
IBANHint
Field
Type
Notes
position
number
0-based index into the 24-character IBAN; the first account character is 8
found
string
The character actually present at that position
expected
string
The digit it was almost certainly meant to be
Bank
Field
Type
Notes
code
string
4-letter IBAN bank identifier (characters 5-8 of a PK IBAN)
name
string
Bank's display name
type
BankType
Licence class — see below
islamic
boolean
Holds a full-fledged Islamic banking licence
swift
string | null
8-character head-office BIC; null for banks outside SWIFT
status
BankStatus
'active', 'merged' or 'defunct'
successorCode
string | undefined
For status: 'merged', the code of the surviving bank
BankType is 'commercial' | 'microfinance' | 'digital' | 'specialized' and
BankStatus is 'active' | 'merged' | 'defunct'. Both are exported.
type and islamic are independent axes, deliberately. Meezan, Dubai
Islamic, BankIslami, MCB Islamic, Al Baraka and Faysal are all scheduled
commercial banks that hold an Islamic licence, so they are
type: 'commercial' with islamic: true. Folding the two together would mean
banks.filter((b) => b.type === 'commercial') silently dropped six of the
largest retail networks in the country.
// every commercial bank, Islamic or not
getBanks().filter((bank) => bank.type === 'commercial');
// only the full-fledged Islamic banks
getBanks().filter((bank) => bank.islamic);
islamic is about the licence, not the product range: a conventional bank
running an Islamic window is false. Faysal Bank is true because it
surrendered its conventional licence in January 2023.
swift is null rather than absent for the banks that issue PK IBANs without
holding a BIC. SWIFT membership serves cross-border correspondent banking, so a
domestic-only microfinance bank has no need of one.
Merged banks stay in the registry so that IBANs issued before the merger still
resolve, and name the bank that took them over:
The bank-code directory (getBank, getBankFromIBAN, searchBanks,
getBanks) is compiled from the State Bank of
Pakistan's own documents: the IBAN Guidelines (PSD Circular Letter No. 02 of 2012),
which establish that the IBAN bank identifier is the first four letters of the
bank's SWIFT BIC, and the Dams Fund IBAN
notification,
which publishes live IBANs for 36 institutions — including the microfinance
banks that no SWIFT directory lists. Both were cross-checked against
theswiftcodes.com/pakistan. The
registry's size and its verification date are whatever src/data/banks.json
says: see _meta.lastVerified and the per-source _meta.sources.
It is still a point-in-time snapshot rather than an automated feed, and it is
deliberately incomplete: a bank is listed only when its code was found in a
source, so several licensed microfinance and digital banks are absent rather
than guessed. Codes, names and coverage may lag renames, mergers and new
entrants. Do not rely on it for compliance-sensitive decisions; verify against
your own bank or payment processor. Corrections and additions are welcome via a
pull request against src/data/banks.json — please cite a source.
What this package does not do
Does not verify that an account actually exists.validateIBAN
checks structure and checksum only. Real account verification requires an
authorized bank/payment API integration, which is outside this package's
scope by design.
A suggestion is not a search over arbitrary edits. It is a
checksum-confirmed correction of visually confusable characters only
(O↔0, I/L↔1, S↔5, B↔8, Z↔2, G↔6). It does not claim the account exists.
Does not validate non-Pakistani IBANs.
Does not include SWIFT/BIC parsing, Raast identifiers, or phone-number
utilities in this release.
Examples
Runnable scripts covering every public function live in
examples/ (not shipped in the npm tarball, but exercised by
the test suite so they never rot):
npm run examples
File
Shows
01-validate-iban.ts
validateIBAN / parseIBAN on valid and invalid IBANs