Skip to content
nif-dni-nie-cif-validation: Spanish NIF, DNI, NIE and CIF validationnif-dni-nie-cif-validation: Spanish NIF, DNI, NIE and CIF validation

Validate Spanish NIF, DNI, NIE and CIF numbers

Correct, fast, small and typed. Every rule cites its source in SPEC.md, and every error says why.

Install with npm
npm i nif-dni-nie-cif-validation yarn add nif-dni-nie-cif-validation pnpm add nif-dni-nie-cif-validation bun add nif-dni-nie-cif-validation

Try it

Type or paste a NIF, DNI, NIE or CIF. The library itself checks it as you type, and tells you why it is wrong.

Spaces, dots, hyphens and lower case are fine, as in 12.345.678-z.

The result appears here as you type.

The live validator needs JavaScript. The rest of this page works without it.

Runs entirely in your browser: no data leaves your browser.

Why this library

  • Correct, with sources

    24 rules, each with an ID (DNI-2, NIE-3, CIF-3…) and its documented basis in SPEC.md: an official source (the BOE, the AEAT), a convention labelled as such, or the library's contract. Every error names the rule that failed.

  • Fast

    At least 6.3× the throughput of every other library tested

    60.1 M ops/s on DNI (millions of validations per second, higher is faster), and 7.0× to 8.9× the throughput of v1.

  • Small

    929 B min+gzip for isValidNif, 4.7 kB for the whole library. Tree-shakable, and each language is its own import.

    Some single-purpose libraries are smaller for one validator (@maistik/validate-nif 456 B, @kreyo/nif-validator 533 B, spain-id 583 B and dni-js 882 B). Some of them validate fewer types: this one spends bytes on input normalization, the CIF-3 control types and K/L/M support.

  • 0 dependencies

    No runtime dependencies. Zod, Valibot and Yup are optional peer dependencies, needed only by their adapters.

  • Typed

    Written in TypeScript, with declarations for import and require. ES modules and CommonJS, compiled to ES2016.

  • Never throws

    Validators take unknown: null, numbers and objects give false, or NOT_A_STRING from validate(). Never a crash on user input.

  • 5 languages

    Error messages and organisation names in English, Spanish, Catalan, Basque and Galician. The validator above speaks the language of this page.

  • Generators and schemas

    Seeded test-data generators, and Zod, Valibot and Yup schemas that output the normalized value and the localized message.

Measured on AMD EPYC 9V74 80-Core Processor with Node.js v24.21.0 on 2 October 2026, each library with its default options. Ratios carry over between machines better than absolute numbers.

Use it

Boolean checks for a yes or no, validate() when you need to know why, opt-in entry points for schemas and test data, and a command line for terminals, scripts and AI agents.

Code examples
import { isValidNif } from "nif-dni-nie-cif-validation";
isValidNif("12345678Z"); // true
isValidNif(" 12.345.678-z "); // true: spaces, dots and hyphens are removed, and lower case becomes upper case
isValidNif("B12345675"); // false: wrong control digit
isValidNif(null); // false: never throws
import { validate } from "nif-dni-nie-cif-validation";
validate(" b-1234567-4 ");
// { valid: true, type: "CIF", normalized: "B12345674",
// meta: { orgKey: "B", orgDescription: "Limited liability company" } }
validate("12345678A").error;
// { code: "INVALID_CONTROL_CHARACTER", rule: "DNI-2", expected: "Z",
// message: 'The control character is not correct: for this DNI it should be "Z".' }
validate(" 12.345.678-z ").normalized; // "12345678Z": store this
import { z } from "zod";
import { zNif } from "nif-dni-nie-cif-validation/zod";
const schema = z.object({ nif: zNif({ types: ["DNI", "NIE"] }) });
schema.parse({ nif: " 12.345.678-z " }); // { nif: "12345678Z" }
schema.safeParse({ nif: "B12345674" }).success; // false: a CIF is not accepted here
import * as v from "valibot";
import { vNif } from "nif-dni-nie-cif-validation/valibot";
const schema = v.object({ nif: vNif({ types: ["DNI", "NIE"] }) });
v.parse(schema, { nif: " 12.345.678-z " }); // { nif: "12345678Z" }
v.safeParse(schema, { nif: "B12345674" }).success; // false: a CIF is not accepted here
import { object } from "yup";
import { yNif } from "nif-dni-nie-cif-validation/yup";
const schema = object({ nif: yNif({ types: ["DNI", "NIE"] }) });
schema.validateSync({ nif: " 12.345.678-z " }); // { nif: "12345678Z" }
schema.isValidSync({ nif: "B12345674" }); // false: a CIF is not accepted here
import { createGenerator, generateCif, generateDni, generateNie } from "nif-dni-nie-cif-validation/generate";
generateDni({ seed: 1 }); // "62707394X": the same on every run and platform
generateNie({ seed: 1, prefix: "Z" }); // "Z6270739R"
generateCif({ seed: 1, orgKey: "B" }); // "B62707393"
generateDni({ seed: 1, format: true }); // "62707394-X"
const gen = createGenerator(42); // a seeded stream: different values, the same on every run
gen.nif(); // "Y8524657C"
gen.nif(); // "X5265925Y"
Terminal window
npx nif-dni-nie-cif-validation validate 12345678Z B12345674 12345678A
# 12345678Z: valid DNI 12345678Z
# B12345674: valid CIF B12345674 (Limited liability company)
# 12345678A: invalid [DNI-2 INVALID_CONTROL_CHARACTER] The control character is not correct: for this DNI it should be "Z".
npx nif-dni-nie-cif-validation generate dni --count 2 --seed 42
# 60110375J
# 44829056D

npx downloads the package the first time. JSON output for scripts and agents, exit codes, test data and CSV checks are in the command line guide.

How it compares

Throughput on the DNI set, in millions of validations per second (M ops/s, higher is faster), and the size of one validator of any type (min+gzip, lower is smaller).

Throughput and size of the Spanish ID libraries on npm
LibraryDNI, M ops/sSize, any type
nif-dni-nie-cif-validation this build60.12929 B
@maistik/validate-nif 2.0.19.46456 B
dni-js 1.0.08.90882 B
nif-dni-nie-cif-validation 1.0.118.591,517 B
better-dni 4.4.28.441,134 B
spain-id 1.1.146.96583 B
validator.js isIdentityCard(x, "ES") 13.15.355.732,539 B
validator.js isTaxID(x, "es-ES") 13.15.354.226,537 B
@kreyo/nif-validator 0.1.02.78533 B
jsvat (Spain) 2.5.42.101,186 B
stdnum 1.12.60.4151,063 B

Every number comes from bench/results/latest.json. Speed is not everything: the benchmarks page links to the full results, which also compare agreement with SPEC.md and features.

Full benchmarks and comparison →