English ยท Espaรฑol ยท Portuguรชs
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.
nationid/sg: SG_NRIC (9-char identity number for citizens/PRs, weighted mod-11 check letter, ICA) and SG_FIN (9-char foreigner identity number incl. the 2022 M-series, ICA/MOM), both personal; and SG_UEN (Unique Entity Number, three category formats each with its own check letter, ACRA), tax. All three at confidence: "high".SG_UEN upgrades 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 from python-stdnum/stdnum/sg/uen.py; the four doctest fixtures (00192200M, 197401143C, S16FC0121D, T01FC6132D) anchor the suite, alongside the real-world Cat B UENs 196800306E (DBS) and 199201624D (Singtel).*.gov.sg hosts, so the governance citation test passes without a host-allowlist patch.nationid/jp: JP_MY_NUMBER (12-digit Individual Number, weighted mod-11, MIC Ordinance 85/2014) and JP_CORPORATE_NUMBER (13-digit Corporate Number, weighted mod-9, NTA). Both at confidence: "high". The NTA's own corporate number 7000012050002 is verifiable in the public registry and is used as a canonical anchor in the test suite.python-stdnum/stdnum/jp/in_.py (My Number) and python-stdnum/stdnum/jp/cn.py (Corporate Number). Independent cross-validation built into CI.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.mod11_10CheckDigit + mod11_10Valid exported from nationid/algorithms. Length-generic; used by HR_OIB, DE_USTID, DE_STEUER_ID.EL/GR prefix handling built in โ accept both on input, normalise to EL (canonical VIES form). Closes the #1 historical EU-VAT bug.browser_fetch (firefox133 TLS impersonation) before publish โ fixes 5 broken URLs caught in shipped India v1.2 and 3 in v2.0. web.archive.org snapshots 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.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).verhoeffValid and verhoeffCheckDigit exported from nationid/algorithms. Canonical Dโ
multiplication and permutation tables verbatim from Verhoeff (1969).nationid/catalog โ getCountryInfo, listCountries, countryName, flagEmoji. Backed by Intl.DisplayNames (CLDR), so any locale the runtime supports works out of the box โ not just es / en / pt.parse("MX_CURP", x).code now infers the literal "MX_CURP", not the 124-member union. extractDOB / extractSex / extractRegion constrain their first argument to the codes that actually encode each field. Country bundles expose literal country / defaultPersonal / defaultTax types.extract/pii/catalog subpaths' dependency on the root REGISTRY. nationid/extract alone drops -90% raw.MIGRATION.md ยง0: pii.mask now throws on unknown codes (symmetric with hash/lastN); package.json exports denies undocumented subpaths; CA_PASAPORTE and ES_PASAPORTE confidence demote high โ moderate (no first-party issuer spec to cite).tests/governance/confidence-citations.test.ts fails CI if any confidence: "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 nationid
Requires 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 / normalize for every supported countryextract (DOB, sex, region) where the document encodes itpii masking + SHA-256 hashing for safe display and storagei18n error messages in es, en, ptcatalog โ queryable document metadata for UI dropdownsSource 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; // 54
Each 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.
nationid for 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 | โ | โ | โ | โ |
extract + pii + i18n + catalog subpaths โ
high-confidence spec backed by a first-party citation (CI-enforced). 76% smaller tarball. Type inference narrowing for parse / getSpec / extract*. โ
nationid/catalog: names + flags + ISO alpha-3, locale param via Intl.DisplayNames (any BCP 47 tag). โ
python-stdnum. โ
python-stdnum/stdnum/sg/uen.py. โ
XI prefix + BG_EGN + CZ_RC (unlocks 10-digit BG and full CZ DIC branches).@nationid/react companion 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.