Cinquante places, pas une de plus. C’est la contrainte que nous avons reçue d’une communauté de communes qui organisait sa réunion publique annuelle dans une salle municipale soumise à un plafond ERP strict. Le formulaire d’inscription existait déjà, construit avec un simple Custom Post Type « participant » relié à l’événement par un champ personnalisé. Ce qui manquait, c’était l’affichage : combien de places restait-il, en direct, sans recharger la page toutes les cinq minutes pour vérifier ?
Poser une extension de billetterie complète pour un besoin aussi ponctuel n’avait pas de sens : la collectivité gère trois ou quatre événements par an, pas un programme de festival. Nous avons donc construit un compteur minimal, calculé à la volée à partir des inscriptions déjà enregistrées, sans jamais toucher à un système de paiement ou de réservation.
Modéliser la capacité de l’événement
Chaque événement est un article du CPT evenement, avec un champ personnalisé capacite_max enregistré via register_post_meta(). Les inscriptions, elles, sont des entrées du CPT inscription, chacune reliée à l’événement par un champ evenement_id. Cette séparation permet de compter les inscriptions sans jamais modifier la fiche de l’événement lui-même, ce qui évite les problèmes de concurrence si plusieurs personnes s’inscrivent au même instant.
- Un champ
capacite_maxde type entier sur l’événement - Un champ
evenement_idde type entier sur chaque inscription - Aucun champ de comptage stocké : tout est recalculé à l’affichage
Compter les inscriptions avec WP_Query
Le comptage repose sur une requête WP_Query ciblée, avec un nombre de résultats réduit au strict nécessaire puisque c’est found_posts qui nous intéresse, pas la liste elle-même.

function wpm_places_restantes( $evenement_id ) {
$capacite = (int) get_post_meta( $evenement_id, 'capacite_max', true );
$query = new WP_Query( array(
'post_type' => 'inscription',
'post_status' => 'publish',
'posts_per_page' => 1,
'meta_query' => array(
array(
'key' => 'evenement_id',
'value' => $evenement_id,
),
),
) );
$inscrits = (int) $query->found_posts;
return max( 0, $capacite - $inscrits );
}
Le posts_per_page à 1 peut surprendre : on ne veut pas la liste des inscriptions, seulement leur nombre total, exposé par found_posts quel que soit le nombre de résultats réellement retourné. Sur un événement de quelques dizaines de participants, cette requête s’exécute en quelques millisecondes.
Afficher le compteur côté public
Le compteur s’affiche via un shortcode simple, appelé dans le contenu de la page d’inscription :
add_shortcode( 'places_restantes', function( $atts ) {
$atts = shortcode_atts( array( 'evenement' => 0 ), $atts );
$restantes = wpm_places_restantes( (int) $atts['evenement'] );
if ( $restantes <= 0 ) {
return '<p><strong>Complet</strong> — la liste d'attente est ouverte.</p>';
}
return sprintf(
'<p><strong>%d place(s)</strong> encore disponible(s) sur cet événement.</p>',
$restantes
);
} );
Le message « Complet » renvoie implicitement vers une liste d’attente, gérée par ailleurs comme un simple statut sur les inscriptions au-delà du quota — un sujet distinct de celui-ci.
Éviter de recalculer à chaque visite
Pour un événement très consulté (relayé sur les réseaux sociaux de la collectivité, par exemple), recalculer la requête à chaque chargement de page reste négligeable en volume, mais autant s’en prémunir. Un transitoire de courte durée, invalidé à chaque nouvelle inscription, suffit :
set_transient()avec une durée de vie de 60 secondes pour absorber les pics de traficdelete_transient()déclenché sur le hooksave_post_inscription- Aucune dépendance à un cache objet externe, ce transitoire suffit largement à ce volume
Sur les trois événements suivis depuis la mise en production de ce compteur, jamais plus de deux inscriptions simultanées n’ont été enregistrées à la même seconde : le risque de sur-réservation par calcul concurrent reste théorique à cette échelle, mais le transitoire à durée courte le couvre sans effort.
En résumé
Un compteur de places restantes n’a pas besoin d’un module de billetterie : deux CPT reliés par un champ personnalisé, une requête WP_Query qui exploite found_posts, et un shortcode suffisent pour un événement de collectivité. La logique de liste d’attente et la billetterie payante restent des sujets à part entière, à ne raccorder que si le besoin se confirme sur la durée.