Tu veux moderniser ton site WordPress ou créer une architecture découplée ? Créer une API REST personnalisée est la solution idéale pour alimenter des interfaces JavaScript modernes, des applications mobiles ou même d’autres services tiers.
Dans ce guide, on va voir comment construire une API robuste et sécurisée étape par étape, en combinant les outils natifs de WordPress avec de bonnes pratiques modernes.
Pourquoi une API REST ?
Le web évolue. Aujourd’hui, les utilisateurs attendent des applications réactives, dynamiques et connectées entre elles. C’est exactement ce que permet une API REST : elle expose les données de ton site sous forme de ressources accessibles par des requêtes HTTP (GET, POST, PUT, DELETE, etc.), ce qui facilite l’intégration avec n’importe quel frontend ou service externe.
Les cas d’usage modernes
WordPress n’est plus limité à un CMS monolithique basé sur des templates PHP. Grâce à la REST API, tu peux le transformer en véritable backend headless.
Voici quelques cas concrets où une API REST WordPress prend tout son sens :
- Headless CMS : WordPress gère le contenu, et tu utilises React, Vue ou Angular côté client pour afficher les données.
- Backend pour app mobile : Ton application iOS ou Android récupère les articles, les utilisateurs ou les produits via des requêtes à l’API.
- SPA (Single Page Application) : Tu crées une interface fluide et réactive, sans rechargement complet de la page, en utilisant Vue Router, React Router ou autre.
- Connexion avec des services tiers : Tu exposes des données (produits, utilisateurs, articles) à d’autres plateformes via ton API.
- Dashboard personnalisé : Pour un intranet, une interface d’admin ou une app métier connectée à WordPress
Avantages vs templates classiques
Traditionnellement, WordPress utilise des fichiers PHP pour afficher les pages :
// Approche classique
get_header();
while (have_posts()) {
the_post();
the_title();
the_content();
}
get_footer();Cette méthode fonctionne, mais elle a des limites dès que tu veux plus de dynamisme, plus de modularité ou un frontend 100% custom.
Avec une API REST, tu sépares complètement le contenu (backend) de la présentation (frontend) :
// Approche moderne
fetch('/wp-json/webpixelia/v1/posts')
.then(response => response.json())
.then(posts => {
posts.forEach(post => renderPost(post));
});Les avantages concrets :
- Performance : Tu peux mettre en cache les appels à l’API côté client ou via un CDN. Moins de rechargements = meilleure UX.
- Flexibilité totale : Tu décides où et comment utiliser les données : sur une app mobile, dans un widget, ou dans un site statique.
- Scalabilité : Tu peux faire évoluer indépendamment ton frontend et ton backend. Besoin d’un nouveau frontend en React ? Pas besoin de toucher au CMS.
- Expérience utilisateur moderne : Tu construis des interfaces fluides, instantanées, sans attendre les rechargements serveur.
✨ En bref, créer une API REST personnalisée avec WordPress, c’est ouvrir ton site à un écosystème plus large, plus rapide, et plus modulaire.
Préparer l’environnement WordPress
Avant de commencer à écrire ton API, assure-toi de bosser proprement :
Crée un plugin dédié plutôt que de mettre ton code dans functions.php. C’est plus modulaire, plus portable, et ça t’évite de casser ton thème.
📁 Structure recommandée :
wp-content/plugins/custom-api/custom-api.php
Enregistrement de custom endpoints
On va créer une classe CustomAPI pour encapsuler toute la logique. Voici la structure de base de ton plugin :
<?php
/**
* Plugin Name: Custom API REST
* Description: Expose des endpoints personnalisés via l'API REST de WordPress.
*/
class CustomAPI {
public function __construct() {
// Hook REST API
add_action('rest_api_init', array($this, 'register_routes'));
}
public function register_routes() {
$namespace = 'webpixelia/v1';
// Endpoint GET : liste des posts
register_rest_route($namespace, '/posts', array(
'methods' => 'GET',
'callback' => array($this, 'get_posts'),
'permission_callback' => array($this, 'check_permissions'),
));
// Endpoint POST : création d’un post
register_rest_route($namespace, '/posts', array(
'methods' => 'POST',
'callback' => array($this, 'create_post'),
'permission_callback' => array($this, 'check_write_permissions'),
));
// Endpoint spécifique avec ID (GET, PUT, DELETE)
register_rest_route($namespace, '/posts/(?P<id>\d+)', array(
array(
'methods' => 'GET',
'callback' => array($this, 'get_post'),
'permission_callback' => array($this, 'check_permissions'),
),
array(
'methods' => 'PUT',
'callback' => array($this, 'update_post'),
'permission_callback' => array($this, 'check_write_permissions'),
),
array(
'methods' => 'DELETE',
'callback' => array($this, 'delete_post'),
'permission_callback' => array($this, 'check_write_permissions'),
),
));
}
// À compléter ensuite : get_posts, create_post, etc.
}
// Instanciation de la classe
new CustomAPI();💡 Tu peux changer le namespace (
) selon ton projet. L’idée est d’avoir un préfixe unique + une version d’API.webpixelia/v1
Gestion des permissions et authentification
La sécurité est critique. WordPress te permet de définir une fonction de permission pour chaque endpoint via permission_callback.
Lecture : autoriser tout le monde
public function check_permissions($request) {
return true; // Accès public (lecture seule)
}Écriture : restreindre aux utilisateurs connectés
public function check_write_permissions($request) {
// 1. Vérifie les rôles/capacités de l'utilisateur
if (!current_user_can('publish_posts')) {
return new WP_Error(
'rest_forbidden',
'Tu n\'as pas les permissions nécessaires.',
array('status' => 403)
);
}
// 2. Vérifie le nonce pour sécuriser les requêtes (AJAX côté front)
$nonce = $request->get_header('X-WP-Nonce');
if (!wp_verify_nonce($nonce, 'wp_rest')) {
return new WP_Error(
'rest_nonce_invalid',
'Le nonce est invalide ou absent.',
array('status' => 401)
);
}
return true;
}🔐 Le header
X-WP-Nonceest généré par WordPress et injecté automatiquement danswp_localize_script()viawp_create_nonce('wp_rest'). Côté front, pense à l’ajouter dans tes requêtes fetch.
Exemple côté JavaScript (React, Vue, Vanilla)
fetch('/wp-json/webpixelia/v1/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': window.wpApiSettings.nonce
},
body: JSON.stringify({ title: 'Mon post' })
});Implémentation des endpoints
Passons maintenant à la pratique : tu vas créer les fonctions de rappel (callback functions) qui traitent les requêtes envoyées à ton API.
Chaque endpoint correspond à une ressource (posts ici) et une méthode HTTP (GET, POST, PUT, DELETE). Tu vas :
- Récupérer les articles (GET)
- Créer un article (POST)
- Mettre à jour un article (PUT)
- Supprimer un article (DELETE)
Récupérer les articles (GET)
On autorise ici la consultation des articles publiés, avec pagination, recherche et filtrage par catégorie :
public function get_posts($request) {
$params = $request->get_params();
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => isset($params['per_page']) ? intval($params['per_page']) : 10,
'paged' => isset($params['page']) ? intval($params['page']) : 1,
);
if (!empty($params['category'])) {
$args['category_name'] = sanitize_text_field($params['category']);
}
if (!empty($params['search'])) {
$args['s'] = sanitize_text_field($params['search']);
}
$query = new WP_Query($args);
$posts = [];
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$posts[] = $this->format_post_data(get_post());
}
wp_reset_postdata();
}
// On ajoute les métadonnées de pagination dans les headers HTTP
$response = rest_ensure_response($posts);
$response->header('X-WP-Total', $query->found_posts);
$response->header('X-WP-TotalPages', $query->max_num_pages);
return $response;
}Formatage d’un post
On isole le format de sortie dans une méthode dédiée pour le rendre facilement réutilisable (DRY).
private function format_post_data($post) {
return [
'id' => $post->ID,
'title' => get_the_title($post->ID),
'content' => apply_filters('the_content', $post->post_content),
'excerpt' => get_the_excerpt($post->ID),
'author' => get_the_author_meta('display_name', $post->post_author),
'date' => get_the_date('c', $post->ID),
'modified' => get_the_modified_date('c', $post->ID),
'featured_image' => get_the_post_thumbnail_url($post->ID, 'medium'),
'categories' => wp_get_post_categories($post->ID, ['fields' => 'names']),
'tags' => wp_get_post_tags($post->ID, ['fields' => 'names']),
'link' => get_permalink($post->ID),
];
}Créer une ressource (POST)
Pour créer une ressource, on récupère les données depuis $request, on les valide, puis on insère le post.
public function create_post($request) {
$params = $request->get_params();
// Validation des données entrantes
$validation = $this->validate_post_data($params);
if (is_wp_error($validation)) {
return $validation;
}
$post_data = [
'post_title' => sanitize_text_field($params['title']),
'post_content' => wp_kses_post($params['content']),
'post_status' => 'publish',
'post_type' => 'post',
'post_author' => get_current_user_id(),
];
if (!empty($params['excerpt'])) {
$post_data['post_excerpt'] = sanitize_text_field($params['excerpt']);
}
$post_id = wp_insert_post($post_data);
if (is_wp_error($post_id)) {
return new WP_Error(
'rest_post_creation_failed',
'Erreur lors de la création du post.',
['status' => 500]
);
}
if (!empty($params['categories']) && is_array($params['categories'])) {
wp_set_post_categories($post_id, $params['categories']);
}
if (!empty($params['tags']) && is_array($params['tags'])) {
wp_set_post_tags($post_id, $params['tags']);
}
return rest_ensure_response($this->format_post_data(get_post($post_id)));
}Validation des données
Fonction réutilisable pour valider les données d’entrée avant création ou mise à jour :
private function validate_post_data($params) {
if (empty($params['title'])) {
return new WP_Error(
'rest_missing_title',
'Le titre est obligatoire.',
['status' => 400]
);
}
if (empty($params['content'])) {
return new WP_Error(
'rest_missing_content',
'Le contenu est obligatoire.',
['status' => 400]
);
}
return true;
}Mettre à jour un article (PUT)
public function update_post($request) {
$post_id = $request['id'];
$params = $request->get_params();
$post = get_post($post_id);
if (!$post) {
return new WP_Error(
'rest_post_not_found',
'Post non trouvé.',
['status' => 404]
);
}
$validation = $this->validate_post_data($params);
if (is_wp_error($validation)) {
return $validation;
}
$post_data = [
'ID' => $post_id,
'post_title' => sanitize_text_field($params['title']),
'post_content' => wp_kses_post($params['content']),
];
$result = wp_update_post($post_data);
if (is_wp_error($result)) {
return new WP_Error(
'rest_post_update_failed',
'Erreur lors de la mise à jour.',
['status' => 500]
);
}
return rest_ensure_response($this->format_post_data(get_post($post_id)));
}Supprimer un article (DELETE)
public function delete_post($request) {
$post_id = $request['id'];
$post = get_post($post_id);
if (!$post) {
return new WP_Error(
'rest_post_not_found',
'Post non trouvé.',
['status' => 404]
);
}
$result = wp_delete_post($post_id, true); // true = suppression définitive
if (!$result) {
return new WP_Error(
'rest_post_delete_failed',
'Erreur lors de la suppression.',
['status' => 500]
);
}
return rest_ensure_response([
'deleted' => true,
'previous' => $this->format_post_data($post),
]);
}Résumé
| Action | Méthode HTTP | Route | Sécurisée ? |
|---|---|---|---|
| Liste des posts | GET | /wp-json/ | ❌ (publique) |
| Créer un post | POST | /wp-json/ | ✅ (nonce + rôle) |
| Lire un post | GET | /wp-json/ | ❌ |
| Mettre à jour | PUT | /wp-json/webpixelia/v1/posts/ID | ✅ |
| Supprimer un post | DELETE | /wp-json/ | ✅ |
Consommer l’API dans une application front
Maintenant que ton API REST est fonctionnelle, voyons comment l’utiliser dans une application front-end (JavaScript) ou côté serveur (PHP).
Tu peux ainsi connecter ton WordPress à un site React, une app Vue, un script Node, ou tout autre service.
Requête fetch en JavaScript
Tu peux utiliser l’API REST dans n’importe quel frontend moderne via fetch. Voici une classe JavaScript complète pour interagir facilement avec ton API :
class WordPressAPI {
constructor(baseUrl) {
this.baseUrl = baseUrl.replace(/\/+$/, '');
this.nonce = null;
this.initNonce();
}
// Récupère le nonce WordPress (si injecté côté front via wp_localize_script)
async initNonce() {
if (typeof wpApiSettings !== 'undefined') {
this.nonce = wpApiSettings.nonce;
}
}
// GET - Liste des posts
async getPosts(params = {}) {
const url = new URL(`${this.baseUrl}/wp-json/webpixelia/v1/posts`);
Object.keys(params).forEach(key =>
url.searchParams.append(key, params[key])
);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur HTTP : ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Erreur lors de la récupération des posts :', error);
throw error;
}
}
// POST - Créer un post
async createPost(postData) {
try {
const response = await fetch(`${this.baseUrl}/wp-json/webpixelia/v1/posts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': this.nonce
},
body: JSON.stringify(postData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Erreur lors de la création');
}
return await response.json();
} catch (error) {
console.error('Erreur lors de la création du post :', error);
throw error;
}
}
// PUT - Mettre à jour un post
async updatePost(postId, postData) {
try {
const response = await fetch(`${this.baseUrl}/wp-json/webpixelia/v1/posts/${postId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': this.nonce
},
body: JSON.stringify(postData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Erreur lors de la mise à jour');
}
return await response.json();
} catch (error) {
console.error('Erreur lors de la mise à jour :', error);
throw error;
}
}
// DELETE - Supprimer un post
async deletePost(postId) {
try {
const response = await fetch(`${this.baseUrl}/wp-json/webpixelia/v1/posts/${postId}`, {
method: 'DELETE',
headers: {
'X-WP-Nonce': this.nonce
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Erreur lors de la suppression');
}
return await response.json();
} catch (error) {
console.error('Erreur lors de la suppression :', error);
throw error;
}
}
}Exemple d’utilisation
const api = new WordPressAPI('https://tonsite.com');
// Exemple : récupérer les 5 derniers articles de la catégorie "tech"
api.getPosts({ per_page: 5, category: 'tech' })
.then(posts => {
posts.forEach(post => {
console.log(`${post.title} - ${post.date}`);
});
});
// Exemple : créer un nouvel article
api.createPost({
title: 'Mon nouveau post',
content: '<p>Voici un contenu riche...</p>',
excerpt: 'Un résumé rapide',
categories: [1, 2],
tags: ['js', 'wordpress']
})
.then(post => {
console.log('Post créé :', post);
});Exemple côté PHP (client serveur)
Tu veux interagir avec ton API depuis une autre instance WordPress, un thème, un plugin, ou un script distant ? Voici une classe PHP simple et efficace :
class WordPressAPIClient {
private $base_url;
private $auth_token;
public function __construct($base_url, $auth_token = null) {
$this->base_url = rtrim($base_url, '/');
$this->auth_token = $auth_token;
}
public function get_posts($params = []) {
$url = $this->base_url . '/wp-json/webpixelia/v1/posts';
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
$response = wp_remote_get($url, [
'headers' => $this->get_headers()
]);
if (is_wp_error($response)) {
return false;
}
return json_decode(wp_remote_retrieve_body($response), true);
}
public function create_post($data) {
$url = $this->base_url . '/wp-json/webpixelia/v1/posts';
$response = wp_remote_post($url, [
'headers' => $this->get_headers(),
'body' => json_encode($data)
]);
if (is_wp_error($response)) {
return false;
}
return json_decode(wp_remote_retrieve_body($response), true);
}
private function get_headers() {
$headers = [
'Content-Type' => 'application/json'
];
if ($this->auth_token) {
$headers['Authorization'] = 'Bearer ' . $this->auth_token;
}
return $headers;
}
}Et côté usage PHP :
$client = new WordPressAPIClient('https://tonsite.com', 'jeton_jwt_facultatif');
$posts = $client->get_posts(['per_page' => 5]);
foreach ($posts as $post) {
echo $post['title'] . '<br>';
}
$new_post = $client->create_post([
'title' => 'Nouveau depuis PHP',
'content' => '<p>Contenu riche en HTML</p>',
'tags' => ['php', 'api']
]);Résumé des bonnes pratiques
- En JS, utilise
X-WP-Noncepour les appels sécurisés (POST, PUT, DELETE) - En PHP ou pour des apps externes, prévois une authentification (JWT, OAuth, clé API…)
- Structure ton client côté front comme une classe modulaire
- Envoie toujours les données en JSON
- Gère proprement les erreurs (
try/catchou contrôle du code HTTP)
Gestion des erreurs et codes de statut HTTP
Une bonne API ne se contente pas de retourner des données quand tout va bien. Elle doit aussi :
- Renvoyer des messages d’erreur clairs,
- Fournir le bon code HTTP pour que le client sache quoi faire,
- Offrir une structure homogène pour faciliter le débogage et l’intégration.
Centraliser les erreurs
Créer une méthode dédiée handle_error() est une bonne pratique pour éviter les répétitions et standardiser le retour d’erreur.
public function handle_error($error_code, $message, $status = 400) {
return new WP_Error(
$error_code,
$message,
['status' => $status]
);
}Bonus : tu pourrais même la rendre statique ou l’extraire dans un trait si tu fais grossir ta classe.
Exemple concret : lire un post
Voici une méthode get_post() bien gérée avec des erreurs explicites et cohérentes :
public function get_post($request) {
$post_id = $request['id'];
$post = get_post($post_id);
if (!$post) {
return $this->handle_error(
'rest_post_not_found',
'Le post demandé n\'existe pas.',
404
);
}
if ($post->post_status !== 'publish') {
return $this->handle_error(
'rest_post_not_published',
'Ce post n\'est pas publié.',
403
);
}
return rest_ensure_response($this->format_post_data($post));
}Codes HTTP à connaître
Voici les principaux codes HTTP que ton API devrait utiliser :
| Code | Signification | Quand l’utiliser |
|---|---|---|
200 | OK | Requête réussie |
201 | Created | Ressource créée (POST) |
204 | No Content | Suppression réussie sans contenu de retour |
400 | Bad Request | Requête mal formée ou données invalides |
401 | Unauthorized | Authentification manquante ou invalide |
403 | Forbidden | Utilisateur authentifié mais non autorisé |
404 | Not Found | Ressource inexistante |
405 | Method Not Allowed | Mauvaise méthode HTTP (ex : GET sur POST-only) |
500 | Internal Server Error | Erreur serveur générale (ex : échec d’insertion) |
Utilisation dans d’autres méthodes
Tu peux maintenant intégrer handle_error() un peu partout, par exemple :
👉 Création
if (empty($params['title'])) {
return $this->handle_error(
'rest_missing_title',
'Le titre est requis.',
400
);
}👉 Mise à jour
$post = get_post($post_id);
if (!$post) {
return $this->handle_error(
'rest_post_not_found',
'Impossible de mettre à jour un post inexistant.',
404
);
}👉 Authentification
if (!wp_verify_nonce($nonce, 'wp_rest')) {
return $this->handle_error(
'rest_nonce_invalid',
'Le nonce est invalide ou expiré.',
401
);
}Exemple de retour d’erreur
Voici ce que verra un client quand une erreur survient (ex. post non trouvé) :
{
"code": "rest_post_not_found",
"message": "Le post demandé n'existe pas.",
"data": {
"status": 404
}
}➡️ Grâce à ça, le frontend peut facilement :
- Afficher un message à l’utilisateur
- Lancer une redirection
- Relancer une authentification si nécessaire
En résumé
- Documente les erreurs possibles pour chaque endpoint (dans ta doc ou README)
- Utilise toujours des codes de statut explicites
- Garde une structure uniforme avec
WP_Error - Centralise la gestion via une fonction comme
handle_error()
Sécurité avancée
La sécurité, ce n’est pas une option. Si tu ouvres ton API au monde extérieur, tu dois anticiper :
- les requêtes cross-origin (navigateur)
- l’authentification des utilisateurs
- les abus (spam, attaques par brute force, etc.)
On passe à la pratique :
Gérer les requêtes Cross-Origin (CORS)
Par défaut, une API REST WordPress n’accepte pas les requêtes cross-domain. Tu dois donc gérer manuellement les headers CORS si ton front est hébergé ailleurs que ton WordPress.
Implémentation :
public function handle_cors() {
// Autorise ton domaine (ou plusieurs, si nécessaire)
header('Access-Control-Allow-Origin: https://tondomaine.com');
// Méthodes HTTP autorisées
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
// Headers autorisés (dont Authorization et X-WP-Nonce)
header('Access-Control-Allow-Headers: Content-Type, X-WP-Nonce, Authorization');
// Permet l'envoi des cookies/session
header('Access-Control-Allow-Credentials: true');
// Pré-vol (OPTIONS) → on arrête là
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
}À activer dans ton plugin ou class constructor :
add_action('init', array($this, 'handle_cors'));⚠️ Tu peux aussi vérifier le
Originreçu avec$_SERVER['HTTP_ORIGIN']pour valider dynamiquement.
Authentification JWT (JSON Web Token)
Pour authentifier des utilisateurs distants (ex : appli mobile, React, etc.), le plugin JWT Authentication for WP REST API est une solution efficace.
Étapes d’installation :
- Installe le plugin JWT Authentication
- Dans
wp-config.php, ajoute :
define('JWT_AUTH_SECRET_KEY', 'cle_super_secrete');
define('JWT_AUTH_CORS_ENABLE', true);- Dans
.htaccess(Apache), ajoute :
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]Obtenir un token (client JS)
const response = await fetch('/wp-json/jwt-auth/v1/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'admin',
password: 'motdepassefort'
})
});
const { token } = await response.json();Utiliser le token :
fetch('/wp-json/webpixelia/v1/posts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ title: 'Mon post sécurisé', content: '...' })
});Pour autoriser certains endpoints sans token :
add_filter('jwt_auth_whitelist', function($endpoints) {
$endpoints[] = '/wp-json/webpixelia/v1/posts';
return $endpoints;
});Rate Limiting & Protection contre les abus
WordPress ne limite pas nativement le nombre de requêtes, mais tu peux :
Utiliser un pare-feu serveur (recommandé)
- Cloudflare : règle des requêtes par IP
- ModSecurity ou fail2ban sur Apache/Nginx
Ajouter un système de limite maison
Exemple (très simple) de limitation par IP en PHP :
public function rate_limit_check($request) {
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'api_rate_limit_' . md5($ip);
$limit = 100; // max requêtes
$timeout = HOUR_IN_SECONDS;
$count = get_transient($key);
if ($count === false) {
set_transient($key, 1, $timeout);
} elseif ($count < $limit) {
set_transient($key, $count + 1, $timeout);
} else {
return $this->handle_error(
'rest_rate_limited',
'Trop de requêtes. Réessaie plus tard.',
429
);
}
return true;
}À appeler dans ta permission_callback.
Résumé des protections recommandées
| Objectif | Outil / méthode |
|---|---|
| Autoriser un front externe | Headers CORS (handle_cors()) |
| Authentifier les utilisateurs | JWT Auth ou X-WP-Nonce |
| Empêcher les abus | Rate limiting (PHP ou firewall) |
| Sécuriser les routes critiques | current_user_can(), rôles et capacités |
| Protéger les données sensibles | Désactive l’index global (/wp-json/) si besoin |
Tests & documentation de ton API
Créer une API, c’est bien. Mais pouvoir la tester, la documenter, et la monitorer facilement, c’est indispensable pour la faire évoluer sans galère.
Tester avec Postman
Postman est l’outil idéal pour tester manuellement chaque endpoint de ton API, vérifier les retours, ajouter des tokens ou des headers, etc.
Exemple de collection Postman
Tu peux créer une collection Postman avec tes endpoints principaux. Voici une base que tu peux exporter/importer :
{
"info": {
"name": "WordPress Custom API",
"_postman_id": "api-wp-webpixelia",
"description": "API personnalisée pour WordPress",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "GET Posts",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/wp-json/webpixelia/v1/posts?per_page=5",
"host": ["{{base_url}}"],
"path": ["wp-json", "webpixelia", "v1", "posts"],
"query": [
{ "key": "per_page", "value": "5" }
]
}
}
},
{
"name": "POST Create Post",
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "X-WP-Nonce", "value": "{{nonce}}" }
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Post de test\",\n \"content\": \"<p>Contenu de test</p>\",\n \"categories\": [1]\n}"
},
"url": {
"raw": "{{base_url}}/wp-json/webpixelia/v1/posts",
"host": ["{{base_url}}"],
"path": ["wp-json", "webpixelia", "v1", "posts"]
}
}
}
]
}
Bonnes pratiques avec Postman :
- Utilise des variables d’environnement (
{{base_url}},{{nonce}}, etc.) - Ajoute des tests automatiques (
pm.test(...)) pour valider les statuts - Gère un workflow JWT si tu protèges ton API
Documentation automatique
Une API sans documentation, c’est comme un code sans commentaire : inutilisable par d’autres (ou toi dans 6 mois 😅).
Tu peux exposer un endpoint /schema pour fournir une description claire de ton API.
Exemple de documentation statique en JSON
public function get_schema() {
return array(
'title' => 'Custom Posts API',
'version' => '1.0.0',
'description' => 'API REST personnalisée pour gérer les posts WordPress',
'endpoints' => array(
'/posts' => array(
'methods' => array('GET', 'POST'),
'description' => 'Lister ou créer des articles'
),
'/posts/{id}' => array(
'methods' => array('GET', 'PUT', 'DELETE'),
'description' => 'Afficher, modifier ou supprimer un article'
)
)
);
}Endpoint de documentation :
register_rest_route($namespace, '/schema', array(
'methods' => 'GET',
'callback' => array($this, 'get_schema'),
'permission_callback' => '__return_true'
));💡 Pour aller plus loin
Tu peux intégrer ta documentation avec :
- Redoc ou Swagger UI pour une interface HTML
- OpenAPI Generator : génère une doc + clients (JS, PHP, Python…) à partir d’un fichier YAML ou JSON
- wp-rest-api-controller (plugin) pour exposer automatiquement des routes documentées
Monitoring & Logs de l’API
Il est essentiel de garder un œil sur ce qu’il se passe sur ton API, notamment pour :
- Déboguer plus facilement,
- Repérer des comportements suspects,
- Auditer l’activité des utilisateurs.
Logger chaque requête REST
Tu as déjà mis en place un hook via rest_post_dispatch, très bien.
Voici un rappel complet avec quelques ajustements possibles :
public function log_api_request($request, $response) {
$log_data = array(
'timestamp' => current_time('mysql'),
'endpoint' => $request->get_route(),
'method' => $request->get_method(),
'user_id' => get_current_user_id(),
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'response_code' => method_exists($response, 'get_status') ? $response->get_status() : 200,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'body_params' => $request->get_body_params(), // Attention aux données sensibles
);
// Masquer les mots de passe ou tokens dans le log
if (isset($log_data['body_params']['password'])) {
$log_data['body_params']['password'] = '[REDACTED]';
}
// Écriture dans le log WordPress
error_log('API Request: ' . json_encode($log_data));
}👉 N’oublie pas d’attacher la méthode dans ton constructeur :
add_action('rest_post_dispatch', array($this, 'log_api_request'), 10, 2);Astuces & bonnes pratiques
Évite de logger des infos sensibles
- Masque les mots de passe, tokens, etc.
- Tu peux créer une liste blanche de routes à logger (ou une liste noire).
Alternative : stocker dans la base
Si tu veux persister les logs plus longtemps et les exploiter, tu peux créer une table personnalisée :
CREATE TABLE wp_api_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
timestamp DATETIME NOT NULL,
endpoint VARCHAR(255),
method VARCHAR(10),
user_id BIGINT,
ip VARCHAR(45),
response_code INT,
user_agent TEXT,
request_payload LONGTEXT
);Et insérer les données via global $wpdb.
Pour aller plus loin
| Objectif | Outil ou méthode |
|---|---|
| Voir les requêtes REST | Plugin Query Monitor |
| Voir les erreurs PHP | Fichier wp-content/debug.log |
| Logs externes centralisés | Services comme Sentry, Loggly, Datadog |
| Alertes en cas d’erreurs | Plugin + webhook Slack/Discord/email |
| Statistiques API | Crée un tableau de bord custom dans l’admin |
Exemple de visualisation dans l’admin (bonus)
Tu peux afficher les 20 dernières requêtes dans une page admin (tableau, tableau de bord, etc.) :
SELECT * FROM wp_api_logs ORDER BY timestamp DESC LIMIT 20Ou même créer un widget sur le wp_dashboard_setup.
Conclusion & bonnes pratiques
Félicitations ! Tu as maintenant une API REST complète, sécurisée et extensible pour WordPress. Tu peux l’utiliser pour alimenter un front React, une app mobile, ou des services tiers.
Voici un récap des points essentiels :
Sécurité avant tout
- Toujours valider et sanitiser les données entrantes (
sanitize_text_field,wp_kses_post, etc.) - Utilise les nonces WordPress (
X-WP-Nonce) ou JWT pour l’authentification - Implémente des permissions granulaires (
current_user_can, rôles) - Gère correctement les CORS pour les requêtes cross-domain
Performance & robustesse
- Active un rate limiting simple (ou via firewall)
- Utilise la pagination (
per_page,page) pour éviter les gros payloads - Optimise tes requêtes WP_Query (évite les méta_query lourdes, post_status inutiles)
- Implémente un cache côté serveur ou client (transient, REST cache, localStorage…)
Maintenance & testabilité
- Versionne ton API proprement (
/v1/,/v2/) - Logge les requêtes importantes (surtout POST/PUT/DELETE)
- Documente chaque endpoint (ex:
/schema, OpenAPI, Redoc) - Teste régulièrement avec Postman ou en scripts automatisés
Évolutivité & architecture
- Structure ton code dans des classes ou modules propres (évite le spaghetti PHP)
- Utilise les hooks WordPress pour l’extensibilité (
apply_filters,do_action) - Prévois des extensions futures (nouveaux CPTs, filtres dynamiques, etc.)
- Respecte les standards REST (GET, POST, PUT, DELETE, statuts HTTP, pagination, erreurs)
Prochaines étapes possibles
Tu peux aller encore plus loin :
- Ajouter des tests automatisés (PHPUnit côté serveur, tests fetch côté JS)
- Créer une interface admin pour configurer l’API (endpoints activés, rate limiting, logs…)
- Implémenter des webhooks pour notifier d’autres services (Zapier, Discord, Slack…)
- Ajouter des endpoints pour gérer les médias (upload, thumbnails…)
- Intégrer des services tiers comme Stripe, Mailchimp, Algolia, etc.

Laisser un commentaire