CNPJ Validation in Node.js
The Brazilian business registry number — 14 digits, two weighted MOD-11 check digits, and an optional company lookup via BrasilAPI. Here's how to validate it correctly.
In this guide
1. What is a CNPJ?
CNPJ (Cadastro Nacional da Pessoa Jurídica) is the Brazilian National Registry of Legal Entities. It is the mandatory identification number for every company, association, foundation, or any other legal entity operating in Brazil. The CNPJ is issued and managed by the Receita Federal (Brazilian Federal Revenue Service).
A CNPJ is required for:
- Opening bank accounts and obtaining credit
- Issuing invoices (notas fiscais)
- Signing contracts and participating in public procurement
- Tax filings and social security obligations
- Registering with state and municipal authorities
| Entity | CNPJ | Type |
|---|---|---|
| Headquarters | 11.222.333/0001-81 | Branch 0001 (matriz) |
| Branch office | 11.222.333/0002-62 | Branch 0002 (filial) |
0001, which identifies the headquarters (matriz). Additional branches receive incrementing branch numbers (0002, 0003, etc.) but share the same 8-digit base.2. CNPJ anatomy
A CNPJ is a 14-digit number. When formatted, it uses the pattern XX.XXX.XXX/XXXX-XX. Internally, it consists of three parts:
Example: 11.222.333/0001-81
| Part | Positions | Description |
|---|---|---|
| Base number | 1–8 | Uniquely identifies the company. Assigned sequentially by Receita Federal. |
| Branch number | 9–12 | 0001 = headquarters (matriz). Higher values identify branch offices (filiais). |
| Check digits | 13–14 | Two verification digits computed with a weighted MOD-11 algorithm. |
XX.XXX.XXX/XXXX-XX. The API accepts both the formatted and the raw 14-digit form.3. The check digit algorithm
CNPJ uses two consecutive weighted MOD-11 sums. The first pass computes check digit 1 (position 13) from digits 1–12. The second pass computes check digit 2 (position 14) from digits 1–13 (including the freshly computed check digit 1).
First check digit (position 13)
- Multiply each of the first 12 digits by the weight sequence:
[5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] - Sum all products
- Calculate
remainder = sum mod 11 - If the remainder is less than 2, the check digit is 0; otherwise the check digit is
11 − remainder
Second check digit (position 14)
- Multiply each of the first 13 digits (including check digit 1) by the weight sequence:
[6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] - Sum all products
- Calculate
remainder = sum mod 11 - Same rule: if the remainder is less than 2, the check digit is 0; otherwise
11 − remainder
Here is a step-by-step worked example for the CNPJ 11222333000181:
| Position | Digit | Weight (1st) | Product |
|---|---|---|---|
| 1 | 1 | 5 | 5 |
| 2 | 1 | 4 | 4 |
| 3 | 2 | 3 | 6 |
| 4 | 2 | 2 | 4 |
| 5 | 2 | 9 | 18 |
| 6 | 3 | 8 | 24 |
| 7 | 3 | 7 | 21 |
| 8 | 3 | 6 | 18 |
| 9 | 0 | 5 | 0 |
| 10 | 0 | 4 | 0 |
| 11 | 0 | 3 | 0 |
| 12 | 1 | 2 | 2 |
Sum = 5 + 4 + 6 + 4 + 18 + 24 + 21 + 18 + 0 + 0 + 0 + 2 = 102
102 mod 11 = 102 − 9 × 11 = 102 − 99 = 3
The remainder is 3 (not less than 2), so check digit 1 = 11 − 3 = 8.
The second pass uses digits 1–13 (including the 8) with weights [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2], yielding check digit 2 = 1.
Both check digits match the CNPJ 11222333000181 — it is structurally valid.
Here is a JavaScript implementation:
function validateCNPJ(cnpj) { // Strip formatting const digits = cnpj.replace(/[^\d]/g, ''); if (digits.length !== 14) return false; // Reject known invalid patterns (all same digit) if (/^(\d)\1{13}$/.test(digits)) return false; const d = digits.split('').map(Number); // First check digit (position 13) const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; const sum1 = w1.reduce((acc, w, i) => acc + d[i] * w, 0); const rem1 = sum1 % 11; const check1 = rem1 < 2 ? 0 : 11 - rem1; if (check1 !== d[12]) return false; // Second check digit (position 14) const w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; const sum2 = w2.reduce((acc, w, i) => acc + d[i] * w, 0); const rem2 = sum2 % 11; const check2 = rem2 < 2 ? 0 : 11 - rem2; return check2 === d[13]; }
?lookup=true parameter to check the company's status via BrasilAPI.4. Why manual validation isn't enough
Checksum only catches typos
The MOD-11 algorithm detects transposition errors and single-digit mistakes, but it cannot tell you whether the CNPJ was actually issued by Receita Federal. A number can pass the checksum and still be completely fabricated.
Companies can be inactive or suspended
A CNPJ remains structurally valid even after the company is dissolved, has its registration suspended, or is declared unfit (inapta) by Receita Federal. Only a registry lookup can reveal the current status.
You need company details for compliance
KYC, invoicing, and procurement workflows require more than a boolean. You need the company's legal name (razão social), trade name (nome fantasia), activity code (CNAE), and registration status. The checksum gives you none of this.
5. The right solution: one API call
The IsValid CNPJ API handles the full validation stack in a single GET request:
Checksum validation
Two-pass MOD-11 algorithm on all 14 digits
Company lookup (optional)
Legal name, trade name, CNAE, status, and address from BrasilAPI
Headquarters detection
Identifies whether the CNPJ is a headquarters (0001) or a branch
Get your free API key at isvalid.dev. The free tier includes 100 calls per day with no credit card required.
Full parameter reference and response schema: CNPJ Validation API docs →
6. Node.js code example
Using the @isvalid-dev/sdk package or the built-in fetch API (Node.js 18+):
// cnpj-validator.mjs import { createClient } from '@isvalid-dev/sdk'; const iv = createClient({ apiKey: process.env.ISVALID_API_KEY }); // ── Basic validation ──────────────────────────────────────────────────────── const result = await iv.br.cnpj('11.222.333/0001-81'); if (!result.valid) { console.log('Invalid CNPJ'); } else { console.log('Formatted:', result.formatted); // "11.222.333/0001-81" console.log('HQ? :', result.isHeadquarters); // true } // ── With company lookup ───────────────────────────────────────────────────── const detailed = await iv.br.cnpj('11222333000181', { lookup: true }); if (detailed.valid && detailed.found) { console.log('Company :', detailed.razaoSocial); console.log('Trade :', detailed.nomeFantasia); console.log('Status :', detailed.situacao); console.log('CNAE :', detailed.cnaeFiscal, detailed.cnaeDescricao); console.log('State :', detailed.uf); }
lookup parameter is optional. Use it when you need company details like legal name, status, or CNAE code. Omit it for fast checksum-only validation.7. cURL examples
Basic CNPJ validation:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/br/cnpj?value=11.222.333/0001-81"
With company lookup via BrasilAPI:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/br/cnpj?value=11222333000181&lookup=true"
8. Understanding the response
Valid CNPJ (basic)
{ "valid": true, "formatted": "11.222.333/0001-81", "base": "11222333", "branch": "0001", "checkDigits": "81", "isHeadquarters": true }
Valid CNPJ (with lookup)
{ "valid": true, "formatted": "11.222.333/0001-81", "base": "11222333", "branch": "0001", "checkDigits": "81", "isHeadquarters": true, "found": true, "razaoSocial": "EMPRESA EXEMPLO LTDA", "nomeFantasia": "EXEMPLO", "situacao": "Ativa", "dataInicioAtividade": "2010-05-15", "cnaeFiscal": "6201501", "cnaeDescricao": "Desenvolvimento de programas de computador sob encomenda", "naturezaJuridica": "206-2 - Sociedade Empresaria Limitada", "porte": "ME", "capitalSocial": 50000, "uf": "SP", "municipio": "SAO PAULO", "cep": "01310100", "dataSource": "brasilapi" }
Invalid CNPJ
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the CNPJ passed format and checksum validation |
| formatted | string | CNPJ in XX.XXX.XXX/XXXX-XX format |
| base | string | 8-digit company identifier |
| branch | string | 4-digit branch number (0001 = headquarters) |
| checkDigits | string | The two verification digits |
| isHeadquarters | boolean | True when the branch number is 0001 |
| found | boolean | Whether the company was found in BrasilAPI (only with lookup=true) |
| razaoSocial | string | Legal name of the company |
| nomeFantasia | string | Trade name / brand name |
| situacao | string | Registration status (e.g. "Ativa", "Baixada", "Inapta") |
| dataInicioAtividade | string | Date when business activity started (YYYY-MM-DD) |
| cnaeFiscal | string | Primary economic activity code (CNAE) |
| cnaeDescricao | string | Human-readable description of the CNAE code |
| naturezaJuridica | string | Legal nature (e.g. "206-2 - Sociedade Empresaria Limitada") |
| porte | string | Company size classification (ME, EPP, etc.) |
| capitalSocial | number | Registered share capital in BRL |
| uf | string | Two-letter state code (e.g. "SP", "RJ") |
| municipio | string | City / municipality name |
| cep | string | Postal code (8 digits, no hyphen) |
| dataSource | string | Data provider identifier ("brasilapi") |
9. Edge cases to handle
(a) Branch vs headquarters
A CNPJ with branch number 0001 identifies the headquarters (matriz). Any other branch number (0002, 0003, etc.) identifies a branch office (filial). The isHeadquarters field tells you which one it is. For KYC or supplier onboarding, you may want to require the headquarters CNPJ.
const result = await validateCNPJ('11222333000262'); if (result.valid && !result.isHeadquarters) { console.log('This is a branch office (filial)'); console.log('Branch number:', result.branch); // "0002" // Consider requesting the headquarters CNPJ instead }
(b) Inactive companies
A CNPJ that passes checksum validation may belong to a company with status "Baixada" (deregistered) or "Inapta" (unfit). Always check the situacao field in the lookup response for compliance workflows.
const result = await validateCNPJ('11222333000181', { lookup: true }); if (result.valid && result.found) { if (result.situacao !== 'Ativa') { console.log('Company is not active:', result.situacao); // "Baixada" = deregistered, "Inapta" = unfit, "Suspensa" = suspended } else { console.log('Company is active'); } }
(c) CNPJ formatting
The API accepts both formatted (11.222.333/0001-81) and raw (11222333000181) input. The response always includes the formatted field for display. Store the raw 14-digit string in your database and format only for display.
// Both produce the same result: const a = await validateCNPJ('11.222.333/0001-81'); const b = await validateCNPJ('11222333000181'); // Always store the raw form const cnpjForDb = result.formatted.replace(/[^\d]/g, ''); // Use the formatted version for display console.log('Display:', result.formatted); // "11.222.333/0001-81"
(d) MEI vs ME vs EPP
The porte field in the lookup response indicates the company size classification. MEI (Microempreendedor Individual) is a sole proprietor with revenue up to R$81,000/year. ME (Microempresa) has revenue up to R$360,000/year. EPP (Empresa de Pequeno Porte) goes up to R$4,800,000/year. This matters for tax bracket calculations and supplier eligibility.
const result = await validateCNPJ('11222333000181', { lookup: true }); if (result.valid && result.found) { switch (result.porte) { case 'MEI': console.log('Sole proprietor (MEI)'); break; case 'ME': console.log('Micro enterprise'); break; case 'EPP': console.log('Small enterprise'); break; default: console.log('Company size:', result.porte); } }
Summary
See also
Validate CNPJ numbers instantly
Free tier includes 100 API calls per day. No credit card required. Checksum validation and optional company lookup from BrasilAPI included.