DesenvolvedorWordPress WhatsApp

AJAX no WordPress: admin-ajax e REST API na prática

O WordPress oferece dois caminhos para requisições assíncronas: o clássico admin-ajax.php e a REST API moderna. Veja como implementar cada um com segurança — hooks, nonce, wp_localize_script e wp_send_json_* — e, principalmente, quando escolher um ou outro.

Precisa de uma interação dinâmica no seu site?

Atualizado em 19/06/2026

AJAX é o que permite a uma página trocar dados com o servidor sem recarregar — curtir um post, filtrar produtos, carregar mais itens, validar um formulário em tempo real. No WordPress há dois caminhos oficiais para isso: o veterano admin-ajax.php, que existe desde os primórdios do painel, e a REST API, incorporada ao núcleo na versão 4.7. Este artigo mostra os dois na prática, com a segurança que eles exigem, e explica em que cenário cada um brilha. Spoiler: a resposta quase nunca é "sempre use X" — é "depende do contexto".

Os dois caminhos, lado a lado

Antes do código, fixe o modelo mental. Ambos recebem uma requisição do navegador e devolvem dados (em geral JSON), mas têm naturezas diferentes:

Caminho 1: admin-ajax.php (o clássico)

O fluxo tem três peças: (1) enfileirar o script e passar a ele a URL do admin-ajax.php e um nonce; (2) o JavaScript que faz a requisição; (3) o handler PHP que valida e responde.

Os hooks wp_ajax_ e wp_ajax_nopriv_

Cada ação é identificada por um nome (ex.: curtir_post) e atendida por hooks dinâmicos:

Se a funcionalidade for pública, registre os dois apontando para o mesmo handler. Esquecer o nopriv é a causa mais comum de "funciona quando estou logado, mas no site some" — o WordPress simplesmente devolve 0 para o visitante anônimo, porque nenhum hook respondeu àquela ação.

Passo 1 — Enfileirar o JS e passar ajaxurl + nonce

No painel a variável global ajaxurl já existe; no front-end, não. Por isso passamos a URL (sempre via admin_url()) e um nonce ao script com wp_localize_script():

<?php
add_action( 'wp_enqueue_scripts', 'meu_plugin_enfileirar_ajax' );

function meu_plugin_enfileirar_ajax() {
    wp_enqueue_script(
        'meu-ajax',
        plugins_url( 'js/ajax.js', __FILE__ ),
        array(),       // sem dependência de jQuery: usaremos fetch()
        '1.0.0',
        true           // no rodapé
    );

    // Entrega a URL do endpoint e um nonce ao JavaScript.
    wp_localize_script( 'meu-ajax', 'MEU_AJAX', array(
        'url'   => admin_url( 'admin-ajax.php' ),
        'nonce' => wp_create_nonce( 'curtir_post_nonce' ),
    ) );
}

Em código mais novo, wp_add_inline_script() é uma alternativa a wp_localize_script() para injetar dados — este último, apesar do nome, é a forma historicamente usada para passar variáveis ao JS, não só traduções. Ambos dependem de o script já ter sido registrado/enfileirado antes. Veja o artigo sobre enfileirar scripts e estilos.

Passo 2 — O JavaScript (fetch)

O front-end faz um POST para a URL recebida, mandando o parâmetro obrigatório action (que casa com o nome do hook, sem o prefixo) e o nonce:

// js/ajax.js
document.querySelector( '.btn-curtir' )?.addEventListener( 'click', async ( ev ) => {
    const botao = ev.currentTarget;
    const postId = botao.dataset.postId;

    // admin-ajax espera dados de formulário (não JSON cru).
    const corpo = new URLSearchParams( {
        action:  'curtir_post',          // = wp_ajax_curtir_post
        post_id: postId,
        _ajax_nonce: MEU_AJAX.nonce,     // nome que o check_ajax_referer lê
    } );

    const resp = await fetch( MEU_AJAX.url, {
        method: 'POST',
        credentials: 'same-origin',      // envia o cookie de login
        body: corpo,
    } );

    const json = await resp.json();
    if ( json.success ) {
        botao.textContent = 'Curtido (' + json.data.total + ')';
    } else {
        console.error( json.data?.mensagem || 'Erro ao curtir.' );
    }
} );

Repare em credentials: 'same-origin': sem isso, o navegador pode não enviar o cookie de sessão, e o WordPress trataria a chamada como deslogada (caindo no hook nopriv). O nome _ajax_nonce é a chave padrão que o check_ajax_referer() procura automaticamente.

Muito código WordPress ainda usa jQuery, que acompanha o núcleo. A mesma requisição com jQuery.post() fica assim — declare jquery como dependência no wp_enqueue_script para garantir que ele já esteja carregado:

// js/ajax.js — versão com jQuery
jQuery( function ( $ ) {
    $( '.btn-curtir' ).on( 'click', function () {
        const botao = $( this );

        $.post( MEU_AJAX.url, {
            action:      'curtir_post',      // = wp_ajax_curtir_post
            post_id:     botao.data( 'postId' ),
            _ajax_nonce: MEU_AJAX.nonce,
        } ).done( function ( res ) {
            if ( res.success ) {
                botao.text( 'Curtido (' + res.data.total + ')' );
            } else {
                console.error( res.data && res.data.mensagem );
            }
        } ).fail( function () {
            console.error( 'Falha de rede no AJAX.' );
        } );
    } );
} );

O jQuery.post() já envia o cookie e serializa os dados como formulário automaticamente, o que torna o código mais enxuto; em projetos novos, porém, fetch() dispensa a dependência. Escolha conforme o que o tema/plugin já carrega — não inclua jQuery só para isso.

Passo 3 — O handler PHP

O handler verifica o nonce, checa permissão quando aplicável, processa e responde com wp_send_json_success() ou wp_send_json_error():

<?php
add_action( 'wp_ajax_curtir_post',        'meu_plugin_curtir_post' );
add_action( 'wp_ajax_nopriv_curtir_post', 'meu_plugin_curtir_post' );

function meu_plugin_curtir_post() {

    // 1) Nonce: barra requisições forjadas (CSRF). Encerra sozinho se falhar.
    check_ajax_referer( 'curtir_post_nonce' );

    // 2) Valide/sanitize a entrada — nunca confie no que chega.
    $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;

    if ( ! $post_id || 'publish' !== get_post_status( $post_id ) ) {
        wp_send_json_error(
            array( 'mensagem' => 'Post inválido.' ),
            400 // status HTTP
        );
    }

    // 3) Lógica de negócio (exemplo: incrementar um contador em post meta).
    $total = (int) get_post_meta( $post_id, 'curtidas', true ) + 1;
    update_post_meta( $post_id, 'curtidas', $total );

    // 4) Resposta. wp_send_json_success() imprime o JSON e encerra (wp_die).
    wp_send_json_success( array( 'total' => $total ) );
}

Nunca dê echo, print ou return depois de wp_send_json_success()/wp_send_json_error() — elas já encerram a execução com wp_die(). Qualquer saída extra corromperia o JSON. E lembre: verificar o nonce não é verificar permissão. O nonce só prova que a requisição veio do seu site; se a ação for sensível, adicione current_user_can() para confirmar que aquele usuário pode executá-la.

Caminho 2: REST API (o moderno)

Para o mesmo objetivo, a REST API oferece uma rota dedicada em vez de um action genérico. A estrutura, a validação de args e o permission_callback são o tema completo do artigo de REST API do WordPress; aqui fica o contraste essencial — uma rota semântica, com método HTTP explícito:

<?php
add_action( 'rest_api_init', function () {
    register_rest_route( 'meu-plugin/v1', '/curtir/(?P<id>\d+)', array(
        'methods'             => WP_REST_Server::CREATABLE, // POST
        'callback'            => 'meu_plugin_rest_curtir',
        'permission_callback' => '__return_true',           // leitura/escrita pública (consciente)
        'args'                => array(
            'id' => array(
                'required'          => true,
                'sanitize_callback' => 'absint',
            ),
        ),
    ) );
} );

No navegador, a chamada usa o cabeçalho X-WP-Nonce (em vez de um campo _ajax_nonce no corpo) e uma URL limpa como /wp-json/meu-plugin/v1/curtir/42. A resposta pode trazer cabeçalhos de cache, algo que o admin-ajax.php não oferece de fábrica.

Quando usar cada um

Aspectoadmin-ajax.phpREST API
Overhead por chamadaCarrega o ambiente WP inteiro a cada requisiçãoTambém carrega o WP, mas com fluxo dedicado a APIs
Cache HTTPNão cacheável (POST para um único arquivo)GET cacheável; aceita Cache-Control/CDN
URLGenérica: admin-ajax.php?action=...Semântica: /wp-json/ns/v1/recurso
AutenticaçãoCookie + nonce (_ajax_nonce)Cookie+nonce, Application Passwords, JWT/OAuth
Validação de entradaManual no handlerDeclarativa via args (validate/sanitize)
Quando preferirAções do painel, código legado, operações simples ligadas à sessãoLeitura pública em escala, integrações externas, apps, headless

Por que isso importa para a performance: o admin-ajax.php sobe todo o ambiente do WordPress a cada requisição e, por ser um POST, não passa por cache de página nem por CDN. Em uma operação de leitura chamada milhares de vezes (um filtro de catálogo, por exemplo), isso vira gargalo. A REST API, com endpoints de leitura via GET, permite cache e alivia o servidor — por isso costuma vencer em escala. Para uma ação ocasional do administrador, a diferença é irrelevante.

Segurança: as regras que valem para os dois

Armadilhas comuns (e como evitar)

SintomaCausa provávelCorreção
Funciona logado, falha deslogadoFaltou o hook wp_ajax_nopriv_Registre as duas ações para uso público
A resposta vem só 0Nenhum hook atende àquela actionConfira o nome da action e o hook correspondente
ajaxurl is not definedVariável só existe no adminPasse a URL via wp_localize_script no front-end
JSON corrompido / lixo no fimecho ou saída após wp_send_json_*Não imprima nada depois delas (já chamam wp_die)
403 / "nonce inválido"Nonce ausente, expirado ou de outra açãoGere e envie o nonce certo; renove se a página ficou aberta
Cookie não enviadofetch sem credentialsUse credentials: 'same-origin'

Esse handler de AJAX normalmente vive em um plugin próprio e se conecta ao enfileiramento correto de assets. Para decidir entre os dois caminhos com mais profundidade, leia a REST API do WordPress e explore os demais temas de desenvolvimento.

Quer interações dinâmicas no seu site, feitas com segurança?

Implemento AJAX e endpoints REST com nonce, validação e a abordagem certa para o seu caso — sem gambiarra e sem brecha de segurança. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

Preciso mesmo dos dois hooks, wp_ajax_ e wp_ajax_nopriv_?

Depende de quem usa a funcionalidade. O hook wp_ajax_{action} só atende usuários logados; o wp_ajax_nopriv_{action} atende visitantes deslogados. Se o recurso é só do painel (admin), basta o primeiro. Se um visitante anônimo precisa usá-lo (um formulário público, um filtro de catálogo), você precisa registrar os dois — é o erro nº 1 de quem vê o AJAX "funcionar logado e quebrar deslogado".

Por que o ajaxurl funciona no admin mas é undefined no front-end?

No painel (wp-admin), o WordPress já declara a variável global JavaScript ajaxurl apontando para o admin-ajax.php. No front-end ela não existe — você precisa passá-la ao seu script, normalmente com wp_localize_script() ou wp_add_inline_script(), junto de um nonce. Tentar usar ajaxurl no site público sem isso resulta em ReferenceError.

Onde a URL do admin-ajax.php é obtida no PHP?

Sempre com admin_url('admin-ajax.php'), nunca com uma string fixa. Essa função respeita o domínio, o protocolo (HTTP/HTTPS) e instalações em subdiretório, gerando a URL correta em qualquer ambiente. Cravar a URL na mão quebra em multisite, em HTTPS misto e ao migrar o site.

O que o check_ajax_referer faz exatamente?

Ele verifica o nonce enviado pela requisição contra uma ação. Se o nonce for inválido ou tiver expirado, por padrão a função encerra a execução com uma resposta de erro — protegendo o handler contra CSRF (requisições forjadas por outro site). Você o chama logo no início do handler, antes de qualquer processamento. É o equivalente, no admin-ajax, ao papel do cabeçalho X-WP-Nonce na REST API.

Por que wp_send_json_success encerra o script sozinho?

As funções wp_send_json_success() e wp_send_json_error() imprimem a resposta JSON (com a estrutura {success, data}), enviam o cabeçalho Content-Type correto e chamam wp_die() internamente. Por isso você não deve dar echo nem return manual depois delas — qualquer saída extra contaminaria o JSON. Esse encerramento automático evita o clássico "0" ou lixo no final da resposta.

AJAX clássico ou REST API: qual usar em 2024+?

Para leitura pública e em escala, a REST API tende a ser melhor: respostas cacheáveis, URLs semânticas e autenticação padronizada. O admin-ajax continua ótimo para ações pontuais do painel, para quem já tem código legado nele, ou para operações simples ligadas à sessão do usuário. Não é "antigo contra moderno" — são ferramentas para contextos diferentes, e ambas exigem nonce e validação de entrada.

Referências oficiais

  1. WordPress.org — AJAX (Plugin Handbook)
  2. WordPress.org — wp_ajax_{action} (Hook Reference)
  3. WordPress.org — wp_ajax_nopriv_{action} (Hook Reference)
  4. WordPress.org — wp_localize_script() (Code Reference)
  5. WordPress.org — Nonces (Security API)
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui