Como integrar validação de CPF em formulários HTML com JavaScript puro

Tutorial completo para integrar validação de CPF em formulários HTML usando JavaScript puro, com formatação automática e consulta à API.

Redação CPFHub.io
Redação CPFHub.io
··11 min de leitura
Como integrar validação de CPF em formulários HTML com JavaScript puro

Para integrar validação de CPF em formulários HTML com JavaScript puro, combine três camadas: máscara de formatação automática no campo, validação local dos dígitos verificadores (algoritmo módulo 11) e consulta à API via backend para confirmar que o CPF existe. Nenhuma biblioteca externa é necessária — o código funciona em qualquer navegador moderno sem dependências.

Introdução

A validação de CPF em formulários HTML é uma das implementações mais comuns no desenvolvimento web brasileiro. Seja em cadastros de e-commerce, sistemas de atendimento, plataformas de serviços ou aplicações internas, o CPF é um campo presente em praticamente todo formulário voltado para o mercado nacional.

Implementar essa validação com JavaScript puro -- sem dependência de frameworks ou bibliotecas externas -- garante compatibilidade universal, performance máxima e controle total sobre o comportamento do campo. Combinada com uma consulta à API da CPFHub.io, a solução confirma que o CPF informado é real e retorna os dados do titular para cruzamento de identidade.


Estrutura do formulário HTML

O ponto de partida é um formulário HTML bem estruturado com os atributos corretos para campos de CPF:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cadastro com Validação de CPF</title>
    <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: #f0f2f5;
    padding: 40px 20px;
    }
    .form-container {
    max-width: 480px;
    margin: 0 auto;
    background: white;
    padding: 32px;
    border-radius: 8px;
    box-shadow: 0 2px 12px rgba(0,0,0,0.08);
    }
    h1 { font-size: 22px; margin-bottom: 24px; color: #1a1a2e; }
    .form-group { margin-bottom: 20px; }
    label {
    display: block;
    font-size: 14px;
    font-weight: 600;
    color: #374151;
    margin-bottom: 6px;
    }
    input {
    width: 100%;
    padding: 12px 14px;
    border: 2px solid #e5e7eb;
    border-radius: 6px;
    font-size: 16px;
    transition: border-color 0.2s;
    }
    input:focus { outline: none; border-color: #3b82f6; }
    input.valido { border-color: #16a34a; }
    input.invalido { border-color: #ef4444; }
    .mensagem {
    font-size: 13px;
    margin-top: 4px;
    min-height: 18px;
    }
    .mensagem.sucesso { color: #16a34a; }
    .mensagem.erro { color: #ef4444; }
    .mensagem.info { color: #6b7280; }
    button {
    width: 100%;
    padding: 14px;
    background: #3b82f6;
    color: white;
    border: none;
    border-radius: 6px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    margin-top: 8px;
    }
    button:hover { background: #2563eb; }
    button:disabled { background: #93c5fd; cursor: not-allowed; }
    .resultado {
    margin-top: 20px;
    padding: 16px;
    background: #f0fdf4;
    border: 1px solid #bbf7d0;
    border-radius: 6px;
    display: none;
    }
    .resultado p { font-size: 14px; color: #166534; margin-bottom: 4px; }
    </style>
</head>
<body>
    <div class="form-container">
    <h1>Cadastro</h1>
    <form id="formCadastro" novalidate>
    <div class="form-group">
    <label for="nome">Nome completo</label>
    <input type="text" id="nome" name="nome" placeholder="Seu nome completo"
    required autocomplete="name" />
    </div>
    <div class="form-group">
    <label for="cpf">CPF</label>
    <input type="text" id="cpf" name="cpf" inputmode="numeric"
    pattern="[0-9]*" maxlength="14" placeholder="000.000.000-00"
    required autocomplete="off" />
    <div id="cpfMensagem" class="mensagem"></div>
    </div>
    <div class="form-group">
    <label for="email">E-mail</label>
    <input type="email" id="email" name="email"
    placeholder="seu@email.com" required autocomplete="email" />
    </div>
    <button type="submit" id="btnSubmit">Cadastrar</button>
    </form>
    <div id="resultado" class="resultado">
    <p id="resultadoNome"></p>
    <p id="resultadoCPF"></p>
    </div>
    </div>

    <script src="cpf-validacao.js"></script>
</body>
</html>

Pontos importantes do HTML:

  • inputmode="numeric" -- Exibe o teclado numérico em dispositivos móveis.
  • pattern="[0-9]*" -- Reforça a validação de formato no lado do cliente.
  • maxlength="14" -- Limita o campo ao tamanho do CPF formatado (000.000.000-00).
  • autocomplete="off" -- Evita autopreenchimento indesejado para o campo de CPF.

Máscara de formatação automática

A máscara formata o CPF enquanto o usuário digita, aplicando automaticamente os pontos e o hífen:

// cpf-validacao.js

/**
    * Aplica máscara de formatação ao campo de CPF.
    * Formato: 000.000.000-00
    */
function aplicarMascaraCPF(input) {
    input.addEventListener('input', function () {
    let valor = this.value.replace(/\D/g, '');
    valor = valor.slice(0, 11);

    if (valor.length > 9) {
    valor = valor.replace(
    /(\d{3})(\d{3})(\d{3})(\d{1,2})/,
    '$1.$2.$3-$4'
    );
    } else if (valor.length > 6) {
    valor = valor.replace(
    /(\d{3})(\d{3})(\d{1,3})/,
    '$1.$2.$3'
    );
    } else if (valor.length > 3) {
    valor = valor.replace(
    /(\d{3})(\d{1,3})/,
    '$1.$2'
    );
    }

    this.value = valor;
    });
}

A máscara funciona removendo todos os caracteres não numéricos, limitando a 11 dígitos e aplicando a formatação conforme o número de dígitos digitados.


Validação dos dígitos verificadores

O CPF possui dois dígitos verificadores (10o e 11o dígitos) calculados pelo algoritmo módulo 11. A validação local verifica se esses dígitos estão corretos:

/**
    * Valida os dígitos verificadores do CPF.
    * Retorna true se o CPF é matematicamente válido.
    */
function validarCPFLocal(cpf) {
    // Remover caracteres não numéricos
    cpf = cpf.replace(/\D/g, '');

    // Verificar se tem 11 dígitos
    if (cpf.length !== 11) {
    return false;
    }

    // Rejeitar CPFs com todos os dígitos iguais
    if (/^(\d)\1{10}$/.test(cpf)) {
    return false;
    }

    // Calcular dígitos verificadores
    for (let t = 9; t < 11; t++) {
    let soma = 0;
    for (let i = 0; i < t; i++) {
    soma += parseInt(cpf.charAt(i)) * ((t + 1) - i);
    }
    let digito = ((soma * 10) % 11) % 10;
    if (digito !== parseInt(cpf.charAt(t))) {
    return false;
    }
    }

    return true;
}

Essa validação é instantânea e não requer chamada à API. Ela elimina CPFs com formato inválido antes de consumir consultas do seu plano.


Feedback visual em tempo real

Combine a máscara e a validação para fornecer feedback visual imediato ao usuário:

/**
    * Configura validação em tempo real com feedback visual.
    */
function configurarValidacaoCPF(inputId, mensagemId) {
    const input = document.getElementById(inputId);
    const mensagem = document.getElementById(mensagemId);

    // Aplicar máscara
    aplicarMascaraCPF(input);

    // Validar após cada entrada
    input.addEventListener('input', function () {
    const digitos = this.value.replace(/\D/g, '');

    if (digitos.length < 11) {
    // CPF incompleto
    this.className = '';
    mensagem.textContent = '';
    mensagem.className = 'mensagem';
    return;
    }

    if (validarCPFLocal(digitos)) {
    // CPF com formato válido
    this.className = 'valido';
    mensagem.textContent = 'CPF com formato válido';
    mensagem.className = 'mensagem sucesso';
    } else {
    // CPF com formato inválido
    this.className = 'invalido';
    mensagem.textContent = 'CPF inválido. Verifique os dígitos.';
    mensagem.className = 'mensagem erro';
    }
    });
}

Consulta à API via backend

A validação local verifica apenas se o formato matemático está correto. Para confirmar que o CPF existe e obter os dados do titular, é necessário consultar a API da CPFHub.io via backend — nunca expondo a chave de API no front-end.

A chave de API nunca deve ser exposta no front-end. Sempre faça a chamada via backend:

Backend em Node.js (Express)

const express = require('express');
const app = express();

app.get('/api/validar-cpf/:cpf', async (req, res) => {
    const { cpf } = req.params;

    try {
    const response = await fetch(`https://api.cpfhub.io/cpf/${cpf}`, {
    method: 'GET',
    headers: {
    'x-api-key': process.env.CPFHUB_API_KEY,
    'Accept': 'application/json'
    },
    timeout: 10000
    });

    const data = await response.json();
    res.json(data);
    } catch (error) {
    res.status(500).json({ success: false, error: 'Erro na consulta' });
    }
});

app.listen(3000);

Chamada à API no front-end (via backend)

/**
    * Consulta a API (via backend) para verificar os dados do CPF.
    */
async function verificarCPFAPI(cpf) {
    try {
    const response = await fetch(`/api/validar-cpf/${cpf}`, {
    method: 'GET',
    headers: { 'Accept': 'application/json' }
    });

    if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();
    } catch (error) {
    console.error('Erro na verificação:', error);
    return { success: false, error: error.message };
    }
}

Integrando tudo no formulário

Agora vamos conectar todas as peças -- máscara, validação local, consulta à API e envio do formulário:

document.addEventListener('DOMContentLoaded', function () {
    // Configurar campo de CPF
    configurarValidacaoCPF('cpf', 'cpfMensagem');

    // Interceptar envio do formulário
    const form = document.getElementById('formCadastro');
    const btnSubmit = document.getElementById('btnSubmit');
    const resultado = document.getElementById('resultado');

    form.addEventListener('submit', async function (event) {
    event.preventDefault();

    const cpfInput = document.getElementById('cpf');
    const nomeInput = document.getElementById('nome');
    const mensagem = document.getElementById('cpfMensagem');
    const cpf = cpfInput.value.replace(/\D/g, '');

    // Validação local
    if (!validarCPFLocal(cpf)) {
    cpfInput.className = 'invalido';
    mensagem.textContent = 'CPF inválido. Verifique os dígitos.';
    mensagem.className = 'mensagem erro';
    return;
    }

    // Desabilitar botão durante a consulta
    btnSubmit.disabled = true;
    btnSubmit.textContent = 'Verificando...';
    mensagem.textContent = 'Consultando dados...';
    mensagem.className = 'mensagem info';

    // Consultar API via backend
    const data = await verificarCPFAPI(cpf);

    if (data.success) {
    cpfInput.className = 'valido';
    mensagem.textContent = 'CPF verificado com sucesso!';
    mensagem.className = 'mensagem sucesso';

    // Exibir resultado
    document.getElementById('resultadoNome').textContent =
    'Nome: ' + data.data.name;
    document.getElementById('resultadoCPF').textContent =
    'CPF: ' + cpfInput.value;
    resultado.style.display = 'block';

    // Aqui você pode prosseguir com o cadastro
    // enviarCadastro({ cpf, nome: nomeInput.value, ... });
    } else {
    cpfInput.className = 'invalido';
    mensagem.textContent = 'CPF não encontrado. Verifique o número.';
    mensagem.className = 'mensagem erro';
    resultado.style.display = 'none';
    }

    btnSubmit.disabled = false;
    btnSubmit.textContent = 'Cadastrar';
    });
});

Exemplo completo com cURL

Para testar a API diretamente, sem front-end:

curl -X GET https://api.cpfhub.io/cpf/12345678900 \
    -H "x-api-key: SUA_CHAVE_DE_API" \
    -H "Accept: application/json"

Resposta:

{
    "success": true,
    "data": {
    "cpf": "12345678900",
    "name": "João da Silva",
    "nameUpper": "JOAO DA SILVA",
    "gender": "M",
    "birthDate": "15/06/1990",
    "day": 15,
    "month": 6,
    "year": 1990
    }
}

Boas práticas de segurança

Nunca exponha a API key no front-end

A chave de API deve estar apenas no servidor. O front-end chama o backend, que por sua vez chama a API da CPFHub.io.

Implemente rate limiting no seu backend

Para evitar abusos, limite o número de consultas que um único IP ou sessão pode fazer por minuto:

// Exemplo simples de rate limiting
const limitePorIP = {};

function verificarRateLimit(ip) {
    const agora = Date.now();
    if (!limitePorIP[ip]) {
    limitePorIP[ip] = { contagem: 1, inicio: agora };
    return true;
    }

    if (agora - limitePorIP[ip].inicio > 60000) {
    limitePorIP[ip] = { contagem: 1, inicio: agora };
    return true;
    }

    limitePorIP[ip].contagem++;
    return limitePorIP[ip].contagem <= 10; // Máximo 10 consultas por minuto
}

Valide no servidor também

Nunca confie apenas na validação do front-end. O servidor deve validar o formato do CPF antes de chamar a API, pois requisições podem ser enviadas diretamente ao backend sem passar pelo front-end.

Sanitize os dados

Remova caracteres não numéricos do CPF antes de enviar à API e antes de armazenar no banco de dados. Para orientações de segurança mais amplas, o OWASP mantém guias sobre validação e sanitização de entrada de dados.


Compatibilidade de navegadores

A solução apresentada utiliza apenas JavaScript vanilla (puro) e é compatível com todos os navegadores modernos:

NavegadorVersão mínima
Chrome55+
Firefox52+
Safari11+
Edge79+
Opera42+
Samsung Internet6.2+

Para navegadores mais antigos que não suportam fetch, utilize XMLHttpRequest como fallback ou um polyfill.


Perguntas frequentes

Por que fazer a validação local antes de chamar a API?

A validação dos dígitos verificadores é instantânea e gratuita. Ela elimina CPFs matematicamente inválidos (digitados errado, sequências como 111.111.111-11) antes de consumir uma consulta do seu plano. A regra prática é: valide localmente primeiro, só chame a API quando o formato estiver correto.

A chave de API pode ser usada diretamente no JavaScript do front-end?

Não. Qualquer pessoa que inspecionar o código-fonte ou o tráfego de rede encontraria a chave e poderia consumi-la em nome da sua conta. Sempre faça a chamada à API pelo servidor, usando variáveis de ambiente para armazenar a chave (process.env.CPFHUB_API_KEY no Node.js).

O que fazer quando a API retorna success: false?

Exiba uma mensagem pedindo que o usuário verifique o número digitado. Não informe se o CPF "não existe" — isso pode ser explorado para enumerar CPFs válidos. Registre o evento no backend para monitorar tentativas suspeitas e aplique rate limiting por IP para dificultar ataques de força bruta.

Como adaptar essa solução para frameworks como React ou Vue?

A lógica de validação local (validarCPFLocal) e a máscara (aplicarMascaraCPF) funcionam como funções puras e podem ser importadas em qualquer componente. A chamada à API continua via backend — em React, por exemplo, use useEffect com debounce para disparar a consulta quando o CPF atingir 11 dígitos válidos.


Conclusão

Integrar a validação de CPF em formulários HTML com JavaScript puro é uma solução leve, performática e universalmente compatível. A combinação de máscara de formatação automática, validação local dos dígitos verificadores e consulta à API da CPFHub.io entrega uma experiência fluida para o usuário e segurança real para o sistema. Com tempo de resposta de aproximadamente 900ms e suporte a mais de 13 linguagens no backend, a integração funciona em qualquer stack tecnológica.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e adicione validação de CPF ao seu formulário HTML hoje mesmo.

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp