REST API en WordPress: rutas personalizadas y cómo leer el JSON

Para leer el cuerpo JSON de una ruta REST propia en WordPress no hace falta php://input: el objeto WP_REST_Request que recibe tu callback ya lo trae parseado en get_json_params(). Y si estás registrando la ruta, hay un argumento que WordPress exige desde la versión 5.5 y que mucho código de ejemplo por ahí todavía omite.
Registrar la ruta
<?php
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/foo', [
'methods' => 'POST',
'callback' => 'my_awesome_func',
'permission_callback' => '__return_true',
] );
} );
function my_awesome_func( WP_REST_Request $request ) {
// TODO
}El permission_callback no es opcional
Desde WordPress 5.5, permission_callback es obligatorio. Si lo omites, WordPress lanza un aviso de _doing_it_wrong con este texto:
The REST API route definition for myplugin/v1/author is missing the required permission_callback argument. For REST API routes that are intended to be public, use
__return_trueas the permission callback.
La traducción es mía: falta el argumento permission_callback, que es obligatorio, y para rutas pensadas como públicas hay que usar __return_true.
Ese __return_true significa literalmente cualquiera puede llamar a esta ruta, sin autenticar. Está bien para un endpoint público de lectura, y está mal para casi todo lo demás. Si la ruta escribe algo, valida:
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},Que la ruta esté registrada en un plugin tuyo no la esconde: /wp-json/ lista los namespaces y cualquiera puede recorrerlos.
Enviar los datos
Con fetch, que ya no necesita jQuery:
const dataValue = { hello: "there" };
const res = await fetch( "/wp-json/myplugin/v1/foo", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify( dataValue ),
} );
const data = await res.json();El Content-Type: application/json es la parte que importa: es lo que hace que WordPress parsee el cuerpo y lo deje disponible en get_json_params(). Sin esa cabecera, el JSON llega como cuerpo crudo y ese método devuelve un array vacío.
Si la ruta requiere autenticación, hay que mandar el nonce que WordPress expone en wpApiSettings.nonce:
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": wpApiSettings.nonce,
},Leer los datos en el callback
Es la parte que casi todo el mundo resuelve mal. Buscando en internet aparece la recomendación de usar php://input, y no es que esté rota: es que en WordPress sobra, porque el objeto request ya hizo ese trabajo.
La documentación oficial lo explica así: las rutas reciben todos los argumentos que vienen en la petición, se combinan en un único conjunto de parámetros y se añaden al objeto Request, que se pasa como primer parámetro a tu endpoint.
Los métodos disponibles:
$param = $request['some_param'];
// O con el método helper:
$param = $request->get_param( 'some_param' );
// El conjunto combinado de todos los parámetros:
$parameters = $request->get_params();
// Y cada conjunto por separado, si lo necesitas:
$parameters = $request->get_url_params();
$parameters = $request->get_query_params();
$parameters = $request->get_body_params();
$parameters = $request->get_json_params();
$parameters = $request->get_default_params();
// Los archivos no se combinan, van aparte:
$parameters = $request->get_file_params();Para el ejemplo de arriba:
function my_awesome_func( WP_REST_Request $request ) {
$parameters = $request->get_json_params();
return rest_ensure_response( [
'recibido' => $parameters['hello'] ?? null,
] );
}El ?? null no es decorativo: get_json_params() devuelve lo que venga en el cuerpo, y si el cliente no mandó esa clave, acceder directo lanza un aviso.
Probar sin tocar producción
Probar rutas REST contra un sitio en producción es mala idea, porque un register_rest_route mal declarado queda expuesto públicamente mientras lo depuras, y con __return_true copiado del ejemplo, expuesto de verdad. Para eso conviene un WordPress local, y hoy lo más rápido es WordPress Studio, que además trae Xdebug configurado.
Preguntas frecuentes
¿Cómo leo el cuerpo JSON de una petición REST en WordPress?
Con $request->get_json_params() dentro del callback. Necesita que el cliente mande la cabecera Content-Type: application/json; sin ella, WordPress no parsea el cuerpo y el método devuelve un array vacío.
¿Tengo que usar php://input?
No. Funciona, pero en WordPress es redundante: WP_REST_Request ya parseó el cuerpo y lo tiene disponible. Usar php://input además te salta la validación de argumentos que ofrece la propia API.
¿Es obligatorio el permission_callback?
Sí, desde WordPress 5.5. Si lo omites, salta un aviso de _doing_it_wrong. Para una ruta pública se usa __return_true, y para cualquier otra, una función que compruebe capacidades con current_user_can().
¿Cuál es la diferencia entre get_params y get_json_params?
get_params() devuelve todo combinado: URL, query string, cuerpo y valores por defecto. get_json_params() devuelve solo lo que llegó como JSON en el cuerpo. Si te importa de dónde viene cada dato, usa el específico.
¿Por qué me da 403 al llamar a mi ruta?
Lo habitual es que falte el nonce. En rutas que requieren usuario autenticado hay que mandar la cabecera X-WP-Nonce con el valor que WordPress expone en wpApiSettings.nonce.
Fuentes
- Adding Custom Endpoints, documentación oficial de la REST API de WordPress
Si lo que tienes entre manos es una integración de verdad y no una prueba, es justo el tipo de trabajo que hago: desarrollo con WordPress.
¿Tienes un proyecto en mente?
Trabajo con Laravel, WordPress, SEO técnico y servidores MCP. El primer paso es una llamada de descubrimiento, sin costo ni compromiso, donde me cuentas qué necesitas y te digo con honestidad si puedo ayudarte.