Utils library for Brazilian-specific businesses.
Erlang
Build
Requires Erlang/OTP 25 or newer (tested on OTP 25, 26 and 27).
rebar3 compile
Generate the API documentation with:
rebar3 edoc
Usage
All functions are available through the brutils facade module and operate on
UTF-8 binaries:
1> brutils:is_valid_cpf(<<"82178537464">>).
true
Each utility also lives in its own module (brutils_cpf, ...) with the
unsuffixed names (brutils_cpf:is_valid/1), if you prefer to depend on the
domain module directly.
Utilities
CPF
is_valid_cpf
Returns whether the verifying checksum digits of the given CPF (Brazilian Individual Taxpayer Number) match its base number. This function does not verify the existence of the CPF; it only validates the format of the string.
Args:
Cpf(term()): the CPF to be validated, an 11-digit binary. Any other term returnsfalse— the function never raises.
Returns:
boolean():trueif the checksum digits match the base number,falseotherwise. Formatting symbols are not stripped, so a formatted CPF must be cleaned withremove_symbols_cpf/1first.
Example:
1> brutils:is_valid_cpf(<<"82178537464">>).
true
2> brutils:is_valid_cpf(<<"00011122233">>).
false
format_cpf
Formats a valid CPF for display, adding the standard visual aid symbols
(XXX.XXX.XXX-XX).
Args:
Cpf(binary()): a numbers-only CPF binary.
Returns:
{ok, Formatted}with the formatted CPF, or{error, invalid}if the input is not a valid CPF.
Example:
1> brutils:format_cpf(<<"82178537464">>).
{ok,<<"821.785.374-64">>}
2> brutils:format_cpf(<<"00011122233">>).
{error,invalid}
remove_symbols_cpf
Removes the formatting symbols . and - from a CPF string. Only those two
characters are removed; anything else is kept unchanged.
Args:
Cpf(binary()): the CPF binary containing symbols to be removed.
Returns:
binary(): a new binary with the specified symbols removed.
Example:
1> brutils:remove_symbols_cpf(<<"000.111.222-33">>).
<<"00011122233">>
generate_cpf
Generates a random valid CPF as a numbers-only binary.
Returns:
binary(): a random valid 11-digit CPF.
Example:
1> brutils:generate_cpf().
<<"44635843700">>
2> brutils:generate_cpf().
<<"06854668417">>
CNPJ
is_valid_cnpj
Returns whether the verifying checksum digits of the given CNPJ (Brazilian Company Registration Number) match its base number. Both the numeric format and the 2026 alphanumeric format (digits and uppercase letters in the first 12 characters) are supported. This function does not verify the existence of the CNPJ; it only validates the format of the string.
Args:
Cnpj(term()): the CNPJ to be validated, a 14-character binary. Any other term returnsfalse— the function never raises.
Returns:
boolean():trueif the checksum digits match the base number,falseotherwise. Lowercase letters are invalid, and formatting symbols are not stripped — clean the input withremove_symbols_cnpj/1first.
Example:
1> brutils:is_valid_cnpj(<<"03560714000142">>).
true
2> brutils:is_valid_cnpj(<<"00111222000133">>).
false
3> brutils:is_valid_cnpj(<<"6Q4E392H000190">>).
true
format_cnpj
Formats a valid CNPJ for display, adding the standard visual aid symbols
(XX.XXX.XXX/XXXX-XX).
Args:
Cnpj(binary()): a symbols-free CNPJ binary, numeric or alphanumeric.
Returns:
{ok, Formatted}with the formatted CNPJ, or{error, invalid}if the input is not a valid CNPJ.
Example:
1> brutils:format_cnpj(<<"03560714000142">>).
{ok,<<"03.560.714/0001-42">>}
2> brutils:format_cnpj(<<"00111222000133">>).
{error,invalid}
remove_symbols_cnpj
Removes the formatting symbols ., / and - from a CNPJ string. Only
those three characters are removed; anything else is kept unchanged.
Args:
Cnpj(binary()): the CNPJ binary containing symbols to be removed.
Returns:
binary(): a new binary with the specified symbols removed.
Example:
1> brutils:remove_symbols_cnpj(<<"03.560.714/0001-42">>).
<<"03560714000142">>
generate_cnpj
Generates a random valid CNPJ. An optional branch number can be given
(non-negative integer or digits-only binary; defaults to 1), and an optional
alphanumeric flag switches to the 2026 alphanumeric format, where the branch
may also contain uppercase letters and invalid branches are replaced by
0001.
Returns:
binary(): a random valid 14-character CNPJ.
Example:
1> brutils:generate_cnpj().
<<"95427181000143">>
2> brutils:generate_cnpj(1234).
<<"44122762123483">>
3> brutils:generate_cnpj(<<"AB12">>, true).
<<"X8641237AB1272">>
PIS
is_valid_pis
Returns whether the verifying check digit of the given PIS/PASEP (Brazilian social integration program number) matches its base number. This function does not verify the existence of the PIS; it only validates the format of the string.
Args:
Pis(term()): the PIS to be validated, an 11-digit binary. Any other term returnsfalse— the function never raises.
Returns:
boolean():trueif the check digit matches the base number,falseotherwise. PIS reserves no repeated-digit sequences — notably,<<"00000000000">>has a matching check digit and is valid. Formatting symbols are not stripped — clean the input withremove_symbols_pis/1first.
Example:
1> brutils:is_valid_pis(<<"12056798818">>).
true
2> brutils:is_valid_pis(<<"12056798810">>).
false
3> brutils:is_valid_pis(<<"00000000000">>).
true
format_pis
Formats a valid PIS for display, adding the standard visual aid symbols
(NNN.NNNNN.NN-N).
Args:
Pis(binary()): a numbers-only PIS binary.
Returns:
{ok, Formatted}with the formatted PIS, or{error, invalid}if the input is not a valid PIS.
Example:
1> brutils:format_pis(<<"12056798818">>).
{ok,<<"120.56798.81-8">>}
2> brutils:format_pis(<<"12056798810">>).
{error,invalid}
remove_symbols_pis
Removes the formatting symbols . and - from a PIS string. Only those two
characters are removed; anything else is kept unchanged.
Args:
Pis(binary()): the PIS binary containing symbols to be removed.
Returns:
binary(): a new binary with the specified symbols removed.
Example:
1> brutils:remove_symbols_pis(<<"120.56798.81-8">>).
<<"12056798818">>
generate_pis
Generates a random valid PIS as a numbers-only binary. The base is drawn uniformly with zero included, so results may start with zeros.
Returns:
binary(): a random valid 11-digit PIS.
Example:
1> brutils:generate_pis().
<<"99360519414">>
2> brutils:generate_pis().
<<"95319303914">>
CNH
is_valid_cnh
Returns whether the given CNH (Brazilian driver's license registration number, 2022 layout) is valid: after stripping every non-digit character, exactly 11 digits must remain and both verifying check digits must match the base number. Earlier CNH layouts are not supported. This function does not verify the existence of the CNH; it only validates the format of the string.
Unlike the CPF/CNPJ/PIS validators, symbols do not need to be removed first:
formatted input such as <<"987654321-00">> is accepted, and letters are
stripped rather than rejected.
Args:
Cnh(term()): the CNH to be validated. Any non-binary term returnsfalse— the function never raises.
Returns:
boolean():trueif 11 digits remain after stripping and the check digits match,falseotherwise.
Example:
1> brutils:is_valid_cnh(<<"98765432100">>).
true
2> brutils:is_valid_cnh(<<"987654321-00">>).
true
3> brutils:is_valid_cnh(<<"12345678901">>).
false
4> brutils:is_valid_cnh(<<"A2C45678901">>).
false
RENAVAM
is_valid_renavam
Returns whether the given RENAVAM (Brazilian vehicle registration number) is valid: exactly 11 digits whose verifying check digit matches the base number. This function does not verify the existence of the RENAVAM; it only validates the format of the string.
Unlike is_valid_cnh, symbols are not stripped: any non-digit character
(space, dash, letter) makes the input invalid.
Args:
Renavam(term()): the RENAVAM to be validated, an 11-digit binary. Any other term returnsfalse— the function never raises.
Returns:
boolean():trueif the check digit matches the base number,falseotherwise.
Example:
1> brutils:is_valid_renavam(<<"86769597308">>).
true
2> brutils:is_valid_renavam(<<"12345678901">>).
false
3> brutils:is_valid_renavam(<<"867695973-08">>).
false
4> brutils:is_valid_renavam(<<"12345678 901">>).
false
CEP
A CEP (Brazilian postal code) has no check digit: validation is purely structural — exactly 8 digits — and says nothing about whether the postal code actually exists. Address lookup functions (via the ViaCEP API) are planned for a future release.
is_valid_cep
Returns whether the given CEP is valid: a binary of exactly 8 digits. Any
8-digit sequence is structurally valid, including repeated ones like
<<"00000000">>.
Args:
Cep(term()): the CEP to be validated, an 8-digit binary. Any other term returnsfalse— the function never raises.
Returns:
boolean():trueif the input is exactly 8 digits,falseotherwise. Formatting symbols are not stripped — clean the input withremove_symbols_cep/1first.
Example:
1> brutils:is_valid_cep(<<"01310200">>).
true
2> brutils:is_valid_cep(<<"1310200">>).
false
3> brutils:is_valid_cep(<<"01310-200">>).
false
format_cep
Formats a valid CEP for display, adding the standard dash (NNNNN-NNN).
Args:
Cep(binary()): a numbers-only CEP binary.
Returns:
{ok, Formatted}with the formatted CEP, or{error, invalid}if the input is not a valid CEP.
Example:
1> brutils:format_cep(<<"01310200">>).
{ok,<<"01310-200">>}
2> brutils:format_cep(<<"1234567">>).
{error,invalid}
remove_symbols_cep
Removes the formatting symbols . and - from a CEP string. Only those two
characters are removed; anything else is kept unchanged.
Args:
Cep(binary()): the CEP binary containing symbols to be removed.
Returns:
binary(): a new binary with the specified symbols removed.
Example:
1> brutils:remove_symbols_cep(<<"01310-200">>).
<<"01310200">>
generate_cep
Generates a random CEP as a numbers-only binary. Each digit is drawn independently, so results may start with zeros.
Returns:
binary(): a random 8-digit CEP.
Example:
1> brutils:generate_cep().
<<"22648357">>
2> brutils:generate_cep().
<<"98885103">>
Phone
Brazilian phone numbers are handled without the +55 country code and with the two-digit DDD (area code) included. Two shapes exist:
| Type | Digits | Shape |
|---|---|---|
| mobile | 11 | DDD (1–9 each) + 9 + 8 digits |
| landline | 10 | DDD (1–9 each) + one digit 2–5 + 7 digits |
is_valid_phone
Returns whether the given phone number is valid — either shape for the
one-argument form, or a specific one when the type atom (mobile or
landline) is given. It does not verify that the number actually exists.
Symbols are not stripped — clean the input with remove_symbols_phone/1
first.
Args:
Phone(term()): the phone number to be validated, digits only. Any non-binary term returnsfalse— the function never raises.Type(mobile | landline, optional): restricts the check to one shape. Any other value raises.
Returns:
boolean():trueif the number matches the (requested) shape,falseotherwise.
Example:
1> brutils:is_valid_phone(<<"11994029275">>).
true
2> brutils:is_valid_phone(<<"1635014415">>).
true
3> brutils:is_valid_phone(<<"11994029275">>, mobile).
true
4> brutils:is_valid_phone(<<"11994029275">>, landline).
false
format_phone
Formats a valid phone number for display: DDD in parentheses (no space after them) and a dash before the last four digits.
Args:
Phone(binary()): a digits-only phone number.
Returns:
{ok, Formatted}with the formatted number, or{error, invalid}if the input is not a valid phone number.
Example:
1> brutils:format_phone(<<"11994029275">>).
{ok,<<"(11)99402-9275">>}
2> brutils:format_phone(<<"1635014415">>).
{ok,<<"(16)3501-4415">>}
remove_symbols_phone
Removes common phone punctuation from a string: (, ), -, + and
spaces. Dots are NOT removed.
Args:
Phone(binary()): the phone number containing symbols to be removed.
Returns:
binary(): a new binary with the specified symbols removed.
Example:
1> brutils:remove_symbols_phone(<<"+55 (11) 99402-9275">>).
<<"5511994029275">>
remove_international_dialing_code
Removes the Brazilian international dialing code (55) from a phone
number: if the input contains 55 and is longer than 11 characters
(ignoring spaces), the first occurrence of 55 is removed; otherwise the
input is returned unchanged. Note the sharp edges: a leading + is kept,
and the removed 55 is the first occurrence wherever it sits.
Args:
Phone(binary()): the phone number, possibly with the dialing code.
Returns:
binary(): the number without the dialing code, or unchanged.
Example:
1> brutils:remove_international_dialing_code(<<"5511994029275">>).
<<"11994029275">>
2> brutils:remove_international_dialing_code(<<"+5511994029275">>).
<<"+11994029275">>
generate_phone
Generates a random valid phone number — of a random type with no argument,
or of the given type (mobile or landline).
Returns:
binary(): a random valid phone number.
Example:
1> brutils:generate_phone().
<<"38950126454">>
2> brutils:generate_phone(mobile).
<<"59956385883">>
3> brutils:generate_phone(landline).
<<"7529936607">>
Passport
A Brazilian passport number has 2 uppercase letters followed by 6 digits.
There is no check digit, so validity says nothing about existence. Note the
division of labor: is_valid_passport is strict (case-sensitive, no
symbol stripping), while format_passport is lenient — it uppercases and
strips symbols before validating, so the same input can fail one and pass
the other.
is_valid_passport
Returns whether the given passport number is valid: exactly 2 uppercase
letters followed by exactly 6 digits. Lowercase letters and symbols make
the input invalid — use format_passport/1 to normalize first.
Args:
Passport(term()): the passport number to be validated. Any non-binary term returnsfalse— the function never raises.
Returns:
boolean():trueif the input matches the shape,falseotherwise.
Example:
1> brutils:is_valid_passport(<<"AB123456">>).
true
2> brutils:is_valid_passport(<<"Ab123456">>).
false
3> brutils:is_valid_passport(<<"AB-123456">>).
false
format_passport
Normalizes and formats a passport number: uppercases ASCII letters, strips
the symbols -, . and spaces, then validates the result.
Args:
Passport(binary()): a passport number, possibly lowercase or with symbols.
Returns:
{ok, Formatted}with the normalized uppercase passport, or{error, invalid}if the input does not normalize into a valid one.
Example:
1> brutils:format_passport(<<"ab-123456">>).
{ok,<<"AB123456">>}
2> brutils:format_passport(<<"111111">>).
{error,invalid}
remove_symbols_passport
Removes the symbols -, . and spaces from a passport string. Only those
three characters are removed, and letter case is preserved.
Args:
Passport(binary()): the passport string containing symbols to be removed.
Returns:
binary(): a new binary with the specified symbols removed.
Example:
1> brutils:remove_symbols_passport(<<"Ab -. 123456">>).
<<"Ab123456">>
generate_passport
Generates a random valid passport number: 2 uniform uppercase letters followed by 6 uniform digits.
Returns:
binary(): a random valid passport number.
Example:
1> brutils:generate_passport().
<<"AP051847">>
2> brutils:generate_passport().
<<"ZN446187">>
License plate
Two Brazilian plate patterns exist:
| Type atom | Pattern | Example |
|---|---|---|
old_format | LLLNNNN — 3 letters + 4 digits | ABC1234 |
mercosul | LLLNLNN — 3 letters, digit, letter, 2 digits | ABC1D23 |
Validation ignores letter case and trims surrounding whitespace; formatting and conversion emit uppercase.
is_valid_license_plate
Returns whether the given plate is valid — either pattern for the one-argument form, or a specific one when the type atom is given.
Args:
Plate(term()): the plate to be validated. Any non-binary term returnsfalse— the function never raises.Type(old_format | mercosul, optional): restricts the check to one pattern. Any other value raises.
Returns:
boolean():trueif the plate matches the (requested) pattern.
Example:
1> brutils:is_valid_license_plate(<<"ABC1234">>).
true
2> brutils:is_valid_license_plate(<<"abc1d23">>).
true
3> brutils:is_valid_license_plate(<<"ABC-1234">>).
false
4> brutils:is_valid_license_plate(<<"ABC1234">>, mercosul).
false
format_license_plate
Formats a valid plate for display: old-format plates get a dash after the letters, Mercosul plates come out bare — both uppercased.
Args:
Plate(binary()): a license plate, any letter case.
Returns:
{ok, Formatted}with the formatted plate, or{error, invalid}if the input matches neither pattern.
Example:
1> brutils:format_license_plate(<<"abc1234">>).
{ok,<<"ABC-1234">>}
2> brutils:format_license_plate(<<"abc1e34">>).
{ok,<<"ABC1E34">>}
remove_symbols_license_plate
Removes the dash (-) from a license plate string. Only the dash is
removed; anything else is kept unchanged.
Args:
Plate(binary()): the plate string containing dashes to be removed.
Returns:
binary(): a new binary with the dashes removed.
Example:
1> brutils:remove_symbols_license_plate(<<"ABC-123">>).
<<"ABC123">>
convert_license_plate_to_mercosul
Converts an old-format plate to the Mercosul pattern by replacing the digit
at the fifth position with a letter (0→A, 1→B, ... 9→J). An
already-Mercosul plate yields an error, not a no-op.
Args:
Plate(binary()): an old-format plate, any letter case.
Returns:
{ok, Converted}with the Mercosul plate, or{error, invalid}if the input is not a valid old-format plate.
Example:
1> brutils:convert_license_plate_to_mercosul(<<"ABC4567">>).
{ok,<<"ABC4F67">>}
2> brutils:convert_license_plate_to_mercosul(<<"ABC1D23">>).
{error,invalid}
get_format_license_plate
Detects the pattern of a license plate, returning the type atom
(old_format for LLLNNNN, mercosul for LLLNLNN) — the result can be
fed straight back into is_valid_license_plate/2.
Args:
Plate(binary()): the plate to inspect, any letter case.
Returns:
{ok, old_format | mercosul}, or{error, invalid}if the input matches neither pattern.
Example:
1> brutils:get_format_license_plate(<<"abc1234">>).
{ok,old_format}
2> brutils:get_format_license_plate(<<"ABC1D23">>).
{ok,mercosul}
generate_license_plate
Generates a random valid plate — Mercosul with no argument, or in the
given pattern (<<"LLLNNNN">> or <<"LLLNLNN">>, case insensitive).
Returns:
{ok, Plate}with the generated plate; the pattern form yields{error, invalid}for unknown patterns.
Example:
1> brutils:generate_license_plate().
{ok,<<"WMF1O31">>}
2> brutils:generate_license_plate(<<"LLLNNNN">>).
{ok,<<"BFX5517">>}
Voter ID
A Brazilian voter id (título de eleitor) is read from the right:
| Field | Digits | Position |
|---|---|---|
| sequential number | 8 (or 9 for some SP/MG titles) | leading |
| federative union | 2 | before the check digits |
| check digits | 2 | last |
Titles normally have 12 digits; São Paulo and Minas Gerais titles may have
13 (an extra sequential digit that the checksum ignores). Federative-union
codes run 01 (SP) through 27 (TO), with 28 (ZZ) for titles issued
abroad.
is_valid_voter_id
Returns whether the given voter id is valid: correct length for its federative union, code in range, and both verifying check digits matching. This function does not verify the existence of the title; it only validates the format of the string.
Args:
VoterId(term()): the voter id to be validated, a 12- or 13-digit binary. Any non-binary term returnsfalse— the function never raises.
Returns:
boolean():trueif the title is structurally valid,falseotherwise.
Example:
1> brutils:is_valid_voter_id(<<"690847092828">>).
true
2> brutils:is_valid_voter_id(<<"690847092820">>).
false
3> brutils:is_valid_voter_id(<<"3476353100183">>).
true
format_voter_id
Formats a valid 12-digit voter id for display with visual spacing
(NNNN NNNN NN NN). Valid 13-digit SP/MG titles yield {error, invalid}:
the display mask has no slot for their extra digit, so they are refused
rather than silently truncated.
Args:
VoterId(binary()): a numbers-only 12-digit voter id.
Returns:
{ok, Formatted}with the spaced voter id, or{error, invalid}.
Example:
1> brutils:format_voter_id(<<"690847092828">>).
{ok,<<"6908 4709 28 28">>}
2> brutils:format_voter_id(<<"3476353100183">>).
{error,invalid}
generate_voter_id
Generates a random valid 12-digit voter id — for a title issued abroad
(ZZ) with no argument, or for the given federative union (two-letter
code, case insensitive).
Returns:
{ok, VoterId}with the generated title; the UF form yields{error, invalid}for unknown codes.
Example:
1> brutils:generate_voter_id().
{ok,<<"469000172810">>}
2> brutils:generate_voter_id(<<"sp">>).
{ok,<<"569431460183">>}
3> brutils:generate_voter_id(<<"XX">>).
{error,invalid}
Author
Camilo Cunha de Azevedo camilotk@gmail.com