Ferramentas gratuitas para desenvolvedores. Nenhum dado real de pessoas é consultado ou armazenado.Como funciona o algoritmo do CPF →
TypeScript

Validar, gerar e formatar CPF em TypeScript

Validação de CPF em TypeScript com tipo de marca e motivo da recusa, geração, formatação, testes com Vitest e chamada tipada à API do cpf.dev.br.

Roberto GuerraPublicado em 05 de outubro de 2026Editar no GitHub

A lógica para validar CPF em TypeScript é a mesma da versão em JavaScript, mas o sistema de tipos permite ir além do true ou false. Nesta página, a validação devolve um resultado discriminado com o motivo da recusa e marca o CPF aprovado com um tipo próprio, de modo que funções de domínio só aceitem um valor que já passou pela checagem. O código compila com strict ligado e não depende de nenhum pacote.

Validar CPF em TypeScript

Cpf é um branded type: em tempo de execução é só uma string de 11 dígitos, mas o compilador não deixa passar uma string qualquer onde se espera Cpf. A única forma de obter esse tipo é pela analisarCpf.

cpf.ts
export type Cpf = string & { readonly __marca: 'Cpf' };

export type ResultadoCpf =
| { valido: true; cpf: Cpf }
| { valido: false; motivo: 'formato' | 'repetido' | 'digito' };

const REPETIDO = /^(\d)\1{10}$/;

function calcularDigito(digitos: string, pesoInicial: number): number {
let soma = 0;
for (let i = 0; i < digitos.length; i++) {
  soma += Number(digitos[i]) * (pesoInicial - i);
}
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}

export function analisarCpf(valor: string): ResultadoCpf {
const cpf = valor.replace(/[.\-\s]/g, '');
if (!/^\d{11}$/.test(cpf)) return { valido: false, motivo: 'formato' };
if (REPETIDO.test(cpf)) return { valido: false, motivo: 'repetido' };
const dv1 = calcularDigito(cpf.slice(0, 9), 10);
const dv2 = calcularDigito(cpf.slice(0, 10), 11);
if (dv1 !== Number(cpf[9]) || dv2 !== Number(cpf[10])) return { valido: false, motivo: 'digito' };
return { valido: true, cpf: cpf as Cpf };
}

export function validarCpf(valor: string): boolean {
return analisarCpf(valor).valido;
}

O fluxo segue a mesma ordem da implementação de referência:

  1. Remove pontos, hífen e espaços. Qualquer outro caractere deixa a string fora do padrão de 11 dígitos e cai em formato.
  2. Recusa sequências repetidas. 111.111.111-11 fecha a conta do módulo 11, mas sequências assim não são consideradas válidas.
  3. Calcula os dois verificadores com a regra do resto menor que 2 virando 0.

Para 529.982.247-25 as somas são 295 e 347, os restos 9 e 6 e os dígitos 2 e 5: resultado válido, com cpf igual a '52998224725'. Para 529.982.247-26, o segundo dígito esperado é 5, e o motivo devolvido é digito.

Como ResultadoCpf é uma união discriminada, depois de if (r.valido) o TypeScript sabe que r.cpf existe; no ramo contrário, só r.motivo está disponível.

Gerar CPF válido em TypeScript

A geração sorteia nove dígitos de uma vez com crypto.getRandomValues, descarta bases repetidas e calcula os verificadores.

cpf.ts
export function gerarCpf(opcoes: { formatado?: boolean } = {}): string {
let base: string;
do {
  base = Array.from(crypto.getRandomValues(new Uint32Array(9)), (n) => n % 10).join('');
} while (/^(\d)\1{8}$/.test(base));
const dv1 = calcularDigito(base, 10);
const dv2 = calcularDigito(base + dv1, 11);
const cpf = base + dv1 + dv2;
return opcoes.formatado ? formatarCpf(cpf) : cpf;
}

O número gerado é válido pelo algoritmo e pode coincidir com um CPF real por acaso, então use-o apenas em testes. Se precisar de centenas de uma vez, o gerador de CPF em lote exporta em CSV, JSON ou SQL.

Formatar e limpar CPF em TypeScript

cpf.ts
export function limparCpf(valor: string): string {
return valor.replace(/\D/g, '');
}

export function formatarCpf(valor: string): string {
const d = limparCpf(valor).slice(0, 11);
let saida = d.slice(0, 3);
if (d.length > 3) saida += '.' + d.slice(3, 6);
if (d.length > 6) saida += '.' + d.slice(6, 9);
if (d.length > 9) saida += '-' + d.slice(9, 11);
return saida;
}

formatarCpf é idempotente e tolera entrada parcial: '5299' vira '529.9' e '529.982.247-25' continua igual. Isso permite usá-la direto no evento de input de um campo com máscara, como mostra o guia de máscara de CPF em React, Vue e Angular. Para gravar no banco, use limparCpf ou, melhor ainda, o cpf devolvido por analisarCpf, que já está limpo e validado.

Testes

Com Vitest, it.each aceita objetos e interpola as chaves no nome do teste. Além dos três casos, vale um teste para o motivo da recusa.

cpf.test.ts
import { describe, expect, it } from 'vitest';
import { analisarCpf, validarCpf } from './cpf';

describe('validarCpf', () => {
it.each([
  { cpf: '529.982.247-25', esperado: true },
  { cpf: '111.111.111-11', esperado: false },
  { cpf: '529.982.247-26', esperado: false },
])('$cpf → $esperado', ({ cpf, esperado }) => {
  expect(validarCpf(cpf)).toBe(esperado);
});

it('explica o motivo da recusa', () => {
  expect(analisarCpf('111.111.111-11')).toEqual({ valido: false, motivo: 'repetido' });
  expect(analisarCpf('529.982.247-26')).toEqual({ valido: false, motivo: 'digito' });
});
});

Usar a API em vez de reimplementar

A API do cpf.dev.br faz a mesma validação por HTTP, sem chave e com limite de 60 requisições por minuto por IP. Declare a forma da resposta para manter o resto do código tipado.

api.ts
export {};

interface RespostaValidacao {
valido: boolean;
cpf: string;
formatado: string;
uf: string[];
}

const resposta = await fetch('https://www.cpf.dev.br/api/v1/cpf/validar/529.982.247-25');
if (!resposta.ok) throw new Error('HTTP ' + resposta.status);
const dados = (await resposta.json()) as RespostaValidacao;
// { valido: true, cpf: '52998224725', formatado: '529.982.247-25', uf: ['ES', 'RJ'] }

const lote = await fetch('https://www.cpf.dev.br/api/v1/cpf/gerar?quantidade=5&uf=SP&formatado=true');
const { cpfs } = (await lote.json()) as { cpfs: string[] };

O as não valida nada em tempo de execução; se a resposta vier de uma fonte em que você não confia, passe-a por um schema. Para testar casos à mão, o validador de CPF mostra o motivo de cada recusa.

Leia também