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 🙂 |
