WordPress

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

Autorangel cruz
Actualizado
Publicado
Lectura4 min de lectura
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_true as 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

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.

Hablemos