The CPF and CNPJ check-digit algorithm, explained
Both Brazilian tax IDs end in two mod-11 check digits. The maths is twenty lines — but three details trip up almost every implementation, and one of them silently accepts 000.000.000-00.
A CPF (Cadastro de Pessoas Físicas) identifies an individual in Brazil; a CNPJ (Cadastro Nacional da Pessoa Jurídica) identifies a company. Both are self-checking: the last two digits are computed from the ones before them, so a single typo is caught without asking any government server.
That property is why validating locally is worth doing. It will not tell you whether a number is registered — only Receita Federal knows that — but it catches the overwhelming majority of real-world errors, which are transpositions and mistyped digits, and it costs nothing.
Does: the number is well-formed and internally consistent. Does not: the number belongs to a real, active person or company, or to the person in front of you. Roughly one in every eleven random 9-digit bases produces a valid CPF, so "valid" is a weak claim on its own. Treat it as a form-input filter, never as identity verification.
The shape of the numbers
| CPF | CNPJ | |
|---|---|---|
| Length | 11 digits | 14 digits |
| Mask | 000.000.000-00 | 00.000.000/0000-00 |
| Base | first 9 | first 12 (8 root + 4 branch) |
| Check digits | last 2 | last 2 |
| Branch marker | — | 0001 = headquarters, 0002+ = branches |
| Region hint | 9th digit encodes the issuing fiscal region | — |
The 9th digit of a CPF is not random: it identifies the fiscal region where the number was issued
(for example 8 covers São Paulo). It plays no part in the check-digit maths, but it is a
useful sanity signal when you are staring at test data.
The algorithm in one paragraph
Take the base digits. Multiply each by a descending weight. Sum the products. Take the remainder modulo 11. If the remainder is 0 or 1, the check digit is 0; otherwise it is 11 minus the remainder. Then repeat, this time including the digit you just computed, with the weights shifted up by one.
That is the whole thing for both documents. Only the weights differ.
CPF, worked out digit by digit
Take 529.982.247-25, a commonly used valid test number. The base is
529982247 and the check digits should come out as 2 and 5.
First check digit
Weights run from 10 down to 2 across the nine base digits:
digit 5 2 9 9 8 2 2 4 7
weight 10 9 8 7 6 5 4 3 2
--- --- --- --- --- --- --- --- ---
prod 50 18 72 63 48 10 8 12 14
sum = 295
295 mod 11 = 9 (9 >= 2, so use 11 - remainder)
check = 11 - 9 = 2 <-- matches the 10th digitSecond check digit
Now the base is ten digits long — the original nine plus the digit we just derived — and the weights run from 11 down to 2:
digit 5 2 9 9 8 2 2 4 7 2
weight 11 10 9 8 7 6 5 4 3 2
--- --- --- --- --- --- --- --- --- ---
prod 55 20 81 72 56 12 10 16 21 4
sum = 347
347 mod 11 = 6
check = 11 - 6 = 5 <-- matches the 11th digitBoth match, so 529.982.247-25 is well-formed.
CNPJ, worked out digit by digit
The idea is identical; the weights are not a simple descending run. They cycle
2..9 from the right, which written left-to-right for 12 base digits gives
[5,4,3,2,9,8,7,6,5,4,3,2]. Take 11.222.333/0001-81:
digit 1 1 2 2 2 3 3 3 0 0 0 1
weight 5 4 3 2 9 8 7 6 5 4 3 2
--- --- --- --- --- --- --- --- --- --- --- ---
prod 5 4 6 4 18 24 21 18 0 0 0 2
sum = 102
102 mod 11 = 3
check = 11 - 3 = 8 <-- matches the 13th digitdigit 1 1 2 2 2 3 3 3 0 0 0 1 8
weight 6 5 4 3 2 9 8 7 6 5 4 3 2
--- --- --- --- --- --- --- --- --- --- --- --- ---
prod 6 5 8 6 4 27 24 21 0 0 0 3 16
sum = 120
120 mod 11 = 10
check = 11 - 10 = 1 <-- matches the 14th digitNotice the second weight array is just the first one with a 6 prepended. That falls out of
the "cycle 2..9 from the right" construction and is why implementations can write
w2 = [6] + w1 instead of spelling out thirteen numbers.
The three things people get wrong
1. The remainder rule
When sum % 11 is 0 or 1, the check digit is 0 — not
11 and not 10. A naive 11 - rest yields 11 or 10, which cannot be a single
digit, and the usual "fix" of taking % 10 silently produces the wrong answer for a small
fraction of numbers. Those are exactly the cases your test suite will not cover unless you go looking
for them.
// WRONG - breaks when rest is 0 or 1
const check = (11 - (sum % 11)) % 10;
// RIGHT
const rest = sum % 11;
const check = rest < 2 ? 0 : 11 - rest;2. Repeated digits pass the arithmetic
111.111.111-11, 000.000.000-00, 222.222.222-22 — every
all-same-digit sequence satisfies both check digits. The maths is happy; Receita Federal is not. These
are explicitly invalid and they are also the most common junk value in a database, because they are what
people type when a form demands a CPF they do not want to give.
// Reject before doing any arithmetic.
if (/^(\d)\1{10}$/.test(cpfDigits)) return false; // 11 identical digits
if (/^(\d)\1{13}$/.test(cnpjDigits)) return false; // 14 identical digitsVerify this yourself: enter 111.111.111-11 into
the free CPF/CNPJ validator and it is rejected, while the same number passes a
bare mod-11 implementation.
3. Length checked on the masked string
"529.982.247-25".length is 14, not 11. Strip everything that is not a digit
first, then check the length. And be careful with the reverse mistake: a CNPJ has 14 digits and a
masked CPF has 14 characters, so a length-14 test on unstripped input will route a CPF into the
CNPJ validator and reject it.
const digits = (s) => String(s ?? "").replace(/\D/g, "");
const isValid = (v) => digits(v).length === 11 ? isValidCPF(v) : isValidCNPJ(v);A complete, dependency-free implementation
const digits = (s) => String(s ?? "").replace(/\D/g, "");
/** Mod-11 check digit over the first weights.length digits. */
function checkDigit(nums, weights) {
const sum = weights.reduce((acc, w, i) => acc + nums[i] * w, 0);
const rest = sum % 11;
return rest < 2 ? 0 : 11 - rest;
}
export function isValidCPF(input) {
const d = digits(input);
if (d.length !== 11) return false;
if (/^(\d)\1{10}$/.test(d)) return false;
const n = [...d].map(Number);
return checkDigit(n, [10, 9, 8, 7, 6, 5, 4, 3, 2]) === n[9]
&& checkDigit(n, [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]) === n[10];
}
export function isValidCNPJ(input) {
const d = digits(input);
if (d.length !== 14) return false;
if (/^(\d)\1{13}$/.test(d)) return false;
const n = [...d].map(Number);
const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
return checkDigit(n, w1) === n[12]
&& checkDigit(n, [6, ...w1]) === n[13];
}
export const isValidTaxId = (v) =>
digits(v).length === 11 ? isValidCPF(v) : isValidCNPJ(v);
/** 000.000.000-00 / 00.000.000/0000-00 */
export function mask(input) {
const d = digits(input);
if (d.length === 11) return `${d.slice(0,3)}.${d.slice(3,6)}.${d.slice(6,9)}-${d.slice(9)}`;
if (d.length === 14) return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5,8)}/${d.slice(8,12)}-${d.slice(12)}`;
return String(input ?? "");
}Ports of exactly this logic, idiomatic per language, are in the Python, PHP, Go, C# and Ruby guides.
Generating valid test data
You cannot use a colleague's real CPF in a test fixture, and you should not. Generate one: pick nine random base digits and append the two computed check digits. The result passes validation and, with overwhelming probability, is not registered to anybody — the CPF space has 109 bases, several times Brazil's population.
export function fakeCPF() {
const base = Array.from({ length: 9 }, () => Math.floor(Math.random() * 10));
const d1 = checkDigit(base, [10, 9, 8, 7, 6, 5, 4, 3, 2]);
const d2 = checkDigit([...base, d1], [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]);
return mask([...base, d1, d2].join(""));
}
export function fakeCNPJ({ branch = "0001" } = {}) {
const root = Array.from({ length: 8 }, () => Math.floor(Math.random() * 10));
const base = [...root, ...[...branch].map(Number)];
const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const d1 = checkDigit(base, w1);
const d2 = checkDigit([...base, d1], [6, ...w1]);
return mask([...base, d1, d2].join(""));
}A generated CPF is structurally valid. Use it in test fixtures, seed data and demos — never to impersonate a person, open an account, or populate a document you send to a third party as if it were real. The browser generator exists for exactly the legitimate case and says so on the page.
The alphanumeric CNPJ (from 2026)
Receita Federal has published rules for an alphanumeric CNPJ, introduced because the numeric space is finite and running down. The headline points:
- Length is unchanged: 14 characters. Eight for the root, four for the branch, two for the check digits.
- The first twelve positions may contain digits and uppercase letters A–Z. The last two — the check digits — remain numeric.
- Existing numeric CNPJs stay valid indefinitely. Nothing is reissued.
- The check-digit maths is the same mod-11 with the same weights. The only change is how each
character is converted to a number: take its ASCII code minus 48. So
'0'→0 …'9'→9,'A'→17,'B'→18, …'Z'→42. For an all-numeric CNPJ this reduces to the classic algorithm — the two rules agree, which is what makes the transition backward-compatible.
// Handles both classic numeric and alphanumeric CNPJ.
const VALUE = (ch) => ch.charCodeAt(0) - 48; // '0'->0 ... '9'->9, 'A'->17 ... 'Z'->42
export function isValidCNPJAlnum(input) {
const s = String(input ?? "").toUpperCase().replace(/[^0-9A-Z]/g, "");
if (s.length !== 14) return false;
if (!/^[0-9A-Z]{12}\d{2}$/.test(s)) return false; // check digits must be numeric
if (/^(.)\1{13}$/.test(s)) return false;
const v = [...s].map(VALUE);
const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const dv = (weights) => {
const rest = weights.reduce((a, w, i) => a + v[i] * w, 0) % 11;
return rest < 2 ? 0 : 11 - rest;
};
return dv(w1) === v[12] && dv([6, ...w1]) === v[13];
}Rollout dates and operational details have moved more than once. Treat the code above as the documented algorithm, not as legal certainty, and confirm against the current Receita Federal technical note for your compliance deadline. If you accept CNPJ input from users today, the low-risk move is to widen your storage column and input mask to alphanumeric now, and switch the validator when you confirm the rule for your jurisdiction and date.
To be straight with you: FaturaPDF's documento field currently validates
numeric CPF/CNPJ only — a 14-character alphanumeric CNPJ is rejected with a 400. If you need to
print one today, the practical workaround is to put it in emitente.nome or
observacoes as text. Alphanumeric support is a known gap, listed here rather than hidden.
Should you call a lookup API instead?
Check-digit validation and registry lookup are different jobs and both have a place:
| Local check digit | Registry lookup (e.g. CNPJ data services) | |
|---|---|---|
| Answers | "Is this well-formed?" | "Does this exist, and what is its legal name/status?" |
| Latency | Microseconds | Network round trip |
| Cost | Zero | Per-request, usually metered |
| Offline | Yes | No |
| Catches typos | Yes | Yes |
| Catches a valid-but-unregistered number | No | Yes |
| Privacy | Nothing leaves your system | You transmit a personal identifier to a third party |
The sensible pattern is both, in order: validate locally on every keystroke to catch typos for free, and call a registry only at the moment it actually matters — onboarding a supplier, releasing a payout — where the extra latency and cost buy something. For CPF in particular, remember that it is personal data under the LGPD: sending one to a third-party service is a processing decision, not just a technical one.
Frequently asked questions
Does a valid CPF mean the person exists?
No. It means the eleven digits are internally consistent. Roughly one in eleven random 9-digit bases produces a structurally valid CPF, so the check catches typos, not fabrication. Only a Receita Federal query establishes that a number is registered and active.
Why is 111.111.111-11 rejected if the maths works?
Because all-same-digit sequences satisfy the mod-11 arithmetic by construction but are explicitly excluded as valid registrations. Every correct implementation adds a repeated-digit guard before doing the arithmetic — and it is worth adding precisely because these values are the most common junk in real databases.
Do CPF and CNPJ use the same algorithm?
Same structure — mod-11, two passes, the same remainder rule — with different weights. CPF uses simple descending runs (10..2, then 11..2); CNPJ cycles 2..9 from the right, giving [5,4,3,2,9,8,7,6,5,4,3,2] and then that array with a 6 prepended.
How do I handle the alphanumeric CNPJ?
Same mod-11 weights, but convert each character with charCode - 48 before multiplying. The check digits remain numeric and the length stays 14. Widen your database column and input mask first; switch the validator once you have confirmed the current official rule and date for your case.
Can I generate CPFs for my test suite?
Yes, and you should — never put a real person's CPF in a fixture. Pick nine random digits and append the two computed check digits, as shown above, or use the free generator. Use them only for testing and demos.
Skip the validator entirely
FaturaPDF runs exactly this algorithm on every document it generates — send a CPF or CNPJ with a bad check digit and you get a 400 telling you which party failed, instead of a PDF with a wrong tax ID printed on it.
Get an API key on RapidAPI → Or try the free browser generator