Como implementar autoComplete de CPF para usuários recorrentes

Aprenda a implementar autocomplete de CPF para usuários recorrentes com segurança, localStorage e validação via API CPFHub.

Redação CPFHub.io
Redação CPFHub.io
··9 min de leitura
Como implementar autoComplete de CPF para usuários recorrentes

Para implementar autocomplete de CPF para usuários recorrentes, armazene no localStorage apenas a versão mascarada do CPF (ex: 123.***.***-09) e um hash de identificação — nunca o número completo. Ao retornar ao site, o usuário vê a sugestão, confirma a identidade e redigita o CPF para revalidação via API. Isso reduz o atrito sem abrir mão da segurança exigida pela LGPD.

Introdução

Usuários recorrentes -- aqueles que voltam ao seu site ou aplicação com frequência -- não deveriam precisar redigitar o CPF a cada nova interação. Um sistema de autocomplete de CPF bem implementado reduz o atrito no preenchimento de formulários, melhora a experiência do usuário e aumenta a velocidade de checkout ou cadastro.

Contudo, o CPF é um dado sensível, e qualquer implementação de autocomplete precisa equilibrar conveniência e segurança.


Autocomplete nativo do navegador

A forma mais simples de autocomplete é utilizar os atributos HTML que permitem ao navegador salvar e sugerir dados:

<div class="form-group">
    <label for="cpf">CPF</label>
    <input
    type="text"
    id="cpf"
    name="cpf"
    autocomplete="off"
    inputmode="numeric"
    maxlength="14"
    placeholder="000.000.000-00"
    />
</div>

Limitações do autocomplete nativo

Para o CPF, o autocomplete nativo do navegador apresenta limitações:

  • Sem padrão específico: não existe um valor autocomplete oficial para CPF.
  • Formatação inconsistente: o navegador pode salvar com ou sem máscara.
  • Sem validação: o dado sugerido não é revalidado automaticamente.

Por isso, uma solução customizada é geralmente mais adequada.


Estratégia 1 -- Autocomplete com localStorage seguro

Armazene uma versão mascarada do CPF no localStorage para exibição, junto com um hash para identificação:

// Módulo de armazenamento seguro de CPF
var CpfStorage = (function () {
    var STORAGE_KEY = "cpfhub_saved_cpfs";
    var MAX_ENTRIES = 5;

    function hashCpf(cpf) {
    // Hash simples para identificação (não use para segurança real)
    var hash = 0;
    for (var i = 0; i < cpf.length; i++) {
    var char = cpf.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash;
    }
    return hash.toString(36);
    }

    function maskCpf(cpf) {
    // 123.456.789-09 -> 123.***.***-09
    var clean = cpf.replace(/\D/g, "");
    return clean.slice(0, 3) + ".***.***-" + clean.slice(9, 11);
    }

    function save(cpf, name) {
    var clean = cpf.replace(/\D/g, "");
    var entries = getAll();

    var existing = entries.findIndex(function (e) {
    return e.hash === hashCpf(clean);
    });

    if (existing >= 0) {
    entries[existing].lastUsed = Date.now();
    } else {
    entries.push({
    masked: maskCpf(clean),
    hash: hashCpf(clean),
    name: name,
    lastUsed: Date.now(),
    });
    }

    // Manter apenas as entradas mais recentes
    entries.sort(function (a, b) {
    return b.lastUsed - a.lastUsed;
    });
    entries = entries.slice(0, MAX_ENTRIES);

    try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
    } catch (e) {
    // Silenciar erros de quota ou modo privado
    }
    }

    function getAll() {
    try {
    var raw = localStorage.getItem(STORAGE_KEY);
    return raw ? JSON.parse(raw) : [];
    } catch (e) {
    return [];
    }
    }

    function clear() {
    localStorage.removeItem(STORAGE_KEY);
    }

    return { save: save, getAll: getAll, clear: clear, maskCpf: maskCpf };
})();

Componente de autocomplete dropdown

<div class="cpf-autocomplete" id="cpf-autocomplete">
    <div class="cpf-autocomplete__input-wrapper">
    <input
    type="text"
    id="cpf"
    class="cpf-autocomplete__input"
    inputmode="numeric"
    maxlength="14"
    placeholder="000.000.000-00"
    autocomplete="off"
    />
    <div class="cpf-autocomplete__icon" id="cpf-icon"></div>
    </div>
    <div class="cpf-autocomplete__dropdown" id="cpf-dropdown"></div>
    <div class="cpf-autocomplete__feedback" id="cpf-feedback" role="status" aria-live="polite"></div>
</div>
.cpf-autocomplete {
    position: relative;
    max-width: 400px;
}

.cpf-autocomplete__input-wrapper {
    position: relative;
}

.cpf-autocomplete__input {
    width: 100%;
    padding: 12px 44px 12px 16px;
    font-size: 16px;
    border: 2px solid #d1d5db;
    border-radius: 8px;
    outline: none;
    transition: border-color 0.2s ease;
}

.cpf-autocomplete__input:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}

.cpf-autocomplete__dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 0 0 8px 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    display: none;
    z-index: 100;
    max-height: 200px;
    overflow-y: auto;
}

.cpf-autocomplete__dropdown.visible {
    display: block;
}

.cpf-autocomplete__item {
    padding: 10px 16px;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    transition: background-color 0.15s ease;
}

.cpf-autocomplete__item:hover {
    background: #f1f5f9;
}

.cpf-autocomplete__item-cpf {
    font-family: monospace;
    font-size: 14px;
    color: #1e293b;
}

.cpf-autocomplete__item-name {
    font-size: 13px;
    color: #64748b;
}

.cpf-autocomplete__feedback {
    font-size: 13px;
    margin-top: 6px;
    min-height: 20px;
}

JavaScript do autocomplete

var cpfInput = document.getElementById("cpf");
var dropdown = document.getElementById("cpf-dropdown");
var feedback = document.getElementById("cpf-feedback");
var debounceTimer = null;

// Mostrar sugestões ao focar
cpfInput.addEventListener("focus", function () {
    var entries = CpfStorage.getAll();
    if (entries.length > 0 && this.value === "") {
    showDropdown(entries);
    }
});

// Esconder dropdown ao clicar fora
document.addEventListener("click", function (e) {
    if (!e.target.closest("#cpf-autocomplete")) {
    dropdown.classList.remove("visible");
    }
});

function showDropdown(entries) {
    dropdown.innerHTML = entries
    .map(function (entry, index) {
    return (
    '<div class="cpf-autocomplete__item" data-index="' + index + '">' +
    '<span class="cpf-autocomplete__item-cpf">' + entry.masked + "</span>" +
    '<span class="cpf-autocomplete__item-name">' + entry.name + "</span>" +
    "</div>"
    );
    })
    .join("");

    dropdown.classList.add("visible");

    // Eventos de clique nas sugestões
    dropdown.querySelectorAll(".cpf-autocomplete__item").forEach(function (item) {
    item.addEventListener("click", function () {
    // O usuário precisa redigitar -- mostramos apenas para identificação
    feedback.textContent =
    "Por segurança, por favor redigite o CPF completo.";
    feedback.style.color = "#6b7280";
    cpfInput.focus();
    dropdown.classList.remove("visible");
    });
    });
}

// Formatação e validação ao digitar
cpfInput.addEventListener("input", function () {
    dropdown.classList.remove("visible");

    var v = this.value.replace(/\D/g, "").slice(0, 11);
    if (v.length > 9)
    v = v.replace(/(\d{3})(\d{3})(\d{3})(\d{1,2})/, "$1.$2.$3-$4");
    else if (v.length > 6)
    v = v.replace(/(\d{3})(\d{3})(\d{1,3})/, "$1.$2.$3");
    else if (v.length > 3)
    v = v.replace(/(\d{3})(\d{1,3})/, "$1.$2");
    this.value = v;

    if (debounceTimer) clearTimeout(debounceTimer);
    var digits = v.replace(/\D/g, "");
    if (digits.length === 11) {
    debounceTimer = setTimeout(function () {
    validateAndSave(digits);
    }, 500);
    }
});

async function validateAndSave(cpf) {
    feedback.textContent = "Validando...";
    feedback.style.color = "#6b7280";

    var controller = new AbortController();
    var timeoutId = setTimeout(function () {
    controller.abort();
    }, 10000);

    try {
    var res = await fetch("https://api.cpfhub.io/cpf/" + cpf, {
    headers: {
    "x-api-key": "SUA_CHAVE_DE_API",
    Accept: "application/json",
    },
    signal: controller.signal,
    });
    clearTimeout(timeoutId);
    var data = await res.json();

    if (data.success) {
    feedback.textContent = "CPF válido - " + data.data.name;
    feedback.style.color = "#059669";

    // Salvar para autocomplete futuro
    CpfStorage.save(cpf, data.data.name);
    } else {
    feedback.textContent = "CPF não encontrado.";
    feedback.style.color = "#dc2626";
    }
    } catch (err) {
    clearTimeout(timeoutId);
    feedback.textContent = "Erro na validação.";
    feedback.style.color = "#dc2626";
    }
}

Estratégia 2 -- Autocomplete via conta do usuário

Para plataformas com sistema de login, o autocomplete mais seguro é buscar o CPF da conta do usuário no backend:

// Ao carregar a página, verificar se o usuário está logado
async function prefillCpf() {
    try {
    var controller = new AbortController();
    var timeoutId = setTimeout(function () {
    controller.abort();
    }, 5000);

    var res = await fetch("/api/user/profile", {
    credentials: "include",
    signal: controller.signal,
    });
    clearTimeout(timeoutId);
    var user = await res.json();

    if (user && user.cpf) {
    var cpfField = document.getElementById("cpf");
    var masked = CpfStorage.maskCpf(user.cpf);
    cpfField.value = masked;
    cpfField.dataset.prefilled = "true";

    var feedback = document.getElementById("cpf-feedback");
    feedback.textContent = "CPF da sua conta. Clique para editar.";
    feedback.style.color = "#059669";
    }
    } catch (err) {
    // Silenciar -- preenchimento é opcional
    }
}

document.addEventListener("DOMContentLoaded", prefillCpf);

Segurança do armazenamento local

O que armazenar

  • Nunca armazene o CPF completo no localStorage ou cookies.
  • Armazene apenas a versão mascarada (ex: 123.***.***-09) e um hash.
  • Armazene o nome associado para facilitar a identificação.

Limpeza de dados

Ofereça ao usuário a opção de limpar dados salvos:

<button type="button" class="btn-clear-data" onclick="CpfStorage.clear()">
    Limpar CPFs salvos
</button>

Consentimento

Antes de salvar no localStorage, peça consentimento:

function saveCpfWithConsent(cpf, name) {
    var consentKey = "cpfhub_storage_consent";
    if (localStorage.getItem(consentKey) === "true") {
    CpfStorage.save(cpf, name);
    return;
    }

    if (
    confirm(
    "Deseja salvar este CPF para preenchimento automático em futuras visitas?"
    )
    ) {
    localStorage.setItem(consentKey, "true");
    CpfStorage.save(cpf, name);
    }
}

Para acessibilidade, o dropdown deve ser navegável por teclado:

cpfInput.addEventListener("keydown", function (e) {
    var items = dropdown.querySelectorAll(".cpf-autocomplete__item");
    if (!items.length || !dropdown.classList.contains("visible")) return;

    var active = dropdown.querySelector(".cpf-autocomplete__item--active");
    var index = active ? Array.from(items).indexOf(active) : -1;

    if (e.key === "ArrowDown") {
    e.preventDefault();
    if (active) active.classList.remove("cpf-autocomplete__item--active");
    index = (index + 1) % items.length;
    items[index].classList.add("cpf-autocomplete__item--active");
    } else if (e.key === "ArrowUp") {
    e.preventDefault();
    if (active) active.classList.remove("cpf-autocomplete__item--active");
    index = (index - 1 + items.length) % items.length;
    items[index].classList.add("cpf-autocomplete__item--active");
    } else if (e.key === "Enter" && active) {
    e.preventDefault();
    active.click();
    } else if (e.key === "Escape") {
    dropdown.classList.remove("visible");
    }
});

Perguntas frequentes

É seguro salvar CPF no localStorage para autocomplete?

Desde que você armazene apenas a versão mascarada (ex: 123.***.***-09) e um hash — nunca o CPF completo — o risco é baixo. O localStorage é acessível apenas pelo mesmo domínio, mas não é criptografado, então evite dados completos. A ANPD orienta que dados pessoais devem ser tratados com o princípio da minimização: colete e armazene apenas o estritamente necessário.

Por que o usuário precisa redigitar o CPF mesmo com autocomplete?

A sugestão serve para identificar qual CPF usar, não para preencher automaticamente. Exigir a redigitação garante que o titular está presente e consciente da ação — o que é especialmente relevante em dispositivos compartilhados e em fluxos regulados pela LGPD.

Como implementar autocomplete de CPF em SPAs com React ou Vue?

Encapsule o CpfStorage em um hook customizado (useCpfAutocomplete) ou composable Vue. Use useEffect/onMounted para carregar as sugestões do localStorage ao montar o componente e chame a API CPFHub.io com debounce de 500ms após o usuário digitar os 11 dígitos.

O que fazer se o usuário estiver em modo de navegação privada?

O localStorage não persiste em modo privado. Trate o erro silenciosamente no bloco try/catch do CpfStorage.save() — o formulário continua funcionando normalmente, só sem a sugestão de autocomplete. Para usuários logados, a estratégia via backend (Estratégia 2) funciona independentemente do modo de navegação.


Conclusão

O autocomplete de CPF para usuários recorrentes melhora a experiência em formulários de uso frequente. A chave é equilibrar conveniência e segurança -- nunca armazenando o CPF completo, sempre pedindo consentimento e revalidando o dado quando necessário. A CPFHub.io oferece validação em ~900ms, uptime de 99,9% e conformidade total com a LGPD, garantindo que cada consulta seja segura e confiável.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito.

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