TypeScript-first, zero-dependency validator for national identity and tax documents from every country.
🎮 Live playground: https://lu1tr0n.github.io/nationid_example/ — try every country, every helper, in 3 locales. 📖 API Reference: https://lu1tr0n.github.io/nationid/ 📊 Benchmarks: see BENCHMARKS.md
nationid is a focused, comprehensive library for validating national identity documents and tax IDs. It ships with checksum verification (not just regex shape), proper formatting and normalization, and works in Node, browsers, Bun, Deno and edge runtimes.
Existing tools cover a fraction of the world. validator.js only validates 6 LATAM tax IDs. cpf-cnpj-validator covers Brazil. rut.js covers Chile. None ship El Salvador, Guatemala, Honduras, Dominican Republic, or Costa Rica with checksum verification.
nationid fills that gap. As of v2.2 it ships 54 countries with ~145 document codes, all with proper algorithms documented from official sources — and ships an API-stability promise plus a CI-enforced governance test that every high-confidence spec cites a first-party issuer source.
- 🇸🇬 Singapore — second country of Asia phase 2. Three new specs under
nationid/sg:SG_NRIC(9-char identity number for citizens/PRs, weighted mod-11 check letter, ICA) andSG_FIN(9-char foreigner identity number incl. the 2022 M-series, ICA/MOM), bothpersonal; andSG_UEN(Unique Entity Number, three category formats each with its own check letter, ACRA),tax. All three atconfidence: "high". - UEN check digits —
SG_UENupgrades from format-only to full check-letter validation across all three categories (Business, Local Company, Other Entity), with the 38-code entity-type whitelist. Constants are taken verbatim frompython-stdnum/stdnum/sg/uen.py; the four doctest fixtures (00192200M,197401143C,S16FC0121D,T01FC6132D) anchor the suite, alongside the real-world Cat B UENs196800306E(DBS) and199201624D(Singtel). - Oracle-agreement tests — every SG spec runs a property test against an independent re-implementation of the published algorithm (NRIC/FIN weighted mod-11, plus the three UEN categories). Singapore's authoritative URLs are all on
*.gov.sghosts, so the governance citation test passes without a host-allowlist patch. - 7000+ → 7800+ tests, 54 countries × 145 codes.
- 🇯🇵 Japan — first country of Asia phase 2. Two new specs under
nationid/jp:JP_MY_NUMBER(12-digit Individual Number, weighted mod-11, MIC Ordinance 85/2014) andJP_CORPORATE_NUMBER(13-digit Corporate Number, weighted mod-9, NTA). Both atconfidence: "high". The NTA's own corporate number7000012050002is verifiable in the public registry and is used as a canonical anchor in the test suite. - Oracle-agreement tests — every JP spec runs a 10k-sample property test against an inline port of
python-stdnum/stdnum/jp/in_.py(My Number) andpython-stdnum/stdnum/jp/cn.py(Corporate Number). Independent cross-validation built into CI. - 6970+ → 7000+ tests, 53 countries × ~147 codes.
- 🇪🇺 EU-VAT complete — 16 EU members + 1 EEA participant ship VAT validators in one batched release:
IE_VAT,AT_UID,LU_VAT,GR_VAT,CZ_DIC,HU_VAT,RO_VAT,BG_VAT,HR_OIB,SK_VAT,SI_VAT,LT_VAT,LV_VAT,EE_VAT,MT_VAT,CY_VAT,IS_VSK. Unlocks EU VIES feature parity as a single tagline. - ISO/IEC 7064 MOD 11,10 primitive —
mod11_10CheckDigit+mod11_10Validexported fromnationid/algorithms. Length-generic; used by HR_OIB, DE_USTID, DE_STEUER_ID. - Greek
EL/GRprefix handling built in — accept both on input, normalise toEL(canonical VIES form). Closes the #1 historical EU-VAT bug. - Every URL in every v2.0+ JSDoc verified live via
browser_fetch(firefox133 TLS impersonation) before publish — fixes 5 broken URLs caught in shipped India v1.2 and 3 in v2.0.web.archive.orgsnapshots accepted as supplementary citation where issuer cert blocks programmatic checks. Pre-v2.0 specs are being back-filled to the same standard on a country-by-country basis as part of the post-v2.1 architecture audit.
- 🇮🇳 India support — first Asia country. Five new specs under
nationid/in:IN_AADHAAR(Verhoeff + palindrome reject, UIDAI),IN_VID(16-digit Aadhaar alias),IN_PAN(entity-type whitelist, Income Tax Department),IN_GSTIN(Luhn mod-36 + embedded PAN + state code, CBIC),IN_EPIC(format-only, ECI). - Verhoeff primitive —
verhoeffValidandverhoeffCheckDigitexported fromnationid/algorithms. Canonical D₅ multiplication and permutation tables verbatim from Verhoeff (1969). - 6488 → 6606 tests, 35 countries × ~125 codes. Tarball 2.9 MB unpacked (+200 KB).
- Country catalog under
nationid/catalog—getCountryInfo,listCountries,countryName,flagEmoji. Backed byIntl.DisplayNames(CLDR), so any locale the runtime supports works out of the box — not justes / en / pt.
- TypeScript inference upgraded.
parse("MX_CURP", x).codenow infers the literal"MX_CURP", not the 124-member union.extractDOB / extractSex / extractRegionconstrain their first argument to the codes that actually encode each field. Country bundles expose literalcountry/defaultPersonal/defaultTaxtypes. - 76% smaller npm tarball. From 1.7 MB → 414 KB by dropping shipped sourcemaps and breaking the
extract/pii/catalogsubpaths' dependency on the root REGISTRY.nationid/extractalone drops -90% raw. - Three small breaking changes, all documented in
MIGRATION.md§0:pii.masknow throws on unknown codes (symmetric withhash/lastN);package.jsonexports denies undocumented subpaths;CA_PASAPORTEandES_PASAPORTEconfidence demotehigh → moderate(no first-party issuer spec to cite). - Governance test. New
tests/governance/confidence-citations.test.tsfails CI if anyconfidence: "high"spec lacks an issuer-TLD URL or recognized legal statute in its JSDoc header.
See MIGRATION.md §0 before upgrading from v0.x.
npm i nationid
# or
pnpm add nationid
# or
yarn add nationidRequires Node 20+.
import { validate, format, normalize, parse } from "nationid";
validate("SV_DUI", "04567890-3"); // true
validate("BR_CPF", "529.982.247-25"); // true
validate("CL_RUT", "12.345.678-5"); // true
validate("ES_DNI", "12345678Z"); // true
format("SV_DUI", "045678903"); // "04567890-3"
normalize("SV_DUI", "04567890-3"); // "045678903"
const result = parse("SV_NIT", "0614-150585-101-5");
if (result.ok) {
console.log(result.normalized); // "06141505851015"
console.log(result.formatted); // "0614-150585-101-5"
console.log(result.confidence); // "moderate"
}parse() returns a discriminated union — no exceptions are thrown from the
public API. On failure it carries a typed reason.kind:
const r = parse("SV_DUI", "");
if (!r.ok) r.reason.kind; // "empty" | "too_short" | "too_long" | "invalid_format" | "invalid_checksum"Try every country and every helper without installing anything: https://lu1tr0n.github.io/nationid_example/
The playground covers:
- ✅
validate / parse / format / normalizefor every supported country - 🎯
extract(DOB, sex, region) where the document encodes it - 🔒
piimasking + SHA-256 hashing for safe display and storage - 🌍
i18nerror messages ines,en,pt - 📚
catalog— queryable document metadata for UI dropdowns - 6 best-practice code examples (React forms, server validation, KYC display, etc.)
Source code for the showcase site: https://github.com/lu1tr0n/nationid_example
Single country, ~3-5KB gzipped:
import { validate } from "nationid/sv";
validate("DUI", "045678903");Algorithm primitives:
import { luhnValid, mod11WeightedSum } from "nationid/algorithms";Four tree-shakable modules for common app needs (available since v0.3):
// Extract structured data from valid documents
import { extractDOB, extractSex, extractRegion } from "nationid/extract";
extractDOB("MX_CURP", "GOMC850315HDFRRR07"); // { year: 1985, month: 3, day: 15 }
extractSex("AR_CUIT", "20-12345678-3"); // "M"
// Mask + hash for safe display and storage
import { mask, hash, lastN } from "nationid/pii";
mask("BR_CPF", "12345678901"); // "***.***.**9-01"
await hash("BR_CPF", "12345678901", { salt: "tenant" }); // hex SHA-256
// Localized error messages (es, en, pt)
import { getErrorMessage } from "nationid/i18n";
getErrorMessage({ kind: "too_short" }, "es", "DUI"); // "El DUI es demasiado corto."
// Document catalog with localized names — for UI dropdowns
import { listDocuments } from "nationid/catalog";
listDocuments("MX", "es");
// [{ code: "MX_CURP", displayName: "CURP",
// longName: "Clave Única de Registro de Población",
// purpose: "identity", confidence: "high", ... }, ...]
// Country catalog (v1.1) — names + flags for every supported country.
// Uses Intl.DisplayNames (CLDR) so any locale the runtime supports works.
import { getCountryInfo, listCountries, flagEmoji } from "nationid/catalog";
getCountryInfo("MX", "es");
// { code: "MX", alpha3: "MEX", name: "México", flag: "🇲🇽" }
flagEmoji("BR"); // "🇧🇷"
listCountries("pt").length; // 54Each subpath is independently tree-shakable. Single locales (nationid/i18n/es, /en, /pt) ship as <200B bundles.
| Country | Personal | Tax |
|---|---|---|
| 🇸🇻 El Salvador | DUI | NIT |
| 🇲🇽 México | CURP, Clave de Elector | RFC (PF + PM) |
| 🇨🇴 Colombia | CC, CE, TI, Pasaporte, PEP, PPT | NIT |
| 🇧🇷 Brasil | CPF, CNH, Título de Eleitor | CNPJ, PIS |
| 🇵🇪 Perú | DNI, CE | RUC |
| 🇦🇷 Argentina | DNI, CUIL | CUIT, CDI |
| 🇨🇱 Chile | RUT/RUN | RUT/RUN |
| 🇩🇴 Rep. Dominicana | Cédula | RNC |
| 🇬🇹 Guatemala | DPI | NIT |
| 🇭🇳 Honduras | DNI | RTN |
| 🇨🇷 Costa Rica | Cédula física, DIMEX | Cédula jurídica |
| 🇪🇸 España | DNI, NIE | NIF (CIF), NUSS |
| 🇺🇸 United States | SSN, ITIN | EIN |
| 🇧🇴 Bolivia (v0.4) | CI | NIT |
| 🇪🇨 Ecuador (v0.4) | Cédula | RUC |
| 🇵🇾 Paraguay (v0.4) | CI | RUC |
| 🇳🇮 Nicaragua (v0.4) | Cédula | RUC |
| 🇵🇦 Panamá (v0.4) | Cédula | RUC |
| 🇺🇾 Uruguay (v0.4) | CI | RUT |
| 🇨🇦 Canadá (v0.4) | SIN | BN |
| 🇵🇹 Portugal (v0.4) | CC | NIF |
| 🇻🇪 Venezuela (v0.4) | Cédula | RIF |
| 🇬🇧 United Kingdom (v0.6) | NINO, NHS Number | UTR, VAT |
| 🇫🇷 France (v0.6) | NIR | SIREN, SIRET, TVA |
| 🇩🇪 Germany (v0.6) | Steuer-ID | Steuernummer, USt-IdNr |
| 🇮🇹 Italy (v0.6) | Codice Fiscale | Partita IVA |
| 🇳🇱 Netherlands (v0.6) | BSN | BTW |
| 🇧🇪 Belgium (v0.6) | NRN | BTW |
| 🇨🇭 Switzerland (v0.6) | AHV | UID, MWST |
| 🇵🇱 Poland (v0.6) | PESEL | NIP, REGON |
| 🇸🇪 Sweden (v0.6) | Personnummer | Organisationsnummer, Moms |
| 🇳🇴 Norway (v0.6) | Fødselsnummer, D-nummer | Organisasjonsnummer, MVA |
| 🇩🇰 Denmark (v0.6) | CPR | CVR, Moms |
| 🇫🇮 Finland (v0.6) | HETU | Y-tunnus, ALV |
| 🇮🇳 India (v1.2) | Aadhaar, VID, Voter ID (EPIC) | PAN, GSTIN |
| 🇯🇵 Japan (v2.1) | My Number | Corporate Number |
| 🇸🇬 Singapore (v2.2) | NRIC, FIN | UEN |
| 🇮🇪 Ireland (v2.0) | — | VAT |
| 🇦🇹 Austria (v2.0) | — | UID (USt-IdNr) |
| 🇱🇺 Luxembourg (v2.0) | — | TVA |
| 🇬🇷 Greece (v2.0) | — | VAT (AFM, VIES prefix EL) |
| 🇨🇿 Czechia (v2.0) | — | DIČ (legal entity) |
| 🇭🇺 Hungary (v2.0) | — | VAT (közösségi adószám) |
| 🇷🇴 Romania (v2.0) | — | VAT (CUI / CIF) |
| 🇧🇬 Bulgaria (v2.0) | — | VAT (legal entity) |
| 🇭🇷 Croatia (v2.0) | OIB | OIB |
| 🇸🇰 Slovakia (v2.0) | — | VAT (IČ DPH) |
| 🇸🇮 Slovenia (v2.0) | — | VAT (DDV) |
| 🇱🇹 Lithuania (v2.0) | — | VAT (PVM) |
| 🇱🇻 Latvia (v2.0) | — | VAT (PVN, legal entity high / personal moderate) |
| 🇪🇪 Estonia (v2.0) | — | VAT (KMKR) |
| 🇲🇹 Malta (v2.0) | — | VAT |
| 🇨🇾 Cyprus (v2.0) | — | VAT |
| 🇮🇸 Iceland (v2.0) | — | VSK (format-only, EEA not VIES) |
Full per-country docs with algorithms and sources cited live in docs/countries/.
Each spec carries a confidence value reflecting how well-verified its algorithm is:
high— official source AND mature library agree.moderate— one official source OR mature library agrees; the other missing.low— only community / reverse-engineered. Format-only validation.unconfirmed— no algorithm verified. Format-only validation.
UIs can choose to surface a warning when a low-confidence document validates only by format.
- Emiso — multi-tenant electronic-invoicing platform for Central America. Uses
nationidfor receiver DUI / NIT / RUC validation and KYC masking.
If your product uses nationid and you'd like to be listed here, open a PR adding a one-line entry above.
| nationid | validator.js | cpf-cnpj-validator | rut.js | |
|---|---|---|---|---|
| LATAM countries | 22 | 6 | 1 | 1 |
| European countries | 31 (EU-27 VIES + UK/CH/NO/IS) | 8 | 0 | 0 |
| EU-VIES VAT coverage | EU-27 complete | partial | 0 | 0 |
| Asia countries | 3 (IN, JP, SG; KR/TW next) | 0 | 0 | 0 |
| El Salvador | ✅ | ❌ | ❌ | ❌ |
| Guatemala | ✅ | ❌ | ❌ | ❌ |
| Honduras | ✅ | ❌ | ❌ | ❌ |
| Costa Rica | ✅ | ❌ | ❌ | ❌ |
| Bundle size (1 country) | ~3-5KB | ~40KB full | ~5KB | ~5KB |
| TypeScript types | First-class | Yes | Limited | Limited |
| Tree-shakable subpaths | ✅ | ❌ | N/A | N/A |
| Zero deps | ✅ | ✅ | ✅ | ✅ |
- v0.1 — 13 countries: SV, MX, CO, BR, PE, AR, CL, DO, GT, HN, CR, ES, US ✅
- v0.2 — 8 additional codes in covered countries ✅
- v0.3 —
extract+pii+i18n+catalogsubpaths ✅ - v0.4 — 9 new countries: UY, VE, PA, EC, BO, PY, NI, CA, PT ✅
- v0.5 — Passport family + ICAO 9303 algorithm + BR_CNPJ alphanum + MX_NSS + audit fixes ✅
- v0.6 — Europe principal: GB, FR, DE, IT, NL, BE, CH, PL, SE, NO, DK, FI ✅
- v1.0 — API stability declared. Every
high-confidence spec backed by a first-party citation (CI-enforced). 76% smaller tarball. Type inference narrowing forparse/getSpec/extract*. ✅ - v1.1 — Country catalog under
nationid/catalog: names + flags + ISO alpha-3, locale param viaIntl.DisplayNames(any BCP 47 tag). ✅ - v1.2 — Asia phase 1: India (Aadhaar, VID, PAN, GSTIN, EPIC) + Verhoeff primitive. ✅
- v2.0 — EU-VAT complete: 16 EU members + Iceland (EEA). ISO/IEC 7064 MOD 11,10 primitive. URL liveness audit pattern. ✅
- v2.1 — Asia phase 2 — Japan (My Number + Corporate Number). MIC + NTA algorithms cross-validated against
python-stdnum. ✅ - v2.2 — Asia phase 2 — Singapore (NRIC, FIN, UEN). ICA/ACRA algorithms, UEN cross-validated against
python-stdnum/stdnum/sg/uen.py. ✅ - v2.3-v2.4 — Asia phase 2 (research + verification complete): KR (RRN/BRN), TW (ID/Tax). Implementing next.
- v2.5 — Balkans via JMBG core primitive (RS/BA/MK/ME) + GB Northern Ireland
XIprefix +BG_EGN+CZ_RC(unlocks 10-digit BG and full CZ DIC branches). - v2.x —
@nationid/reactcompanion with<DocumentInput>, additional i18n locales, mutation testing (Stryker), lazy REGISTRY for full root-import tree-shaking.
See CONTRIBUTING.md. Country submissions are welcomed — every country added must include cited official sources and a comprehensive test fixture set.
MIT — see LICENSE.