Brazilian document validation — CPF, CNPJ, CNH, CEP, CNS, PIS, IE (all 27 states), RENAVAM, Mercosul Plate, and Voter Title. PHP & TypeScript, identical API, zero production dependencies.
Validating Brazilian documents — CPF, CNPJ, IE — in production code accumulates silently: scattered regexes, copy-pasted checksum loops, and state-specific IE rules duplicated across the codebase. Each developer re-implements the same Mod-11 calculations, gets the Bahia dual-modulus branch wrong, and ships with no tests for edge cases like all-same-digit inputs or the CNPJ alfanumérico format.
Without this library:
// PHP — CPF validation copy-pasted from StackOverflow
function validateCpf(string $cpf): bool {
$cpf = preg_replace('/\D/', '', $cpf);
if (strlen($cpf) !== 11 || preg_match('/(\d)\1{10}/', $cpf)) return false;
// 30 lines of Mod-11 loops, weights hardcoded, DVs compared manually...
}With this library:
Identum::cpf('529.982.247-25')->validate(); // true — formatting stripped automatically
Identum::ie('343.173.196.450', StateEnum::SP)->validate(); // true — all 27 statesThe same API in TypeScript, identical output for identical input.
Use this library when you need to validate Brazilian document numbers: form submissions, API payloads, database entries, webhook data.
Don't use it as a source of business rules you can't inspect. All validation algorithms are open, documented, and unit-tested — so you can audit exactly what's being checked. If a government specification changes, open an issue.
- 10 document types — CPF, CNPJ (alphanumeric), CNH, CEP, CNS, PIS, IE (all 27 states), RENAVAM, Mercosul Plate, Voter Title
- PHP + TypeScript — same public API, same checksum logic, same sanitization rules, same output for the same input
- IE all 27 states — every state algorithm implemented, tested with edge cases in both packages
- Input sanitization by default —
'529.982.247-25'and'52998224725'both just work validateOrFail()— throws a typedValidationExceptioninstead of returningfalse- Blacklist / whitelist — force-accept or force-reject specific values, useful for test environments and exceptional business rules
- 100% line + branch coverage — Pest + Infection (PHP) · Vitest + Stryker (TypeScript)
- Zero production dependencies
| Package | Language | Install |
|---|---|---|
safeaccess/identum |
PHP 8.2+ | composer require safeaccess/identum |
@safeaccess/identum |
TypeScript (ESM) | npm install @safeaccess/identum |
Both packages expose the same public API surface and are tested for behavioral parity.
composer require safeaccess/identumRequirements: PHP 8.2+
npm install @safeaccess/identumRequirements: Node.js 22+
use SafeAccess\Identum\Identum;
use SafeAccess\Identum\Assets\IE\StateEnum;
use SafeAccess\Identum\Exceptions\ValidationException;
// All document types
Identum::cpf('529.982.247-25')->validate(); // true
Identum::cnpj('84.773.274/0001-03')->validate(); // true
Identum::cnpj('A0000000000032')->validate(); // true — alphanumeric CNPJ
Identum::cnh('22522791508')->validate(); // true
Identum::cep('78000-000')->validate(); // true
Identum::cns('100000000060018')->validate(); // true
Identum::pis('329.9506.158-9')->validate(); // true
Identum::ie('343.173.196.450', StateEnum::SP)->validate(); // true — all 27 states
Identum::renavam('60390908553')->validate(); // true
Identum::placa('ABC1D23')->validate(); // true — Mercosul format
Identum::tituloEleitor('123456781295')->validate(); // true
// Validate or throw
try {
Identum::cpf('000.000.000-00')->validateOrFail();
} catch (ValidationException $e) {
// handle invalid document
}
// Blacklist / whitelist
Identum::cpf('529.982.247-25')->blacklist(['529.982.247-25'])->validate(); // false
Identum::cpf('000.000.000-00')->whitelist(['000.000.000-00'])->validate(); // trueimport { Identum, StateEnum, ValidationException } from '@safeaccess/identum';
// All document types
Identum.cpf('529.982.247-25').validate(); // true
Identum.cnpj('84.773.274/0001-03').validate(); // true
Identum.cnpj('A0000000000032').validate(); // true — alphanumeric CNPJ
Identum.cnh('22522791508').validate(); // true
Identum.cep('78000-000').validate(); // true
Identum.cns('100000000060018').validate(); // true
Identum.pis('329.9506.158-9').validate(); // true
Identum.ie('343173196450', StateEnum.SP).validate(); // true — all 27 states
Identum.renavam('60390908553').validate(); // true
Identum.placa('ABC1D23').validate(); // true — Mercosul format
Identum.tituloEleitor('123456781295').validate(); // true
// Validate or throw
try {
Identum.cpf('000.000.000-00').validateOrFail();
} catch (e) {
if (e instanceof ValidationException) {
// handle invalid document
}
}
// Blacklist / whitelist
Identum.cpf('529.982.247-25').blacklist(['529.982.247-25']).validate(); // false
Identum.cpf('000.000.000-00').whitelist(['000.000.000-00']).validate(); // trueAll validator classes share the same fluent interface after construction:
| Method | PHP return | TS return | Description |
|---|---|---|---|
validate() |
bool |
boolean |
Returns true if valid, false otherwise |
validateOrFail() |
bool |
boolean |
Returns true if valid, throws ValidationException otherwise |
blacklist(string[]) |
static |
this |
Force-reject the given values regardless of checksum |
whitelist(string[]) |
static |
this |
Force-accept the given values regardless of checksum |
blacklist() and whitelist() are fluent and can be chained before validate() or validateOrFail().
| Document | Alias | PHP Class | TS Class |
|---|---|---|---|
| CPF | cpf |
CPFValidation |
CPFValidation |
| CNPJ | cnpj |
CNPJValidation |
CNPJValidation |
| CNH | cnh |
CNHValidation |
CNHValidation |
| CEP | cep |
CEPValidation |
CEPValidation |
| CNS | cns |
CNSValidation |
CNSValidation |
| PIS/PASEP | pis |
PISValidation |
PISValidation |
| IE | ie |
IEValidation |
IEValidation |
| RENAVAM | renavam |
RenavamValidation |
RenavamValidation |
| Mercosul Plate | placa |
PlateMercosulValidation |
PlateMercosulValidation |
| Voter Title | tituloEleitor |
VoterTitleValidation |
VoterTitleValidation |
use SafeAccess\Identum\Assets\IE\StateEnum;
Identum::ie('153189458', StateEnum::BA)->validate(); // true — Mod-10/11 dual
Identum::ie('7908930932562', StateEnum::MG)->validate(); // true — Mod-10 + Mod-11
Identum::ie('P199163724045', StateEnum::SP)->validate(); // true — rural (P prefix)import { Identum, StateEnum } from '@safeaccess/identum';
Identum.ie('153189458', StateEnum.BA).validate(); // true
Identum.ie('7908930932562', StateEnum.MG).validate(); // true
Identum.ie('P199163724045', StateEnum.SP).validate(); // trueAll 27 states are tested with valid inputs, invalid checksums, wrong lengths, wrong prefixes, and modulus edge cases.
The CNPJ format supports alphanumeric characters in addition to numeric-only (Receita Federal 2026 format):
Identum::cnpj('A0000000000032')->validate(); // true — alphanumeric CNPJIdentum.cnpj('A0000000000032').validate(); // trueUse the validator classes directly when you don't need the facade:
use SafeAccess\Identum\Assets\CPF\CPFValidation;
$validator = CPFValidation::make('529.982.247-25');
$validator->validate(); // trueimport { CPFValidation } from '@safeaccess/identum';
const validator = new CPFValidation('529.982.247-25');
validator.validate(); // trueSee CONTRIBUTING.md for development setup, commit conventions, and pull request guidelines.
See SECURITY.md for vulnerability reporting and the security policy.
MIT © Felipe Sauer