Reference

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.

Updated 2026-08-07 · 11 min read · by the FaturaPDF team

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.

What this check does and does not prove

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

CPFCNPJ
Length11 digits14 digits
Mask000.000.000-0000.000.000/0000-00
Basefirst 9first 12 (8 root + 4 branch)
Check digitslast 2last 2
Branch marker0001 = headquarters, 0002+ = branches
Region hint9th 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:

textfirst check digit
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 digit

Second 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:

textsecond check digit
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 digit

Both 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:

textCNPJ first check digit
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 digit
textCNPJ second check digit
digit    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 digit

Notice 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.

javascriptthe remainder rule
// 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.

javascriptrepeated-digit guard
// 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 digits

Verify 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.

javascriptnormalize first
const digits = (s) => String(s ?? "").replace(/\D/g, "");
const isValid = (v) => digits(v).length === 11 ? isValidCPF(v) : isValidCNPJ(v);

A complete, dependency-free implementation

javascripttaxid.js
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.

javascriptfixtures.js
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(""));
}
Synthetic, not fake-real

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.
javascriptcnpj-alnum.js
// 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];
}
Check the current official rule before you ship this

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.

Current limitation of this API

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 digitRegistry lookup (e.g. CNPJ data services)
Answers"Is this well-formed?""Does this exist, and what is its legal name/status?"
LatencyMicrosecondsNetwork round trip
CostZeroPer-request, usually metered
OfflineYesNo
Catches typosYesYes
Catches a valid-but-unregistered numberNoYes
PrivacyNothing leaves your systemYou 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

Related guides