Les Custom Post Types WordPress : au-delà des articles et pages
Par défaut, WordPress propose 2 types de contenu : les articles (blog) et les pages (contenu statique). Mais si vous gérez des biens immobiliers, un catalogue de produits, des témoignages, des événements ou un portfolio, vous avez besoin de types de contenu personnalisés (Custom Post Types — CPT). Les CPT sont la fonctionnalité qui transforme WordPress d’un simple blog en un véritable CMS capable de gérer n’importe quel type de données.
1. Quand créer un Custom Post Type
Créez un CPT quand votre contenu :
- A une structure différente des articles classiques (champs spécifiques)
- Doit être filtré et recherché indépendamment
- A besoin de son propre template d’affichage
- N’entre pas naturellement dans les catégories du blog
Exemples concrets
| Type de site | CPT recommandés |
|---|---|
| Agence immobilière | Biens, Agents, Témoignages |
| Restaurant | Menu (plats), Événements, Réservations |
| Agence web | Portfolio, Services, Équipe, Témoignages |
| École/Formation | Cours, Formateurs, Sessions |
| ONG | Projets, Rapports, Partenaires |
| E-commerce (hors WooCommerce) | Produits, Commandes, Avis |
2. Créer un CPT manuellement (functions.php)
Voici un exemple complet pour un CPT « Service » destiné à une agence web sénégalaise :
// Enregistrer le CPT "Service"
add_action('init', function() {
register_post_type('service', [
'labels' => [
'name' => 'Services',
'singular_name' => 'Service',
'add_new' => 'Ajouter un service',
'add_new_item' => 'Ajouter un nouveau service',
'edit_item' => 'Modifier le service',
'new_item' => 'Nouveau service',
'view_item' => 'Voir le service',
'search_items' => 'Rechercher un service',
'not_found' => 'Aucun service trouvé',
'not_found_in_trash' => 'Aucun service dans la corbeille',
'all_items' => 'Tous les services',
'menu_name' => 'Services',
],
'public' => true,
'has_archive' => true,
'rewrite' => ['slug' => 'services', 'with_front' => false],
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'page-attributes'],
'menu_icon' => 'dashicons-portfolio',
'menu_position' => 5,
'show_in_rest' => true, // Active Gutenberg et l'API REST
'publicly_queryable' => true,
'capability_type' => 'post',
]);
});
// IMPORTANT : après avoir ajouté ce code, allez dans
// Réglages → Permaliens et cliquez "Enregistrer"
// pour régénérer les règles de réécriture
Explication des paramètres clés
| Paramètre | Valeur | Effet |
|---|---|---|
public |
true | Visible sur le site et dans l’admin |
has_archive |
true | Crée une page d’archive à /services/ |
rewrite |
[‘slug’ => ‘services’] | URL : votresite.com/services/nom-du-service |
supports |
tableau | Fonctionnalités de l’éditeur (titre, contenu, image…) |
show_in_rest |
true | Compatible Gutenberg et API REST |
menu_icon |
dashicons-* | Icône dans le menu admin |
3. Taxonomies personnalisées
Les taxonomies organisent vos CPT comme les catégories et tags organisent les articles :
// Taxonomie hiérarchique (comme les catégories)
add_action('init', function() {
register_taxonomy('type_service', 'service', [
'labels' => [
'name' => 'Types de service',
'singular_name' => 'Type de service',
'search_items' => 'Rechercher un type',
'all_items' => 'Tous les types',
'edit_item' => 'Modifier le type',
'add_new_item' => 'Ajouter un type',
],
'hierarchical' => true, // true = catégories, false = tags
'rewrite' => ['slug' => 'type-service'],
'show_in_rest' => true,
'show_admin_column' => true, // Colonne dans la liste admin
]);
// Ajouter des termes par défaut
$types = ['Développement web', 'Design graphique', 'SEO',
'Marketing digital', 'Formation', 'Maintenance'];
foreach ($types as $type) {
if (!term_exists($type, 'type_service')) {
wp_insert_term($type, 'type_service');
}
}
});
// Taxonomie plate (comme les tags)
add_action('init', function() {
register_taxonomy('technologie', 'service', [
'labels' => [
'name' => 'Technologies',
'singular_name' => 'Technologie',
],
'hierarchical' => false,
'rewrite' => ['slug' => 'tech'],
'show_in_rest' => true,
]);
});
4. Champs personnalisés avec ACF
Les CPT deviennent vraiment puissants avec des champs personnalisés. Advanced Custom Fields (ACF) est l’outil standard :
- Installez ACF depuis Extensions → Ajouter
- Allez dans ACF → Groupes de champs → Ajouter
- Créez un groupe « Détails du service »
- Condition d’affichage : « Type de publication = Service »
Champs recommandés pour un CPT « Service »
| Champ | Type ACF | Usage |
|---|---|---|
| Prix à partir de | Number | Montant en FCFA |
| Durée estimée | Text | « 2-4 semaines » |
| Icône | Select ou Image | Icône visuelle du service |
| Points clés | Repeater | Liste de bénéfices |
| Portfolio associé | Relationship | Lier aux réalisations |
| CTA texte | Text | « Demander un devis » |
| CTA lien | URL | Lien WhatsApp ou formulaire |
Afficher les champs ACF dans le template
// Dans single-service.php
<?php get_header(); ?>
<article class="service-detail">
<div class="service-detail__hero">
<?php if (has_post_thumbnail()) : ?>
<img src="<?php the_post_thumbnail_url('large'); ?>"
alt="<?php the_title_attribute(); ?>" class="service-detail__image">
<?php endif; ?>
<div class="service-detail__info">
<h1><?php the_title(); ?></h1>
<?php if ($prix = get_field('prix')) : ?>
<p class="service-price">
À partir de <strong><?php echo number_format($prix, 0, ',', ' '); ?> FCFA</strong>
</p>
<?php endif; ?>
<?php if ($duree = get_field('duree_estimee')) : ?>
<p class="service-duration">Durée : <?php echo esc_html($duree); ?></p>
<?php endif; ?>
</div>
</div>
<div class="service-detail__content">
<?php the_content(); ?>
</div>
<!-- Points clés (champ Repeater) -->
<?php if (have_rows('points_cles')) : ?>
<div class="service-features">
<h2>Ce qui est inclus</h2>
<ul>
<?php while (have_rows('points_cles')) : the_row(); ?>
<li><?php the_sub_field('point'); ?></li>
<?php endwhile; ?>
</ul>
</div>
<?php endif; ?>
<!-- CTA -->
<?php $cta_link = get_field('cta_lien'); ?>
<?php if ($cta_link) : ?>
<div class="service-cta">
<a href="<?php echo esc_url($cta_link); ?>" class="btn-primary">
<?php echo esc_html(get_field('cta_texte') ?: 'Demander un devis'); ?>
</a>
</div>
<?php endif; ?>
<!-- Réalisations associées -->
<?php $portfolio = get_field('portfolio_associe'); ?>
<?php if ($portfolio) : ?>
<div class="service-portfolio">
<h2>Réalisations dans ce domaine</h2>
<div class="portfolio-grid">
<?php foreach ($portfolio as $project) : ?>
<a href="<?php echo get_permalink($project->ID); ?>" class="portfolio-item">
<?php echo get_the_post_thumbnail($project->ID, 'medium'); ?>
<h3><?php echo esc_html($project->post_title); ?></h3>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</article>
<?php get_footer(); ?>
5. Créer un CPT sans code : plugins
Custom Post Type UI (CPT UI)
Si vous ne voulez pas toucher au code, CPT UI (gratuit) offre une interface pour créer des CPT et taxonomies :
- Installez CPT UI depuis Extensions → Ajouter
- CPT UI → Ajouter un type de publication
- Remplissez le slug, les labels et les options
- Cliquez « Ajouter le type de publication »
Avantage : pas de code. Limite : si vous désactivez le plugin, les CPT disparaissent du menu (les données restent en base). Le code dans functions.php est plus durable.
6. Templates de CPT
WordPress utilise la hiérarchie de templates pour afficher les CPT :
// Page individuelle d'un service :
single-service.php → Template spécifique
single.php → Fallback générique
// Archive (liste de tous les services) :
archive-service.php → Template spécifique
archive.php → Fallback générique
// Taxonomie :
taxonomy-type_service.php → Template par taxonomie
taxonomy.php → Fallback
archive.php → Fallback générique
Archive personnalisée (archive-service.php)
<?php get_header(); ?>
<section class="services-archive">
<h1>Nos Services</h1>
<!-- Filtres par type -->
<div class="service-filters">
<a href="<?php echo get_post_type_archive_link('service'); ?>"
class="filter-btn active">Tous</a>
<?php
$types = get_terms(['taxonomy' => 'type_service', 'hide_empty' => true]);
foreach ($types as $type) : ?>
<a href="<?php echo get_term_link($type); ?>"
class="filter-btn"><?php echo esc_html($type->name); ?></a>
<?php endforeach; ?>
</div>
<div class="services-grid">
<?php while (have_posts()) : the_post(); ?>
<div class="service-card">
<?php if (has_post_thumbnail()) : ?>
<img src="<?php the_post_thumbnail_url('medium'); ?>"
alt="<?php the_title_attribute(); ?>" loading="lazy">
<?php endif; ?>
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php the_excerpt(); ?></p>
<?php if ($prix = get_field('prix')) : ?>
<span class="price">À partir de <?php echo number_format($prix, 0, ',', ' '); ?> FCFA</span>
<?php endif; ?>
<a href="<?php the_permalink(); ?>" class="btn-secondary">En savoir plus →</a>
</div>
<?php endwhile; ?>
</div>
<?php the_posts_pagination(); ?>
</section>
<?php get_footer(); ?>
7. Requêtes WP_Query sur les CPT
// Afficher les 6 derniers services sur la page d'accueil
$services = new WP_Query([
'post_type' => 'service',
'posts_per_page' => 6,
'orderby' => 'menu_order',
'order' => 'ASC',
]);
if ($services->have_posts()) :
while ($services->have_posts()) : $services->the_post();
// Afficher chaque service
endwhile;
wp_reset_postdata();
endif;
// Filtrer par taxonomie
$services_dev = new WP_Query([
'post_type' => 'service',
'tax_query' => [
[
'taxonomy' => 'type_service',
'field' => 'slug',
'terms' => 'developpement-web',
],
],
]);
// Filtrer par champ ACF (meta_query)
$services_abordables = new WP_Query([
'post_type' => 'service',
'meta_query' => [
[
'key' => 'prix',
'value' => 100000,
'compare' => '<=',
'type' => 'NUMERIC',
],
],
'orderby' => 'meta_value_num',
'meta_key' => 'prix',
'order' => 'ASC',
]);
8. Exposer le CPT dans l’API REST
// Avec show_in_rest => true dans register_post_type,
// votre CPT est accessible via l'API REST :
// GET tous les services :
// https://votresite.com/wp-json/wp/v2/service
// GET un service spécifique :
// https://votresite.com/wp-json/wp/v2/service/123
// Exposer les champs ACF dans l'API REST :
add_action('rest_api_init', function() {
register_rest_field('service', 'prix', [
'get_callback' => function($post) {
return get_field('prix', $post['id']);
},
]);
register_rest_field('service', 'duree', [
'get_callback' => function($post) {
return get_field('duree_estimee', $post['id']);
},
]);
});
9. Bonnes pratiques CPT
- Nommage : le slug du CPT doit être court, en minuscules, sans accents (ex:
service, pasnos-services-professionnels) - Pas de conflit : évitez les slugs réservés par WordPress : post, page, attachment, revision, nav_menu_item
- Flush des permaliens : après chaque ajout/modification de CPT, allez dans Réglages → Permaliens et sauvegardez
- Préfixez vos CPT : utilisez un préfixe pour éviter les conflits avec les plugins (ex:
itsc_service) - Code dans un plugin : placez le code CPT dans un plugin (mu-plugin) plutôt que dans functions.php — cela dissocie les données du thème. Si vous changez de thème, vos CPT restent
// Créer un mu-plugin : wp-content/mu-plugins/custom-post-types.php
<?php
/**
* Plugin Name: Custom Post Types
* Description: CPT personnalisés pour le site
*/
// Tout le code register_post_type et register_taxonomy ici
// Les mu-plugins sont chargés automatiquement, avant les autres plugins
Les Custom Post Types sont la fonctionnalité la plus puissante de WordPress pour créer des sites sur mesure. Combinés avec ACF pour les champs personnalisés et des templates dédiés, ils permettent de gérer n’importe quel type de contenu structuré sans aucun plugin lourd.