Open Source · Library Published

mx-identifiers: Validate and Generate Mexican RFC, CURP, CLABE and NSS

Zero-dependency TypeScript npm library that validates and generates Mexican RFC, CURP, CLABE and NSS, with check digits verified against public vectors. Node and browser.

Role
Design & Development
Year
2026
Published
Cover of mx-identifiers with the npm i mx-identifiers command, a validateRfc result showing valid true, birth date and expected check digit, and RFC, CURP, CLABE and NSS badges on a dark background.

mx-identifiers is a TypeScript npm library that validates and generates the four identifiers a Mexican invoicing, payroll or onboarding form ends up needing: RFC (tax ID), CURP (population registry ID), CLABE (18-digit bank account number) and NSS (social security number), with check digits verified against public vectors. One package instead of four, zero dependencies, the same code in Node and in the browser, MIT licensed.

npm install mx-identifiers

Why it exists

npm has a package for CLABE, another for CURP and almost nothing covering all four, let alone one that also generates valid test data. This is the module behind the Mexican ID validator and the test data generator on this site, extracted as-is. It is also the arithmetic the Mexican tax MCP server runs on. If you want the maths rather than the package, the algorithms are worked through here.

Usage

import { validateRfc, validateClabe, isCurp, generatePerson } from 'mx-identifiers';

validateRfc('GODE561231GR8');
// { valid: true, normalized: 'GODE561231GR8', kind: 'fisica',
//   parts: { iniciales: 'GODE', fecha: '561231', homoclave: 'GR', digito: '8' },
//   birthDate: '1956-12-31', expectedCheckDigit: '8', errors: [] }

validateClabe('002010077777777771').bankName; // 'Banamex'
isCurp('not-a-curp');                         // false, never throws

generatePerson(); // fictional person: RFC and CURP derive from the same name and birth date

Validators never throw. They return an object with valid, the identifier’s pieces (parts), the check digit the algorithm expected (expectedCheckDigit) and an errors array of stable, language-neutral codes (empty, length, shape, digits, date, state, inconvenient, checksum) that you map to your own copy. When all you want is a boolean there are isRfc, isCurp, isClabe and isNss. Field names such as iniciales or homoclave are the Spanish terms the identifiers are made of; the API itself is in English.

What is in the box

  • Validation: validateRfc (13-character individuals and 12-character companies), validateCurp (18 characters, resolves state and sex), validateClabe (18 digits, resolves the bank) and validateNss (11 digits, Luhn). Input is normalized first: uppercased, with spaces, hyphens, dots and underscores stripped.
  • Test data generation: generatePerson, generateCompany, buildRfcFisica, buildRfcMoral, buildCurp, buildClabe and buildNss. They apply the public naming rules: strip accents, map Ñ to X, skip particles (de, la, del…), use the second given name when the first is José or María, and replace the second letter with X when the initials spell a word RENAPO forbids.
  • Catalogs: CURP_STATES, CLABE_BANKS, INCONVENIENT_WORDS and the CFDI 4.0 invoice catalogs (CFDI_REGIMEN, CFDI_USO, CFDI_FORMA_PAGO…), handy for driving a <select>. The bank list follows Banxico’s listing of 6 October 2026; the CFDI ones were checked against the SAT’s catCFDI spreadsheet.
  • Two things a hand-rolled implementation tends to get wrong: the SAT’s generic RFC XAXX010101000 must be allowlisted explicitly or you reject what every consumer invoice in Mexico carries, and the CLABE check digit keeps the last digit of each product (63 counts as 3), not the digit sum as Luhn would (which would give 9).
  • Tested and published: 173 tests that run against the built package (both the ESM and CommonJS entry points), with vectors from the SAT, python-stdnum and Banxico; CI on Node 18, 20, 22 and 24; published on npm with verifiable provenance.

What it does not do

  • Valid does not mean it exists. It checks structure, date and check digit; it does not query the SAT, RENAPO, IMSS or Banxico. To find out whether an invoice is still live, see the MCP server.
  • Generated data is fictional. It passes any format and check-digit validation but belongs to no real person or record. The RFC homoclave is random, so results are self-consistent, not a prediction of what the SAT would issue. Use it for development databases and fixtures, never to fill official filings.
  • Not reproducible yet. Generation uses Math.random(); seeded generation is on the roadmap.
  • The bank catalog is not Banxico’s full list: a code that is not in it is reported as unknown (bankName: null) rather than given an invented name, and it does not invalidate the CLABE.

Tech stack

  • TypeScript, strict, built with tsup to ESM and CommonJS with type declarations
  • Node.js ≥ 18 and browsers, no Node APIs
  • Zero runtime dependencies
  • node:test for the suite
  • GitHub Actions for CI and publishing

Repository

View on GitHub → · Package on npm →

Goal

So nobody has to rewrite, again and with a bug in the CLABE, the algorithms behind four public identifiers. One library, with the SAT, Banxico and RENAPO vectors as proof, equally good for validating a form and for seeding a development database.

07 / Contact

Tell me what's getting stuck in your business I reply within 24 hours.

Fill out the form and I'll reply within 24 hours. You can also email me directly: