lumensistemas/br-validation

Validators, generators, and formatters for Brazilian identifiers (CPF, CNPJ, PIS, Título de Eleitor, CNH, Renavam, placa de veículo, CNS, chave PIX, CEP, telefone, boleto bancário, and NF-e access key).

Maintainers

Package info

github.com/lumensistemas/br-validation

pkg:composer/lumensistemas/br-validation

Statistics

Installs: 31

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-05-17 01:22 UTC

This package is auto-updated.

Last update: 2026-05-17 12:07:56 UTC


README

Tests Latest Stable Version Total Downloads

Validators, generators, and formatters for Brazilian identifiers (CPF, CNPJ, PIS, Título de Eleitor, CNH, Renavam, placa de veículo, CNS, chave PIX, CEP, telefone, boleto bancário, and NF-e access key) in PHP. Framework-agnostic and dependency-free at runtime.

Requirements

  • PHP 8.3 or newer
  • ext-mbstring

Installation

composer require lumensistemas/br-validation

Usage

CPF

use LumenSistemas\BrValidation\Cpf;

Cpf::isValid('856.981.040-77');   // true
Cpf::isValid('85698104077');      // true (raw form also accepted)
Cpf::isValid('11111111111');      // false (all-equal sequence)
Cpf::isValid(85698104077);        // false (non-string input)

Cpf::format('85698104077');       // '856.981.040-77'
Cpf::normalize('856.981.040-77'); // '85698104077'

Cpf::generate(); // a valid 11-digit CPF

CNPJ

use LumenSistemas\BrValidation\Cnpj;

Cnpj::isValid('11.222.333/0001-81');      // true (legacy numeric)
Cnpj::isValid('12ABC34501DE35');          // true (alphanumeric, 2026 rules)
Cnpj::isValid('12abc34501de35');          // true (case-insensitive)
Cnpj::isValid('00000000000000');          // false (all-equal sequence)

Cnpj::format('11222333000181');           // '11.222.333/0001-81'
Cnpj::format('12abc34501de35');           // '12.ABC.345/01DE-35'
Cnpj::normalize(' 11.222.333/0001-81 '); // '11222333000181'

Cnpj::generateNumeric();      // a valid 14-digit numeric CNPJ
Cnpj::generateAlphanumeric(); // a valid alphanumeric CNPJ

PIS / PASEP / NIS / NIT

use LumenSistemas\BrValidation\Pis;

Pis::isValid('120.65328.70-5'); // true
Pis::isValid('12065328705');    // true (raw form also accepted)
Pis::isValid('00000000000');    // false (all-equal sequence)
Pis::isValid(12065328705);      // false (non-string input)

Pis::format('12065328705');     // '120.65328.70-5'
Pis::normalize('120.65328.70-5'); // '12065328705'

Pis::generate(); // a valid 11-digit PIS

The same 11-digit number is issued under four different government program names — PIS, PASEP, NIS, NIT — and shares a single mod-11 check digit. Pis validates any of them.

Título de Eleitor

use LumenSistemas\BrValidation\TituloEleitor;

TituloEleitor::isValid('1234 5678 0396'); // true (RJ)
TituloEleitor::isValid('123456780396');   // true (raw form also accepted)
TituloEleitor::isValid('123456789905');   // false (UF 99 is not a TSE code)
TituloEleitor::isValid('111111111111');   // false (all-equal sequence)

TituloEleitor::format('123456780396');    // '1234 5678 0396'
TituloEleitor::normalize('1234 5678 0396'); // '123456780396'

TituloEleitor::generate(); // a valid 12-digit título

The UF code in positions 9–10 is the TSE's own numbering (01..28), not the IBGE UF code. São Paulo (01) and Minas Gerais (02) follow a special rule: whenever a check-digit calculation yields remainder 0, the digit is bumped to 1.

CNH

use LumenSistemas\BrValidation\Cnh;

Cnh::isValid('98765432109');         // true
Cnh::isValid('123.456.789-00');      // true (mask characters are stripped)
Cnh::isValid('12345678901');         // false (wrong check digits)
Cnh::isValid('11111111111');         // false (all-equal sequence)

Cnh::normalize(' 123.456.789-00 ');  // '12345678900'

Cnh::generate(); // a valid 11-digit CNH

The CNH número de registro has no canonical visual mask on the document, so no format() method is exposed — call normalize() for the 11-digit storage/display form.

Renavam

use LumenSistemas\BrValidation\Renavam;

Renavam::isValid('01234567897');     // true
Renavam::isValid('98765432103');     // true
Renavam::isValid('00000000000');     // false (all-equal sequence)
Renavam::isValid(1234567897);        // false (non-string input)

Renavam::normalize(' 01234567897 '); // '01234567897'

Renavam::generate(); // a valid 11-digit Renavam

Like CNH, Renavam has no canonical visual mask on the CRLV, so no format() method is exposed — call normalize() for the 11-digit storage/display form. Pre-2007 nine-digit Renavams must be left-padded with zeros by the caller before validation.

Placa de veículo

use LumenSistemas\BrValidation\Placa;

Placa::isValid('ABC-1234'); // true (old format)
Placa::isValid('abc1234');  // true (case-insensitive)
Placa::isValid('ABC1D23');  // true (Mercosul format)
Placa::isValid('ABCD123');  // false (wrong shape)

Placa::format('ABC1234');   // 'ABC-1234'
Placa::format('abc1d23');   // 'ABC1D23'
Placa::normalize(' abc-1234 '); // 'ABC1234'

Placa::generateOld();       // a valid old-format placa
Placa::generateMercosul();  // a valid Mercosul placa

Old and Mercosul plates coexist on the road indefinitely (vehicles only switch to Mercosul on first registration or transfer), so this is an accept-both library, not a transitional one. Placas carry no check digit; only the shape is validated.

CNS (Cartão Nacional de Saúde)

use LumenSistemas\BrValidation\Cns;

Cns::isValid('120 6532 8705 0007'); // true (definitive)
Cns::isValid('900000000000008');    // true (provisional, starts with 9)
Cns::isValid('320653287050007');    // false (first digit must be 1, 2, 7, 8, or 9)
Cns::isValid('100000000060026');    // false (definitive with non-000/001 appendix)

Cns::format('120653287050007');     // '120 6532 8705 0007'
Cns::normalize('120 6532 8705 0007'); // '120653287050007'

Cns::generateDefinitive();   // a valid definitive CNS (first digit 1 or 2)
Cns::generateProvisional();  // a valid provisional CNS (first digit 7, 8, or 9)

Two structural shapes share one mod-11 check: definitive cards (first digit 1 or 2) carry the citizen's PIS in positions 1–11 and a 000/001 appendix in positions 12–14; provisional cards (first digit 7, 8, or 9) carry no internal structure. The validator enforces the appendix pattern for definitive cards so that arithmetically-conforming numbers the SUS would never issue are still rejected.

Chave PIX

use LumenSistemas\BrValidation\Pix;

Pix::isValid('85698104077');                              // true (CPF key)
Pix::isValid('user@example.com');                         // true (email key)
Pix::isValid('+5511987654321');                           // true (phone key)
Pix::isValid('123e4567-e89b-42d3-a456-426614174000');     // true (EVP key)
Pix::isValid('11.222.333/0001-81');                       // true (CNPJ key)
Pix::isValid('not-a-key');                                // false

Pix::type('user@example.com');                            // 'email'
Pix::type('85698104077');                                 // 'cpf'

Pix::normalize('Test.User@Example.COM');                  // 'test.user@example.com'
Pix::normalize('856.981.040-77');                         // '85698104077'

Pix::generateEvp(); // a valid UUID v4 EVP key

Pix is a thin dispatcher over the five chave PIX shapes (CPF, CNPJ, e-mail, E.164 phone starting with +55, and UUID v4 EVP); CPF and CNPJ delegate to the existing validators. No format() method is exposed because the canonical display form depends on the type — use the type-specific Cpf::format() / Cnpj::format() when you need user-facing display.

CEP

use LumenSistemas\BrValidation\Cep;

Cep::isValid('01310-100');     // true
Cep::isValid('01310100');      // true (raw form also accepted)
Cep::isValid('0131010');       // false (too short)

Cep::format('01310100');       // '01310-100'
Cep::normalize('01310-100');   // '01310100'

Cep::generate(); // a shape-valid 8-digit CEP (not guaranteed to exist)

CEP has no check digit — this is shape validation only. Whether a given CEP corresponds to a real address requires a Correios lookup and is out of scope. All-equal sequences like 00000000 therefore pass; callers that need existence checks should integrate a lookup service separately.

Phone

use LumenSistemas\BrValidation\Phone;

Phone::isValid('(11) 98765-4321');  // true (mobile)
Phone::isValid('11987654321');      // true (mobile, raw)
Phone::isValid('(11) 3333-4444');   // true (landline)
Phone::isValid('+5511987654321');   // true (E.164)
Phone::isValid('11187654321');      // false (mobile must start with 9)

Phone::format('11987654321');       // '(11) 98765-4321'
Phone::format('1133334444');        // '(11) 3333-4444'
Phone::formatE164('11987654321');   // '+5511987654321'
Phone::normalize('+5511987654321'); // '11987654321'

Phone::generateMobile();    // a valid 11-digit mobile
Phone::generateLandline();  // a valid 10-digit landline

Enforces the post-2017 ANATEL mobile-9 mandate: 11-digit numbers must have a 9 as the first subscriber digit; 10-digit landlines must not. DDD is checked against the 11..99 range with no 0 in either position; semantic DDD allocation (which DDDs are actually issued by ANATEL) is left to callers, since the allocation list shifts over time.

Boleto bancário

use LumenSistemas\BrValidation\Boleto;

Boleto::isValid('00190.12343 56789.012343 56789.012343 1 99990000010000'); // true (linha digitável)
Boleto::isValid('00190123435678901234356789012343199990000010000');       // true (raw)
Boleto::isValid('00191999900000100000123456789012345678901234');          // true (44-digit barcode)
Boleto::isValid('00191123435678901234356789012343199990000010000');       // false (wrong field-1 DV)

Boleto::format('00190123435678901234356789012343199990000010000');
// '00190.12343 56789.012343 56789.012343 1 99990000010000'

Boleto::normalize('00190.12343 56789.012343 56789.012343 1 99990000010000');
// '00190123435678901234356789012343199990000010000'

Boleto::generate(); // a valid 47-digit linha digitável

Boleto::isValid accepts either the 47-digit linha digitável or the 44-digit código de barras and dispatches by length. A linha digitável is valid iff all three of its mod-10 field DVs match AND the barcode it reconstructs to passes the mod-11 general DV check. Targets boletos bancários only; concessionária slips (48-digit utility shape) are out of scope.

NF-e access key (chave de acesso)

use LumenSistemas\BrValidation\Nfe;

Nfe::isValid('35240111222333000181550010000000011123456788'); // true
Nfe::isValid('3524 0111 2223 3300 0181 5500 1000 0000 0111 2345 6788'); // true (masked)
Nfe::isValid('00000000000000000000000000000000000000000000');           // false (all-equal sequence)

Nfe::format('35240111222333000181550010000000011123456788');
// '3524 0111 2223 3300 0181 5500 1000 0000 0111 2345 6788'

Nfe::normalize('3524 0111 2223 3300 0181 5500 1000 0000 0111 2345 6788');
// '35240111222333000181550010000000011123456788'

Nfe::generate(); // a valid 44-digit access key

The same 44-digit shape and check-digit algorithm cover NF-e (modelo 55), NFC-e (modelo 65) and the broader SEFAZ document family (CT-e, MDF-e, BP-e). This validator does not constrain modelo.

Behavior

  • Validation never throws. isValid accepts any input type and returns false for non-string or malformed values. Callers can pass user input directly without try/catch.
  • All-equal sequences are rejected by validators with a checksum (11111111111, 00000000000000, …) even though some pass the underlying mod-11 algorithm — they are conventional placeholder values across the Brazilian validation ecosystem and never represent real identifiers. Cep and Placa are exceptions: neither carries a checksum to reject them against, so any shape-valid input is accepted and existence checks are left to the caller (Correios lookup for CEP, DETRAN for placa).
  • CNPJ is case-insensitive. Letters in alphanumeric CNPJs are normalized to uppercase before validation and formatting; both '12abc34501de35' and '12ABC34501DE35' validate equivalently, and format always produces the canonical uppercase masked form.
  • Numeric CNPJs remain valid in perpetuity alongside the 2026 alphanumeric format. This is an accept-both library, not a transitional one.
  • format() is tolerant. When the input shape doesn't match the canonical form, format() returns the input unchanged rather than raising. It does not validate check digits — that is isValid()'s job.

Laravel integration

A companion package lumensistemas/laravel-br-validation is planned, providing Rule classes and a service provider. It will be linked here once published.

Development

composer install
composer test

License

MIT. See LICENSE.