Salta al contenuto principale
Sistemi Attivi
WP Planet — WordPress Cloud & Managed Services
Torna al MagazineSviluppo

REST API WordPress: creare endpoint custom senza appesantire il core

27 Ott 2026 — 09:30 9 min di lettura
REST API WordPress: creare endpoint custom senza appesantire il core

La REST API di WordPress è potente ma gli endpoint di default restituiscono troppo. Come creare endpoint custom leggeri, sicuri e cachabili, con autenticazione e validazione. Il pattern che usiamo per integrare WordPress con sistemi esterni senza gonfiare il database.

La REST API di WordPress è una delle feature più sottovalutate della piattaforma. Chi la usa la considera un modo per leggere i post da fuori; chi non la usa pensa sia roba da sviluppatori avanzati. La verità è che la REST API è il ponte tra WordPress e qualsiasi sistema esterno — un CRM, un'app mobile, un frontend separato, un servizio di automazione. E quando la usi bene, ti permette di tenere WordPress come backend di gestione contenuti senza appesantirlo con logica che non gli appartiene.

Il problema degli endpoint di default è che restituiscono tutto. Un GET /wp-json/wp/v2/posts ti dà titolo, contenuto, excerpt, autore, featured media, link, e una decina di campi che il 90% delle volte non ti servono. Su un endpoint che restituisce 50 post, stai trasferendo 200KB di JSON quando te ne servirebbero 30. La soluzione è creare endpoint custom che restituiscono solo quello che serve — e niente di più.

Endpoint custom: la struttura base

Registrare un endpoint custom è semplice. La parte che la maggior parte salta è la validazione degli argomenti e la permission callback — senza quelle, l'endpoint è una porta aperta. Ecco un endpoint completo che restituisce articoli ottimizzati per un frontend esterno:

wp-config.php
<?php
// Endpoint REST custom per articoli ottimizzati
add_action( 'rest_api_init', function () {
    register_rest_route( 'wp-planet/v1', '/articles', array(
        'methods'             => 'GET',
        'callback'            => 'wp_planet_get_articles',
        'permission_callback' => '__return_true', // endpoint pubblico
        'args'                => array(
            'page'     => array(
                'validate_callback' => function( $v ) {
                    return is_numeric( $v ) && $v > 0;
                },
                'default'           => 1,
            ),
            'per_page' => array(
                'validate_callback' => function( $v ) {
                    return is_numeric( $v ) && $v > 0 && $v <= 50;
                },
                'default'           => 12,
            ),
            'category' => array(
                'sanitize_callback' => 'sanitize_title',
                'default'            => '',
            ),
        ),
    ) );
} );

function wp_planet_get_articles( WP_REST_Request $request ) {
    $page     = (int) $request['page'];
    $per_page = (int) $request['per_page'];
    $category = $request['category'];

    $args = array(
        'post_type'           => 'post',
        'post_status'         => 'publish',
        'posts_per_page'      => $per_page,
        'paged'               => $page,
        'ignore_sticky_posts' => true,
        'no_found_rows'       => false,
    );

    if ( $category ) {
        $args['category_name'] = $category;
    }

    $query = new WP_Query( $args );
    $items = array();

    foreach ( $query->posts as $post ) {
        $items[] = array(
            'id'        => $post->ID,
            'title'     => get_the_title( $post ),
            'slug'      => $post->post_name,
            'excerpt'   => get_the_excerpt( $post ),
            'date'      => get_the_date( 'c', $post ),
            'cover_url' => get_the_post_thumbnail_url( $post, 'large' ),
        );
    }

    return new WP_REST_Response( array(
        'items'       => $items,
        'total'       => (int) $query->found_posts,
        'total_pages' => (int) $query->max_num_pages,
        'page'        => $page,
    ), 200 );
}
Nota: L'endpoint restituisce solo i campi necessari — niente contenuto completo, niente link relativi, niente _embedded. Questo riduce il payload del 70% rispetto all'endpoint di default. La permission_callback '__return_true' rende l'endpoint pubblico; per endpoint privati usa un controllo sulle capability o sull'autenticazione.

Autenticazione: non lasciare l'endpoint aperto

Per endpoint che scrivono dati o restituiscono informazioni sensibili, l'autenticazione è obbligatoria. WordPress supporta tre metodi: Application Passwords (il più semplice, da WP 5.6), JWT (con plugin), e OAuth (con plugin). Per integrazioni server-to-server, Application Passwords è la scelta più semplice e sicura:

wp-config.php
<?php
// Endpoint protetto con autenticazione Application Passwords
add_action( 'rest_api_init', function () {
    register_rest_route( 'wp-planet/v1', '/sync-crm', array(
        'methods'             => 'POST',
        'callback'            => 'wp_planet_sync_crm',
        'permission_callback' => function() {
            // Solo utenti autenticati con capability manage_options
            return current_user_can( 'manage_options' );
        },
        'args'                => array(
            'contact_id' => array(
                'required'          => true,
                'validate_callback' => function( $v ) {
                    return is_string( $v ) && strlen( $v ) > 0;
                },
            ),
            'email' => array(
                'required'          => true,
                'sanitize_callback' => 'sanitize_email',
                'validate_callback' => 'is_email',
            ),
        ),
    ) );
} );

function wp_planet_sync_crm( WP_REST_Request $request ) {
    $contact_id = $request['contact_id'];
    $email      = $request['email'];

    // Logica di sincronizzazione con CRM esterno
    // ... chiama API del CRM, aggiorna contatto, etc.

    return new WP_REST_Response( array(
        'success'    => true,
        'contact_id' => $contact_id,
        'synced_at'  => current_time( 'c' ),
    ), 200 );
}

// Chiamata dall'esterno con Application Password:
// curl -X POST https://example.com/wp-json/wp-planet/v1/sync-crm
//   -u admin:xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
//   -H 'Content-Type: application/json'
//   -d '{"contact_id":"123","email":"user@example.com"}'
Nota: Application Passwords si generano dal profilo utente in WordPress admin (Utenti → Profilo → Application Passwords). Ogni password è dedicata a un'applicazione e può essere revocata singolarmente. Non usare mai la password principale dell'utente per autenticazione API.

Caching degli endpoint: il collo di bottiglia nascosto

Un endpoint REST non cachato è un problema di performance che si manifesta sotto carico. Ogni richiesta esegue una WP_Query, che esegue query SQL, che carica WordPress. Su un endpoint chiamato frequentemente (es. da un frontend che lo chiama ad ogni page load), il server si satura. La soluzione è cachare la risposta con i transient:

wp-config.php
<?php
// Caching della risposta REST con transient
function wp_planet_get_articles( WP_REST_Request $request ) {
    $page     = (int) $request['page'];
    $per_page = (int) $request['per_page'];
    $category = $request['category'];

    // Chiave di cache univoca per combinazione di parametri
    $cache_key = 'wp_planet_articles_' . md5( $page . '_' . $per_page . '_' . $category );
    $cached    = get_transient( $cache_key );

    if ( false !== $cached ) {
        return new WP_REST_Response( $cached, 200 );
    }

    // ... query e costruzione $response (come sopra) ...
    $response = array(
        'items'       => $items,
        'total'       => (int) $query->found_posts,
        'total_pages' => (int) $query->max_num_pages,
        'page'        => $page,
    );

    // Cache per 5 minuti (300 secondi)
    set_transient( $cache_key, $response, 300 );

    return new WP_REST_Response( $response, 200 );
}

// Invalida la cache quando un post viene pubblicato/aggiornato
add_action( 'save_post', function( $post_id ) {
    if ( wp_is_post_revision( $post_id ) ) return;
    delete_transient( 'wp_planet_articles_' . md5( (string) $post_id ) );
} );
Nota: Con Redis object cache attivo, i transient sono salvati in memoria — la risposta cachata è praticamente istantanea. Senza Redis, i transient sono nel database, che è comunque più veloce di una WP_Query completa. Imposta un TTL breve (5 minuti) per dati che cambiano spesso, più lungo (1 ora) per dati più statici.

La REST API di WordPress è uno strumento potente quando la usi con metodo: endpoint custom che restituiscono solo quello che serve, autenticazione per i dati sensibili, e caching per i endpoint pubblici chiamati spesso. Senza questi tre elementi, l'API è un peso, non un'infrastruttura. Con metodo, è il ponte più affidabile tra WordPress e qualsiasi sistema esterno — senza appesantire il core.

Sviluppo WordPress

Codice pulito, prestazioni e dipendenze ridotte al minimo.

Sviluppo WordPress personalizzato: temi leggeri, plugin su misura e integrazioni API senza appesantire il sito.

Sviluppo WordPress
Condividi