Este tutorial explica con todo lujo de detalles cómo crear endpoints REST personalizados en WordPress usando register_rest_route. Cubre desde los conceptos básicos hasta prácticas recomendadas de seguridad, validación, respuesta y ejemplos completos (tanto PHP como JavaScript) listos para copiar e integrar en un plugin o en functions.php. Las muestras de código se muestran con la etiqueta solicitada para que se resalten correctamente.
Contents
Introducción: ¿por qué crear endpoints personalizados?
WordPress ya expone una API REST nativa para posts, taxonomías, usuarios, etc. Sin embargo, en muchos proyectos necesitas exponer lógica de negocio específica, agregada o filtrada de forma personalizada. Los endpoints personalizados permiten:
- Crear rutas con lógica específica para tu plugin o tema.
- Optimizar consultas y respuestas para clientes (SPA, apps móviles, microservicios).
- Controlar permisos, validación y formato de manera centralizada.
Conceptos clave
- Namespace: prefijo para agrupar rutas (ej. myplugin/v1).
- Route: path dentro del namespace (ej. /items).
- Methods: métodos HTTP permitidos (GET, POST, PUT, DELETE).
- Callback: función que procesa la petición y devuelve la respuesta.
- Permission callback: función que determina si el solicitante puede usar el endpoint.
- Args: definición de parámetros esperados, validación y sanitización.
Hook principal
Todas las rutas se registran durante el hook rest_api_init:
add_action( rest_api_init, miplugin_registrar_rutas ) function miplugin_registrar_rutas() { // register_rest_route(...) aquí }
Ejemplo mínimo: ruta GET pública
Un ejemplo básico que devuelve un mensaje simple.
GET, callback => function( WP_REST_Request request ) { return rest_ensure_response( array( mensaje => Hola desde mi endpoint REST personalizado ) ) }, permission_callback => __return_true, // pública ) ) } )
Argumentos, validación y sanitización
Es importante declarar los parámetros (args) para que WordPress valide y documente correctamente el endpoint. En el siguiente ejemplo se acepta un parámetro q y un parámetro numérico pagina.
GET, callback => miplugin_buscar_callback, permission_callback => __return_true, args => array( q => array( required => true, type => string, sanitize_callback => sanitize_text_field, validate_callback => function( param, request, key ) { return is_string( param ) strlen( trim( param ) ) >= 3 } ), pagina => array( required => false, type => integer, default => 1, sanitize_callback => absint, ), ), ) ) } ) function miplugin_buscar_callback( WP_REST_Request request ) { q = request->get_param( q ) pagina = request->get_param( pagina ) // Ejemplo simple: buscar posts por título args = array( s => q, posts_per_page => 10, paged => pagina, ) query = new WP_Query( args ) resultados = array() foreach ( query->posts as post ) { resultados[] = array( id => post->ID, titulo => get_the_title( post ), link => get_permalink( post ), ) } return rest_ensure_response( array( q => q, pagina => pagina, total => (int) query->found_posts, items => resultados, ) ) }
Ejemplo: endpoint para crear un post (POST) con permisos
Este ejemplo demuestra cómo procesar un POST para crear contenido y validar permisos. Requiere que el usuario esté autenticado y tenga capacidad edit_posts. Puedes adaptar la permission_callback a comprobaciones con nonce para peticiones no autenticadas si es necesario.
POST, callback => miplugin_crear_post_callback, permission_callback => function ( WP_REST_Request request ) { return current_user_can( edit_posts ) }, args => array( title => array( required => true, type => string, sanitize_callback => sanitize_text_field, ), content => array( required => true, type => string, ), status => array( required => false, type => string, default => draft, validate_callback => function( param ) { return in_array( param, array( draft, publish, private ), true ) } ), ), ) ) } ) function miplugin_crear_post_callback( WP_REST_Request request ) { title = request->get_param( title ) content = request->get_param( content ) status = request->get_param( status ) postarr = array( post_title => title, post_content => content, post_status => status, post_type => post, post_author => get_current_user_id(), ) post_id = wp_insert_post( postarr, true ) if ( is_wp_error( post_id ) ) { return new WP_Error( post_creation_failed, post_id->get_error_message(), array( status => 500 ) ) } post = get_post( post_id ) data = array( id => post_id, title => get_the_title( post ), link => get_permalink( post ), ) return rest_ensure_response( data ) }
Devolver errores y códigos HTTP
Para devolver errores correctamente usa WP_Error con un código de estado apropiado o WP_REST_Response para controlar headers y status:
// Devuelve un error 403 return new WP_Error( forbidden, No tienes permisos, array( status => 403 ) ) // Devuelve un WP_REST_Response con cabeceras personalizadas response = rest_ensure_response( array( ok => true ) ) response->set_status( 201 ) response->header( X-Mi-Cabecera, valor ) return response
Uso de constantes de métodos
WordPress proporciona constantes convenientes en WP_REST_Server:
Constante | Métodos |
WP_REST_Server::READABLE | GET, HEAD |
WP_REST_Server::CREATABLE | POST |
WP_REST_Server::EDITABLE | PUT, PATCH |
WP_REST_Server::DELETABLE | DELETE |
Ejemplo: ruta con parámetros en la URL
Rutas dinámicas con expresiones regulares permiten capturar segmentos de la URL, por ejemplo un ID numérico:
d ), array( methods => GET, callback => function( WP_REST_Request request ) { id = (int) request->get_param( id ) post = get_post( id ) if ( ! post ) { return new WP_Error( not_found, Elemento no encontrado, array( status => 404 ) ) } return rest_ensure_response( array( id => id, title => get_the_title( post ), ) ) }, permission_callback => __return_true, ) ) } )
Ejemplo de cliente JS (fetch) para consumir el endpoint
Ejemplo para llamar al endpoint público /miplugin/v1/hola:
fetch(/wp-json/miplugin/v1/hola) .then(response => { if (!response.ok) throw new Error(Error en la petición) return response.json() }) .then(data => console.log(data)) .catch(err => console.error(err))
Buenas prácticas de seguridad y rendimiento
- Validar y sanear siempre: define args con validate_callback y sanitize_callback.
- Comprobar permisos: usar permission_callback para impedir accesos no autorizados.
- No exponer datos sensibles: no devolver campos como user_pass o meta privados.
- Cache: implementa transients o cabeceras HTTP apropiadas para endpoints pesados.
- Limitar resultados: usa paginación y límites para evitar respuestas enormes.
- Rate limiting: considera mecanismos a nivel de servidor o plugin para evitar abusos.
- Usar WP_REST_Response: para controlar estado y cabeceras explícitamente.
Ejemplo de cache simple con transient
function miplugin_cached_items_callback( WP_REST_Request request ) { cache_key = miplugin_items_cache_v1 data = get_transient( cache_key ) if ( false === data ) { // Realiza la consulta costosa query = new WP_Query( array( posts_per_page => 20 ) ) items = array() foreach ( query->posts as post ) { items[] = array( id => post->ID, title => get_the_title( post ) ) } data = items set_transient( cache_key, data, HOUR_IN_SECONDS ) } return rest_ensure_response( data ) }
Documentación automática y schema
Si defines args y schema, WordPress puede exponer la documentación de la ruta en la API y ayudar a clientes a entender los campos.
register_rest_route( miplugin/v1, /producto, array( methods => POST, callback => crear_producto, args => array( nombre => array( type => string, required => true, ), precio => array( type => number, required => true, ), ), schema => array( description => Crear producto, type => object, properties => array( nombre => array( type => string ), precio => array( type => number ), ), ), ) )
Organización del código: plugin vs functions.php
- Para proyectos serios, encapsula rutas y callbacks en un plugin (mejor mantenimiento y despliegue).
- Evita mezclar lógica en el archivo functions.php si esperas reutilizar o transportar el código.
- Usa clases para agrupar endpoints relacionados y facilitar pruebas e inyección de dependencias.
Resumen operativo: pasos para crear un endpoint
- Elegir namespace profesional (ej. myplugin/v1).
- Registrar ruta en rest_api_init con register_rest_route.
- Definir callback y permission_callback.
- Declarar args con validación y sanitización.
- Manejar respuestas con rest_ensure_response o WP_REST_Response y errores con WP_Error.
- Probar con herramientas como curl, Postman o desde el navegador.
Ejemplo final: plugin mínimo completo
Archivo plugin: miplugin-endpoints.php (coloca en wp-content/plugins/miplugin-endpoints/)
WP_REST_Server::READABLE, callback => function() { return rest_ensure_response( array( mensaje => Hola mundo ) ) }, permission_callback => __return_true, ) ) // Ruta protegida para crear posts register_rest_route( miplugin/v1, /crear-post, array( methods => WP_REST_Server::CREATABLE, callback => miplugin_crear_post_callback, permission_callback => function () { return current_user_can( edit_posts ) }, args => array( title => array( required => true, type => string, sanitize_callback => sanitize_text_field ), content => array( required => true, type => string ), ), ) ) } ) function miplugin_crear_post_callback( WP_REST_Request request ) { title = request->get_param( title ) content = request->get_param( content ) post_id = wp_insert_post( array( post_title => title, post_content => content, post_status => draft, post_type => post, post_author => get_current_user_id(), ), true ) if ( is_wp_error( post_id ) ) { return new WP_Error( creation_failed, post_id->get_error_message(), array( status => 500 ) ) } return rest_ensure_response( array( id => post_id ) ) }
Conclusión técnica
Crear endpoints REST personalizados con register_rest_route te permite adaptar la API de WordPress a necesidades concretas y controlar la validación, permisos y formato de la respuesta. Sigue las buenas prácticas (validación, sanitización, permisos y cache) para mantener una API segura y eficiente. Con los ejemplos aquí incluidos tienes una base sólida para construir endpoints más complejos, versionarlos y documentarlos correctamente.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |