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).
Requires
- php: ^8.3
- ext-mbstring: *
Requires (Dev)
- lumensistemas/lens: ^1.0
- pestphp/pest: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
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.
isValidaccepts any input type and returnsfalsefor non-string or malformed values. Callers can pass user input directly withouttry/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.CepandPlacaare 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, andformatalways 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 isisValid()'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.