Eigene REST-Endpunkte in WordPress: Validierung, Ausgabe und Performance

Management Summary
Eine eigene Datenschnittstelle ermöglicht einer WordPress-Seite oder Anwendung, gezielt Informationen für eine bestimmte Funktion abzurufen. Eine Schnittstelle ist dabei eine festgelegte Adresse zum Datenaustausch statt einer vollständigen Webseite. Das kann Antworten vereinfachen und unnötige Arbeit begrenzen, wenn vorhandene Zugänge nicht passen. Zugleich muss feststehen, wer Informationen lesen oder ändern darf. Der Nutzen liegt in klarer Aufgabe, geregeltem Zugriff und verlässlichem Verhalten.
Inhalt
- Ein kleiner Endpunkt mit klar festgelegten Antwortfeldern
- 1. Vor einer neuen Route die bestehende API prüfen
- 2. Anfrage registrieren, prüfen und begrenzen
- 3. Endpunkt-Adresse durch WordPress erzeugen
- 4. Daten als Text darstellen
- 5. Caching und Anmeldung getrennt entscheiden
- 6. Korrektheit und tatsächlichen Aufwand prüfen
- Fragen und Antworten
- Quellen
Ein kleiner Endpunkt mit klar festgelegten Antwortfeldern
Ein REST-Endpunkt ist eine Adresse, über die ein Programm Daten abruft. Ein eigener WordPress-Endpunkt kann genau die Felder liefern, die ein Seitenelement benötigt, etwa eine Liste neuer Beiträge. Der Nutzen liegt in einer vorhersehbaren Schnittstelle und begrenzter Arbeit pro Anfrage. Der Wechsel von admin-ajax.php garantiert noch keine bestimmte Antwortzeit.
Beide Anfragewege laden WordPress sowie relevante Plugin- und Theme-Funktionen. admin-ajax.php lädt zusätzlich Verwaltungscode und führt admin_init aus. Der Aufwand hängt von Callbacks, Datenbank, Cache und Serverlast ab. „400–600 ms gegenüber unter 50 ms“ und „60–80 % schneller“ benötigen Messwerte; diese Zahlen sind keine Eigenschaften von REST.

1. Vor einer neuen Route die bestehende API prüfen
Für eine gewöhnliche Beitragsliste existiert mit wp/v2/posts bereits ein gepflegter Endpunkt. Eine begrenzte Anzahl über per_page und ausgewählte _fields können die Antwort verkleinern. Eine eigene Route lohnt sich bei abweichendem Datenbedarf. Zusätzliche Routen benötigen eigene Zugriffsregeln, Tests und Wartung.
Das folgende Beispiel liefert aktuelle Beitragsmetadaten, keine Logs. Das Lehrbeispiel setzt voraus, dass Titel und Datum der ausgewählten veröffentlichten Beiträge ohne Passwortschutz ausdrücklich öffentlich sein sollen. Mitgliedschaftsregeln, Sprachauswahl und weitere Sichtbarkeitsregeln benötigen vor einer öffentlichen Bereitstellung projektspezifische Prüfungen. Weder WP_Query noch ein erfolgreicher Permission-Callback setzt automatisch sämtliche Zugriffsregeln anderer Plugins um.
2. Anfrage registrieren, prüfen und begrenzen
Die Registrierung gehört an rest_api_init. Ein Namensraum wie lw-toolbox/v1 grenzt die Route von anderen Plugins ab. Eine Registrierung an anderer Stelle entspricht nicht der vorgesehenen Verwendung und kann eine Warnung auslösen; eine automatische 404-Antwort in jedem solchen Fall ist jedoch nicht garantiert.
Die Regel für limit akzeptiert ausschließlich gewöhnliche dezimale Ganzzahlen von 1 bis 100. Brüche, Exponentialschreibweise, Arrays, null und negative Werte werden abgelehnt. Die Validierung entscheidet über zulässige Eingaben; anschließend entsteht durch Konvertierung eine Ganzzahl. Validierung ist weder Zugriffsprüfung noch Ersatz für parametrisierte SQL-Abfragen.
add_action( 'rest_api_init', function () {
register_rest_route( 'lw-toolbox/v1', '/latest-posts', array(
'methods' => WP_REST_Server::READABLE,
// Public data only: no private logs or membership content.
'permission_callback' => '__return_true',
'args' => array(
'limit' => array(
'default' => 10,
'validate_callback' => function ( $value ) {
return ( is_string( $value ) || is_int( $value ) )
&& preg_match( '/\A(?:[1-9][0-9]?|100)\z/', (string) $value ) === 1;
},
'sanitize_callback' => function ( $value ) {
return (int) $value;
},
),
),
'callback' => function ( WP_REST_Request $request ) {
$query = new WP_Query( array(
'post_type' => 'post',
'post_status' => 'publish',
'has_password' => false,
'posts_per_page' => $request->get_param( 'limit' ),
'orderby' => array( 'date' => 'DESC', 'ID' => 'DESC' ),
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
) );
$items = array();
foreach ( $query->posts as $post ) {
$items[] = array(
'id' => (int) $post->ID,
'title' => (string) $post->post_title,
'date_gmt' => (string) $post->post_date_gmt,
);
}
$response = new WP_REST_Response( $items, 200 );
$response->header( 'Cache-Control', 'no-store' );
return $response;
},
) );
} );Der Code gehört in ein kleines projektspezifisches Plugin oder eine passende Child-Theme-Datei; eine neue PHP-Datei benötigt den öffnenden PHP-Tag. Der öffentliche Permission-Callback ist eine bewusste Zugriffsentscheidung. Private Logs, Kontodaten oder geschützte Inhalte benötigen stattdessen geeignete Berechtigungs- beziehungsweise Objektprüfungen.
no_found_rows vermeidet eine unnötige Abfrage der Gesamtzahl. Das Vorladen von Metadaten- und Taxonomie-Caches entfällt, weil die Antwort diese Felder nicht verwendet. Das normale Query-Caching bleibt verfügbar. WP_Query ist nicht grundsätzlich langsam, direktes SQL nicht grundsätzlich schneller. Direktes SQL kann Query-Filter und Sichtbarkeitsregeln von Plugins umgehen. Bei tatsächlich erforderlichen Abfragen einer eigenen Tabelle gehören Werte in Platzhalter von $wpdb->prepare(); Bezeichner müssen aus vertrauenswürdiger Konfiguration stammen.
Das Beispiel liefert eine kompakte Erfolgsantwort, bei fehlenden Treffern eine leere Liste. Ein vollständiges Überwachungssystem ist damit nicht umgesetzt: Datenbankfehler benötigen zugriffsbeschränktes Servermonitoring und eine festgelegte Fehlerbehandlung. Fehlermeldungen dürfen weder SQL-Anweisungen noch Zugangsdaten offenlegen. Ein abgelehnter REST-Parameter verhindert die Abfrage dieses Callbacks, nicht sämtliche Datenbankarbeit beim Start von WordPress.
3. Endpunkt-Adresse durch WordPress erzeugen
Ein fest eingetragener Host mit /wp-json/ direkt im Hauptverzeichnis kann bei einer Unterverzeichnis-Installation oder anderen Permalinks scheitern. Der folgende Container gehört in ein PHP-Template, nicht in einen gewöhnlichen HTML-Block des Editors. rest_url() ermittelt die Adresse; esc_url() maskiert das HTML-Attribut. Die drei Statusmeldungen passen zur Seitensprache.
<div id="lw-fast-data-box" role="status"
data-endpoint="<?php echo esc_url( rest_url( 'lw-toolbox/v1/latest-posts' ) ); ?>"
data-loading="Wird geladen..." data-empty="Keine Beiträge."
data-error="Beiträge konnten nicht geladen werden."></div>4. Daten als Text darstellen
Das JavaScript kann einmal als externe Frontend-Datei eingebunden werden. Ladezustand, leere Ergebnisse, HTTP-Fehler, ungültige Antwortdaten und ein clientseitiges Zeitlimit von zehn Sekunden sind berücksichtigt. Der öffentliche Abruf übermittelt bewusst keine Anmeldedaten. Ohne Anfragekörper ist auch kein JSON-Content-Type-Header erforderlich.
async function loadLatestPosts() {
const box = document.getElementById('lw-fast-data-box');
if (!box) return;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
box.textContent = box.dataset.loading;
try {
const url = new URL(box.dataset.endpoint, document.baseURI);
url.searchParams.set('limit', '5');
const response = await fetch(url, {
method: 'GET', credentials: 'omit', cache: 'no-store',
headers: { Accept: 'application/json' },
signal: controller.signal
});
if (!response.ok) throw new Error('HTTP ' + response.status);
const items = await response.json();
if (!Array.isArray(items) || items.length > 5 || items.some(item =>
!item || !Number.isInteger(item.id) ||
typeof item.title !== 'string' || typeof item.date_gmt !== 'string'
)) throw new Error('Invalid response');
if (!items.length) {
box.textContent = box.dataset.empty;
return;
}
const list = document.createElement('ul');
for (const item of items) {
const li = document.createElement('li');
const title = document.createElement('strong');
title.textContent = item.title;
li.append(title, document.createTextNode(' (' + item.date_gmt + ' UTC)'));
list.append(li);
}
box.replaceChildren(list);
} catch (error) {
box.textContent = box.dataset.error;
} finally {
clearTimeout(timeout);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadLatestPosts, { once: true });
} else {
loadLatestPosts();
}textContent behandelt einen Titel einschließlich spitzer Klammern als Text. Das Einsetzen eines Datenbanktitels in innerHTML würde dagegen HTML-Interpretation ermöglichen. Die Eingabeprüfung für limit schützt nicht vor dieser Ausgabeschwachstelle. Das rohe Titelformat ist hier bewusst gewählt: Gespeicherte HTML-Entities können wörtlich erscheinen. Für eine gewünschte Dekodierung ist eine eigene geprüfte Darstellungsregel erforderlich.
5. Caching und Anmeldung getrennt entscheiden
Das Beispiel setzt no-store für erfolgreiche leere und gefüllte Antworten einheitlich. Das ist eine vorsichtige Ausgangsregel, keine Performance-Optimierung. Gemeinsames Caching ist ein zusätzlicher Entwurfsschritt: public, max-age=60 passt nur zu ausdrücklich gemeinsam nutzbaren Antworten mit korrekten Cache-Schlüsseln und Invalidierung. Cookies, Autorisierung, Sprache, Anfrageparameter und Proxy-Konfiguration beeinflussen diese Entscheidung. private untersagt gemeinsamen Caches das Speichern, erlaubt aber Browser-Caching. no-store weist Caches an, die Antwort nicht zu speichern. Beide Header ersetzen keine Zugriffsprüfung.
Bei der Cookie-Anmeldung in WordPress ergänzt eine gültige wp_rest-Nonce das Anmelde-Cookie, normalerweise im Header X-WP-Nonce. Der geschützte Endpunkt benötigt trotzdem die passende Berechtigungsprüfung. Eine Nonce ist weder API-Passwort noch Ersatz für Autorisierung. Externe Clients können unterstützte Verfahren wie Anwendungspasswörter über HTTPS verwenden; Zugangsdaten gehören nicht in Browsercode.
6. Korrektheit und tatsächlichen Aufwand prüfen
- Standardlimit, 1, 100, 0, 101, Brüche, Exponentialschreibweise und Arrays testen. Ungültige Eingaben sollen vor Ausführung des Endpunkt-Callbacks mit 400 abgelehnt werden.
- Leere Daten, Titel mit Markup, ungültiges JSON, HTTP-Fehler und Client-Zeitlimit prüfen. Reale Unterverzeichnis- und Permalink-Konfiguration einbeziehen.
- Unveröffentlichte, passwortgeschützte und durch Mitgliedschaft beschränkte Inhalte sowie alle Sprachen und anonyme beziehungsweise angemeldete Zugriffe prüfen.
- Header direkt am Ursprung und hinter dem tatsächlichen Proxy kontrollieren. Kalte und warme Anfragen wiederholen; Serverlaufzeit, Query-Anzahl, Speicher, TTFB und typische Parallelität messen. Datenumfang und Zugriffsregeln beim Vergleich gleich halten.
Ein brauchbarer Performance-Bericht nennt die Testumgebung und die Streuung der Antwortzeiten. Der Median ist der mittlere Messwert; 95 % der gemessenen Antwortzeiten liegen höchstens beim 95. Perzentil. Eine einzelne schnelle Antwort oder weniger JSON-Daten begründen keine allgemeine Latenzgarantie.
Fragen und Antworten
Warum kann ein angemeldeter Browser an der Rechteprüfung scheitern?
Die Cookie-basierte REST-Anmeldung benötigt zusätzlich die REST-Nonce. Ohne diese Nonce behandelt WordPress die entsprechende Cookie-Anfrage als anonym. Ungültige Nonces und fehlende Berechtigungen sind unterschiedliche Fehler; der endgültige Status hängt auch vom Anmeldeverfahren und Permission-Callback ab.
Entfällt bei REST jede Plugin-Arbeit?
Nein. Aufwendige Arbeit während des normalen WordPress-Starts kann auch REST betreffen. Profiling identifiziert relevante Callbacks, bevor eine andere Architektur begründet ist.
Macht der Permission-Callback öffentliche Daten automatisch unbedenklich?
Nein. Die Rückgabe true erlaubt den Zugriff ausdrücklich. Ausgewählte Felder und Datensätze benötigen weiterhin eine bewusste Veröffentlichungsregel einschließlich der Vorgaben anderer Plugins.