How CPF and CNPJ Check Digits Work (With the Algorithm)
Every CPF ends in two digits that are not chosen — they are computed from the nine before them. The same is true of the CNPJ's last two, computed from the twelve before them. That is what makes it possible to reject a mistyped document number in a form field without asking any government API whether it exists.
This post works through the arithmetic by hand, then turns it into code. If you only want the answer for one number, the validator does this live as you type.
What a check digit is for
Check digits exist to catch human error, not fraud. They are designed so that the two most common transcription mistakes — changing one digit, and swapping two adjacent digits — produce a number that fails the check.
They cannot tell you a document was ever issued. A number that passes is structurally valid; whether it belongs to a real person is a completely separate question that only the Receita Federal can answer. This distinction matters enough that it is worth repeating: passing the check means the digits are internally consistent, nothing more.
CPF, step by step
Take the first nine digits and compute the tenth.
Use 123.456.789-XX. The first nine digits are 1 2 3 4 5 6 7 8 9.
Step 1 — multiply each digit by a descending weight starting at 10.
digit 1 2 3 4 5 6 7 8 9
weight 10 9 8 7 6 5 4 3 2
prod 10 18 24 28 30 30 28 24 18
Step 2 — sum the products.
10 + 18 + 24 + 28 + 30 + 30 + 28 + 24 + 18 = 210
Step 3 — multiply by 10 and take the remainder mod 11.
210 * 10 = 2100
2100 mod 11 = 10
Step 4 — if the result is 10 or 11, the digit is 0. Otherwise it is the result itself. Here the result is 10, so the first check digit is 0.
Now the eleventh digit, computed from the first ten (including the one we just found). The weights start at 11 this time:
digit 1 2 3 4 5 6 7 8 9 0
weight 11 10 9 8 7 6 5 4 3 2
prod 11 20 27 32 35 36 35 32 27 0
sum = 255
255 * 10 = 2550
2550 mod 11 = 3
The second check digit is 3. The complete number is 123.456.789-03.
The * 10 and the mod 11 look arbitrary. They are the mechanism: multiplying by 10 before taking the remainder is what guarantees that changing any single digit changes the result, and the descending weights are what makes a swap of two adjacent digits change the sum.
CPF in code
function isValidCpf(input) {
const digits = input.replace(/\D/g, '');
if (digits.length !== 11) return false;
// All-same-digit sequences satisfy the arithmetic but are never issued.
if (/^(\d)\1{10}$/.test(digits)) return false;
const checkDigit = (upTo) => {
let sum = 0;
for (let i = 0; i < upTo; i++) {
sum += Number(digits[i]) * (upTo + 1 - i);
}
const result = (sum * 10) % 11;
return result === 10 || result === 11 ? 0 : result;
};
return checkDigit(9) === Number(digits[9])
&& checkDigit(10) === Number(digits[10]);
}
That guard clause on repeated digits is not optional. 111.111.111-11 passes the mod-11 arithmetic perfectly — the weights and the sum work out — and it is explicitly not a valid CPF. Every naive implementation accepts it, and this is the single most common bug in home-grown validators.
CNPJ: same idea, different weights
The CNPJ has 14 characters: twelve of payload and two check digits. The weight sequence is not a simple descending run; it cycles from 2 to 9 and restarts.
For the first check digit, applied to the twelve payload characters, read right to left with weights 2,3,4,5,6,7,8,9,2,3,4,5. For the second, applied to thirteen characters, the same cycle extended by one.
The remainder rule differs slightly from CPF: compute sum mod 11, and the digit is 0 if the remainder is less than 2, otherwise 11 - remainder.
The 2026 alphanumeric CNPJ
From 2026 the CNPJ accepts letters in its first twelve positions. This breaks every validator built on a digits-only regex, and it breaks them silently — new registrations start getting rejected as malformed.
The fix is smaller than it sounds. The check-digit routine keeps its exact shape; only the value of each character changes. Instead of Number(char), each character contributes its ASCII code minus 48:
'0'has code 48, so it contributes 0.'9'has code 57, contributing 9. Digits keep exactly the weight they always had.'A'has code 65, contributing 17.'Z'has code 90, contributing 42.
The two check digits themselves remain numeric.
function cnpjCharValue(char) {
return char.toUpperCase().charCodeAt(0) - 48;
}
function cnpjCheckDigit(chars) {
let weight = 2;
let sum = 0;
for (let i = chars.length - 1; i >= 0; i--) {
sum += cnpjCharValue(chars[i]) * weight;
weight = weight === 9 ? 2 : weight + 1;
}
const remainder = sum % 11;
return remainder < 2 ? 0 : 11 - remainder;
}
function isValidCnpj(input) {
const clean = input.replace(/[^0-9A-Za-z]/g, '').toUpperCase();
if (clean.length !== 14) return false;
if (/^(.)\1{13}$/.test(clean)) return false;
const body = clean.slice(0, 12);
const d1 = cnpjCheckDigit(body);
const d2 = cnpjCheckDigit(body + d1);
return `${d1}${d2}` === clean.slice(12);
}
Because digits map to themselves under charCodeAt - 48, this implementation validates old all-numeric CNPJs and new alphanumeric ones with the same code path. There is no branch, no feature flag, and no migration date to handle.
Three implementation notes worth having
Strip punctuation before validating, store the bare characters. The mask is presentation. Rejecting an unformatted number and accepting a well-formatted invalid one are two halves of the same mistake.
Do not validate on every keystroke without masking. A user typing a CPF is invalid for the first ten keystrokes. Show the check result when the field is complete or on blur, not while they type, or the form screams at them for the entire time they are filling it in.
Never generate a real-looking document number outside a test fixture. If you need one for a form test, a generator built for that produces structurally valid numbers that belong to nobody. Using them for anything other than testing is fraud, and the arithmetic above is precisely why the numbers look convincing enough for that to be a real temptation.
The whole scheme is about a hundred lines of arithmetic protecting millions of forms from typos. It is worth understanding rather than copying, because the copies are the ones that accept 111.111.111-11.