DesenvolvedorWordPress WhatsApp

REST API do WordPress: criar endpoints customizados

A REST API transforma o WordPress em um back-end de dados acessível por HTTP e JSON. Veja como registrar rotas próprias com register_rest_route(), validar e sanitizar entradas, proteger tudo com permission_callback e escolher a autenticação certa para cada cenário.

Precisa de uma API sob medida no WordPress?

Atualizado em 19/06/2026

A REST API do WordPress expõe o conteúdo e as operações do site como recursos HTTP que retornam JSON. É o que permite o editor de blocos (Gutenberg) salvar posts, um app mobile listar artigos, ou um sistema externo criar pedidos — tudo sem renderizar HTML. Faz parte do núcleo desde o WordPress 4.7 (dezembro de 2016), então está disponível em qualquer instalação atual. Neste artigo você vai aprender a estendê-la com endpoints customizados, do registro da rota à autenticação, com a atenção a segurança que separa um especialista de quem só copia trechos prontos.

Anatomia: namespace, route e endpoint

Toda a API vive sob a base /wp-json/. A partir daí, três conceitos se encaixam um dentro do outro:

Na prática, fazer um GET /wp-json/wp/v2/posts devolve os últimos posts em JSON; GET /wp-json/wp/v2/posts/42 devolve o post 42. Antes de criar qualquer rota, vale conhecer esses endpoints padrão — muitas vezes eles já resolvem o problema.

Para descobrir tudo que a API expõe em um site, faça um GET em /wp-json/ (o índice) ou em /wp-json/wp/v2/. A resposta é auto-documentada: lista namespaces, rotas, métodos e os argumentos aceitos por cada um.

Registrando uma rota com register_rest_route()

O registro deve acontecer no hook rest_api_init — é o momento em que a API está montando suas rotas. A função-chave é register_rest_route( $namespace, $route, $args ). O exemplo abaixo registra dois endpoints na mesma route: um GET para ler um livro pelo ID e um POST para criar um livro.

<?php
add_action( 'rest_api_init', 'meu_plugin_registrar_rotas' );

function meu_plugin_registrar_rotas() {

    register_rest_route( 'meu-plugin/v1', '/livros/(?P<id>\d+)', array(

        // Endpoint de LEITURA: GET /wp-json/meu-plugin/v1/livros/42
        array(
            'methods'             => WP_REST_Server::READABLE,   // 'GET'
            'callback'            => 'meu_plugin_obter_livro',
            'permission_callback' => '__return_true',            // leitura pública (consciente)
            'args'                => array(
                'id' => array(
                    'description'       => 'ID do livro (post).',
                    'type'              => 'integer',
                    'required'          => true,
                    'validate_callback' => function ( $valor ) {
                        return is_numeric( $valor ) && (int) $valor > 0;
                    },
                    'sanitize_callback' => 'absint',
                ),
            ),
        ),

        // Endpoint de ESCRITA: POST /wp-json/meu-plugin/v1/livros/42
        array(
            'methods'             => WP_REST_Server::CREATABLE,  // 'POST'
            'callback'            => 'meu_plugin_atualizar_livro',
            'permission_callback' => function () {
                // Só quem pode editar posts grava aqui.
                return current_user_can( 'edit_posts' );
            },
            'args'                => array(
                'titulo' => array(
                    'type'              => 'string',
                    'required'          => true,
                    'sanitize_callback' => 'sanitize_text_field',
                ),
            ),
        ),
    ) );
}

Repare em três decisões de especialista. Primeiro, o parâmetro id é capturado pela regex (?P<id>\d+) — o \d+ garante que só dígitos cheguem à rota, e o nome id torna o valor acessível no callback. Segundo, cada argumento tem validate_callback (rejeita o que não serve) e sanitize_callback (limpa o que passou). Terceiro, os métodos usam as constantes de WP_REST_Server em vez de strings soltas — é mais legível e à prova de erro de digitação.

O permission_callback não é opcional

Desde o WordPress 5.5, registrar uma rota sem permission_callback dispara um aviso de doing it wrong. A razão é didática e de segurança: o desenvolvedor precisa decidir explicitamente quem pode acessar cada endpoint, em vez de deixar tudo aberto por descuido. O callback pode devolver:

__return_true libera o endpoint para qualquer pessoa na internet, autenticada ou não. Use-o apenas em leituras de dados que já seriam públicos no site. Para qualquer escrita, exclusão, ou dado privado, verifique uma capability com current_user_can() — nunca confie só no fato de o usuário estar logado.

O callback: WP_REST_Request, WP_REST_Response e WP_Error

O callback recebe um único objeto WP_REST_Request, de onde você lê os parâmetros (de URL, query string ou corpo JSON) com sintaxe de array: $req['id']. Ele deve retornar dados (que viram JSON), um objeto WP_REST_Response (quando você quer controlar status e cabeçalhos) ou um WP_Error (para falhas com código HTTP apropriado).

<?php
function meu_plugin_obter_livro( WP_REST_Request $req ) {

    $id   = (int) $req['id'];           // já sanitizado por absint
    $post = get_post( $id );

    // Recurso inexistente ou tipo errado -> 404 explícito.
    if ( ! $post || 'livro' !== $post->post_type ) {
        return new WP_Error(
            'livro_nao_encontrado',
            'Nenhum livro com esse ID.',
            array( 'status' => 404 )
        );
    }

    // Monte só o que pode ser exposto — não devolva o objeto WP_Post cru.
    $dados = array(
        'id'        => $post->ID,
        'titulo'    => get_the_title( $post ),
        'resumo'    => get_the_excerpt( $post ),
        'permalink' => get_permalink( $post ),
    );

    $resposta = new WP_REST_Response( $dados, 200 );
    $resposta->header( 'Cache-Control', 'public, max-age=300' );
    return $resposta;
}

Dois pontos cruciais. O WP_Error com array( 'status' => 404 ) faz a API responder com o código HTTP correto — clientes bem-feitos dependem disso para distinguir "não existe" de "erro do servidor". E jamais devolva o objeto WP_Post inteiro: ele carrega campos internos (como post_password ou conteúdo de rascunhos) que não deveriam vazar. Monte explicitamente o array de saída com apenas o que é público.

Métodos HTTP e as constantes de WP_REST_Server

Cada verbo HTTP tem uma constante de atalho. Usá-las evita erros sutis (a constante EDITABLE, por exemplo, cobre três verbos de uma vez):

IntençãoMétodo(s) HTTPConstante
Ler um recursoGETWP_REST_Server::READABLE
Criar um recursoPOSTWP_REST_Server::CREATABLE
Atualizar um recursoPOST, PUT, PATCHWP_REST_Server::EDITABLE
Apagar um recursoDELETEWP_REST_Server::DELETABLE
Qualquer métodoGET, POST, PUT, PATCH, DELETEWP_REST_Server::ALLMETHODS

Autenticação: escolha pelo contexto

A REST API só sabe quem está chamando se a requisição carregar uma forma de identidade. As leituras de dados públicos não exigem nada; tudo que é privado ou de escrita, sim. Há três caminhos principais.

1. Cookie + nonce (same-origin)

É o método para chamadas feitas de dentro do próprio site (tema, plugin, painel) por um usuário já logado. O cookie de sessão viaja automaticamente; o que falta é provar que a chamada não vem de outro domínio (proteção CSRF). Isso é feito com um nonce enviado no cabeçalho X-WP-Nonce. Gere o nonce com a ação wp_rest e entregue-o ao JavaScript:

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

function meu_plugin_assets_rest() {
    wp_enqueue_script(
        'meu-app',
        plugins_url( 'js/app.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // Entrega a raiz da API e um nonce válido ao front-end.
    wp_localize_script( 'meu-app', 'MEU_APP', array(
        'root'  => esc_url_raw( rest_url( 'meu-plugin/v1/' ) ),
        'nonce' => wp_create_nonce( 'wp_rest' ),
    ) );
}
// js/app.js  — consumindo o endpoint com fetch()
async function atualizarLivro( id, titulo ) {
    const resp = await fetch( MEU_APP.root + 'livros/' + id, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': MEU_APP.nonce,   // prova same-origin + login
        },
        body: JSON.stringify( { titulo: titulo } ),
    } );

    if ( ! resp.ok ) {
        // 4xx/5xx: leia o erro JSON que o WordPress devolve.
        const erro = await resp.json();
        throw new Error( erro.message || 'Falha na requisição.' );
    }
    return resp.json();
}

O nonce do WordPress tem vida curta: é aceito no tick atual e no anterior, e cada tick vale metade do nonce_life (24h por padrão), então a validade efetiva fica entre 12 e 24 horas. Em páginas que ficam abertas muito tempo, ele pode expirar e a API passa a responder 403 com o código rest_cookie_invalid_nonce — nesses casos, atualize a página ou renove o nonce.

2. Application Passwords (acesso externo)

Para clientes que rodam fora do site (um servidor, um script, um app), o nonce não se aplica. Desde o WordPress 5.6, o núcleo traz Application Passwords: cada usuário gera, no próprio perfil, senhas dedicadas por aplicação, enviadas via HTTP Basic Auth. A grande vantagem é que você pode revogar uma aplicação sem trocar a senha principal da conta.

# Exemplo com curl — sempre sobre HTTPS, nunca HTTP puro.
# O usuário e a senha de aplicação vão no Basic Auth.
curl --user "usuario:xxxx yyyy zzzz aaaa bbbb cccc" \
     https://seusite.com.br/wp-json/wp/v2/posts \
     -H "Content-Type: application/json" \
     -d '{"title":"Post via API","status":"draft"}'

Application Passwords só funcionam sobre HTTPS (o WordPress as desabilita em conexões não seguras, exceto em ambiente local explícito). Basic Auth envia a credencial em toda requisição; sem TLS, ela trafega praticamente em texto claro. Trate cada senha de aplicação como uma chave: armazene em variável de ambiente, nunca no código versionado.

3. JWT e OAuth (via plugin)

Quando você precisa de tokens com expiração, escopos ou um fluxo de autorização de terceiros (um app que age em nome do usuário), o caminho é JWT ou OAuth 1.0a por plugin — não há implementação nativa no núcleo para esses fluxos. São indicados para APIs públicas consumidas por aplicações cliente que você não controla. Para integrações internas, cookie+nonce e Application Passwords quase sempre bastam.

Boas práticas e segurança

Armadilhas comuns (e como evitar)

SintomaCausa provávelCorreção
Aviso doing it wrong no logpermission_callback ausenteDefina-o sempre (ex.: current_user_can(...))
Rota não aparece / 404Registro fora do hook rest_api_initRegistre dentro de rest_api_init
/wp-json/ dá 404Links permanentes desativadosAtive permalinks ou use ?rest_route=
403 rest_cookie_invalid_nonceNonce ausente ou expiradoEnvie X-WP-Nonce; renove se a página ficou aberta
Parâmetro chega como stringFaltou sanitize_callbackUse absint / sanitize_text_field em args
Dados internos no JSONRetornou o objeto cru do bancoMonte um array só com campos públicos

Endpoints customizados costumam morar em um plugin próprio, e a alternativa clássica de comunicação assíncrona — útil em contextos onde a REST API é exagero — é o AJAX no WordPress. Explore também os demais temas de desenvolvimento.

Precisa expor dados do WordPress por uma API confiável?

Crio endpoints REST com validação, autenticação e a segurança que o seu projeto exige — do app mobile à integração entre sistemas. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

Por que recebo o aviso "permission_callback was not provided"?

Desde o WordPress 5.5, todo endpoint precisa de um permission_callback. Se você omitir, o WordPress registra um doing it wrong no log e, em versões recentes, pode até recusar a rota. O callback deve devolver true, false ou um WP_Error. Se a rota for realmente pública (leitura de dados que já são públicos), use __return_true — mas de forma consciente, sabendo que qualquer pessoa poderá chamá-la. Para qualquer escrita ou dado sensível, verifique uma capability com current_user_can().

Qual a diferença entre sanitize_callback e validate_callback?

O validate_callback roda primeiro e responde "este valor é aceitável?" — retornando true/false ou um WP_Error; se reprovar, o WordPress devolve 400 antes mesmo de chamar seu callback. O sanitize_callback roda depois e transforma o valor para uma forma segura (ex.: absint, sanitize_text_field). Pense em validar como o porteiro e sanitizar como a faxina: um barra o que não serve, o outro limpa o que passou.

A URL /wp-json/ não funciona, só ?rest_route=. Por quê?

As URLs "bonitas" do tipo /wp-json/ dependem de links permanentes ativados (qualquer estrutura diferente de "Simples"). Em Configurações → Links permanentes, escolha por exemplo "Nome do post" e salve para regravar as regras de rewrite. Sem permalinks bonitos, a API ainda funciona pela forma alternativa ?rest_route=/wp/v2/posts, que não depende de mod_rewrite. Veja também o erro de 404 e links permanentes.

Como chamo a REST API a partir do próprio tema/plugin (same-origin)?

Use autenticação por cookie + nonce. Gere o nonce no PHP com wp_create_nonce('wp_rest'), passe-o ao JavaScript (via wp_localize_script) e envie-o no cabeçalho X-WP-Nonce de cada requisição. O cookie de login do usuário já viaja junto, e o nonce comprova que a chamada partiu do seu site, não de outro domínio. É o mesmo princípio usado no AJAX no WordPress.

E para acessar a API de fora do site (outro servidor, app)?

O nonce de cookie não serve para clientes externos. Para acesso de servidor a servidor ou apps, use Application Passwords (nativo desde o WordPress 5.6), que cria senhas específicas por aplicação enviadas via HTTP Basic Auth — sempre sobre HTTPS. Para fluxos mais robustos (tokens com expiração, escopos), considere plugins de JWT ou OAuth 1.0a. Nunca trafegue credenciais por HTTP puro.

Devo criar um endpoint customizado ou usar os endpoints padrão wp/v2?

Se você só precisa ler ou gravar posts, páginas, taxonomias ou usuários, os endpoints nativos wp/v2 já cobrem isso (inclusive campos via register_meta e register_rest_field). Crie uma rota própria quando a resposta tem um formato específico da sua aplicação, agrega dados de várias fontes, ou expõe uma ação de negócio (ex.: "calcular frete", "validar cupom"). Endpoint customizado é para a sua lógica; os padrões são para CRUD de conteúdo.

Referências oficiais

  1. WordPress.org — REST API Handbook
  2. WordPress.org — Adding Custom Endpoints
  3. WordPress.org — register_rest_route() (Code Reference)
  4. WordPress.org — REST API Authentication
  5. Make WordPress Core — Application Passwords Integration Guide
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui