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

REST API WordPress: registrare endpoint custom con register_rest_route e permission_callback

02 Nov 2026 — 12:45 9 min di lettura
REST API WordPress: registrare endpoint custom con register_rest_route e permission_callback

La REST API di WordPress è completa ma ogni endpoint custom mal scritto può pesare 200KB di JSON e bypassare la page cache. Come registrare endpoint performanti, validare l'input con Zod-like schemas, e perché l'autenticazione application passwords non basta per tutto.

La REST API di WordPress esiste dal 2015 ma la usiamo davvero solo quando ci serve collegare qualcosa di esterno — un'app, un CRM, un frontend headless, un servizio di automazione. Il problema non è che manchi, è che la maggior parte degli endpoint custom che vediamo in produzione sono scritti male. Restituiscono troppo, non validano l'input, non gestiscono la cache, e si fidano dell'autenticazione di default. Un endpoint che restituisce 50 post con immagini e meta può pesare 200KB di JSON — se il frontend lo chiama a ogni page load, il risparmio di tempo rispetto a un HTML renderizzato si mangia tutto.

Il primo errore classico è registrare l'endpoint senza pensare a cosa restituisce. WordPress di default espone tutto l'oggetto post — titolo, contenuto, excerpt, data, modificato, link, autore, featured media, e una sfilza di meta che il 90% delle volte non servono. Se il tuo frontend vuole solo titolo, slug e copertina, restituisci solo quelli. Il payload passa da 4KB a 400 byte per post, e su 50 post la differenza è tra una risposta istantanea e una che fa timeout sul mobile.

Registrare un endpoint minimale e performante

La registrazione avviene con register_rest_route nel file functions.php del tema child o (meglio) in un MU plugin. L'endpoint deve dichiarare metodo, callback, permessi e argomenti. Il permission_callback non è opzionale — se lo ometti, WordPress restituisce un errore di deprecation e in futuro rifiuterà la registrazione. '__return_true' significa pubblico, ma va usato consapevolmente:

wp-config.php
add_action( 'rest_api_init', function () {
    register_rest_route( 'wp-planet/v1', '/stats', array(
        'methods'             => 'GET',
        'callback'            => 'wpplanet_get_site_stats',
        'permission_callback' => '__return_true', // endpoint pubblico, nessun dato sensibile
        'args'                => array(
            'range' => array(
                'type'    => 'string',
                'default' => '30d',
                'enum'    => array( '7d', '30d', '90d' ),
            ),
        ),
    ) );
} );

function wpplanet_get_site_stats( WP_REST_Request $request ) {
    $range = $request->get_param( 'range' );

    // Recupera solo i campi necessari — niente oggetto post completo
    $stats = array(
        'posts'     => wp_count_posts( 'post' )->publish,
        'pages'     => wp_count_posts( 'page' )->publish,
        'comments'  => wp_count_comments()->approved,
        'updated'   => current_time( 'c' ),
    );

    // Cache lato server: 5 minuti per non rifare le query ogni richiesta
    return new WP_REST_Response( $stats, 200, array(
        'Cache-Control' => 'max-age=300, s-maxage=300',
    ) );
}
Nota: L'header Cache-Control con s-maxage dice alla CDN di memorizzare la risposta per 300 secondi. Senza questo header, ogni richiesta arriva al server PHP anche se la risposta non cambia. Usa 'private' invece di 'public' se la risposta contiene dati utente-specifici.

Validare l'input (perché WordPress non lo fa per te)

Gli args della registrazione definiscono i tipi, ma WordPress non li valida in modo rigoroso di default. Un 'type' => 'integer' accetta ancora una stringa se il cast implicito funziona. Per endpoint che accettano input complesso, valida esplicitamente nel callback — o con un plugin di validazione, o con controlli manuali. Un endpoint che accetta un ID senza verificare che sia un intero positivo è un invito a injection e a query pesanti.

Per endpoint che accettano POST con payload JSON, usa get_json_params() invece dei parametri form. E valida ogni campo:

wp-config.php
function wpplanet_create_ticket( WP_REST_Request $request ) {
    $body = $request->get_json_params();

    // Validazione manuale — ogni campo obbligatorio
    $required = array( 'subject', 'description', 'site_url' );
    foreach ( $required as $field ) {
        if ( empty( $body[ $field ] ) ) {
            return new WP_Error(
                'missing_field',
                sprintf( 'Campo obbligatorio mancante: %s', $field ),
                array( 'status' => 400 )
            );
        }
    }

    // Sanitizza ogni input
    $subject     = sanitize_text_field( $body['subject'] );
    $description = sanitize_textarea_field( $body['description'] );
    $site_url    = esc_url_raw( $body['site_url'] );

    // Verifica che site_url sia un URL valido
    if ( ! filter_var( $site_url, FILTER_VALIDATE_URL ) ) {
        return new WP_Error( 'invalid_url', 'URL del sito non valido', array( 'status' => 400 ) );
    }

    // ... logica di creazione del ticket ...

    return new WP_REST_Response( array( 'id' => $ticket_id ), 201 );
}
Nota: Mai fidarsi dell'input del client. Anche se il frontend valida, un utente può chiamare l'endpoint direttamente con curl e inviare qualsiasi cosa. sanitize_text_field rimuove tag HTML e caratteri non validi; esc_url_raw valida gli URL per il salvataggio nel database.

Autenticazione: application passwords non basta per tutto

L'autenticazione di default della REST API usa i cookie di sessione di WordPress — funziona solo se la richiesta viene dallo stesso dominio (con il nonce wp_rest). Per richieste esterne (app, CRM, servizi terzi), le application passwords sono il metodo più semplice: generi una password applicazione dal profilo utente e la invii come Basic Auth. Funziona, ma ha limiti: ogni password è legata a un utente, non puoi scaderla facilmente, e se l'app la perde hai un problema.

Per endpoint che richiedono sicurezza maggiore — operazioni amministrative, accesso a dati sensibili — usa un token JWT o un custom API key system. Un pattern che vediamo spesso è un endpoint di autenticazione che restituisce un token a scadenza, e gli altri endpoint verificano il token:

wp-config.php
// Verifica un custom API key header su endpoint protetti
add_filter( 'rest_authentication_errors', function( $result ) {
    // Se c'è già un errore (es. nonce mancante), lascialo passare
    if ( ! empty( $result ) ) {
        return $result;
    }

    // Solo per le route custom wp-planet/v1
    $route = $_SERVER['REQUEST_URI'] ?? '';
    if ( strpos( $route, 'wp-planet/v1' ) === false ) {
        return $result;
    }

    // Endpoint pubblici non richiedono autenticazione
    $public_routes = array( '/stats', '/public-posts' );
    foreach ( $public_routes as $public ) {
        if ( strpos( $route, $public ) !== false ) {
            return $result; // pubblico, lascia passare
        }
    }

    // Verifica l'API key header
    $api_key = $_SERVER['HTTP_X_WPPLANET_KEY'] ?? '';
    $stored_key = get_option( 'wpplanet_api_key' );

    if ( ! $stored_key || ! hash_equals( $stored_key, $api_key ) ) {
        return new WP_Error(
            'rest_forbidden',
            'API key non valida o mancante',
            array( 'status' => 401 )
        );
    }

    return $result;
} );
Nota: hash_equals confronta le stringhe in modo timing-safe, prevenendo timing attacks. Mai usare === per confrontare password o API key. L'API key va salvata in wp_options con autoload=no per non caricarla a ogni page request.

Il test che ti dice se l'endpoint è performante

Il modo più rapido di misurare un endpoint è curl con -w per il timing. Un endpoint REST su WordPress dovrebbe rispondere sotto i 200ms su una cache calda. Se ci mette 800ms o più, il problema è quasi sempre una query non ottimizzata o un get_posts che carica troppo:

Terminale
# Misura tempo di risposta e dimensione del payload
curl -o /dev/null -s -w 'Tempo: %{time_total}s\nDimensione: %{size_download} byte\n' \
  -H 'X-WPPlanet-Key: tua-api-key' \
  'https://www.example.com/wp-json/wp-planet/v1/stats?range=30d'

# Test di carico rapido: 20 richieste consecutive
for i in $(seq 1 20); do
  curl -o /dev/null -s -w '%{time_total}\n' \
    'https://www.example.com/wp-json/wp-planet/v1/stats?range=30d'
done
Nota: Se la prima richiesta è lenta (800ms) e le successive sono veloci (50ms), la cache funziona. Se tutte sono lente, il problema è la query o l'assenza di caching lato server. Aggiungi object cache Redis per gli endpoint che fanno query ripetitive.

Un endpoint REST ben scritto è invisibile: risponde veloce, restituisce solo lo stretto necessario, valida tutto, e non si fa notare. Uno scritto male rallenta il sito, apre falle di sicurezza, e diventa un collo di bottiglia che si scopre solo quando il traffico cresce. La differenza, quasi sempre, è nei dettagli che nessuno controlla.

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