Como crear endpoints REST personalizados con register_rest_route en WordPress

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

  1. Para proyectos serios, encapsula rutas y callbacks en un plugin (mejor mantenimiento y despliegue).
  2. Evita mezclar lógica en el archivo functions.php si esperas reutilizar o transportar el código.
  3. Usa clases para agrupar endpoints relacionados y facilitar pruebas e inyección de dependencias.

Resumen operativo: pasos para crear un endpoint

  1. Elegir namespace profesional (ej. myplugin/v1).
  2. Registrar ruta en rest_api_init con register_rest_route.
  3. Definir callback y permission_callback.
  4. Declarar args con validación y sanitización.
  5. Manejar respuestas con rest_ensure_response o WP_REST_Response y errores con WP_Error.
  6. 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 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *