Intermediaire 2 min de lecture · 286 mots

Les Custom Post Types et Taxonomies : Guide complet

Estimated reading time: 1 minute

Introduction

Les Custom Post Types (CPT) et les taxonomies personnalisées constituent le fondement d’une architecture de contenu flexible et scalable dans WordPress. En 2025, avec l’évolution du Full Site Editing et l’importance croissante du SEO structuré, maîtriser ces outils est essentiel pour créer des sites WordPress performants et bien organisés. Ce guide vous accompagnera dans la création, l’optimisation et la gestion avancée des CPT et taxonomies.

Comprendre les Custom Post Types

Quand utiliser un Custom Post Type

Les Custom Post Types sont appropriés lorsque vous avez besoin de :

  • Séparer différents types de contenu (produits, témoignages, portfolios)
  • Appliquer des workflows de publication différents
  • Créer des templates spécifiques
  • Organiser du contenu avec des métadonnées distinctes
  • Améliorer le SEO avec des structures d’URL personnalisées
  • Exemples concrets :

  • Site immobilier : property (propriété)
  • Site événementiel : event (événement)
  • Site portfolio : project (projet)
  • Site e-learning : course (cours)
  • Créer un Custom Post Type complet

    Voici un exemple professionnel et testé pour un CPT « Portfolio » :


  Custom Post Type : Portfolio
 
  @package MonTheme
 /

namespace MonThemePostTypes;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Portfolio {

    /
      Slug du post type
     /
    private $posttype = 'portfolio';

    /
      Constructeur
     /
    public function construct() {
        addaction( 'init', array( $this, 'registerposttype' ) );
        addaction( 'init', array( $this, 'registertaxonomies' ) );
        addfilter( 'entertitlehere', array( $this, 'changetitleplaceholder' ), 10, 2 );
        addaction( 'addmetaboxes', array( $this, 'addmetaboxes' ) );
        addaction( 'savepost', array( $this, 'savemetaboxes' ), 10, 2 );
        addfilter( 'manage' . $this->posttype . 'postscolumns', array( $this, 'customcolumns' ) );
        addaction( 'manage' . $this->posttype . 'postscustomcolumn', array( $this, 'customcolumncontent' ), 10, 2 );
        addfilter( 'manageedit-' . $this->posttype . 'sortablecolumns', array( $this, 'sortablecolumns' ) );
    }

    /
      Enregistrer le Custom Post Type
     /
    public function registerposttype() {
        $labels = array(
            'name'                  => x( 'Projets Portfolio', 'Post type general name', 'mon-theme' ),
            'singularname'         => x( 'Projet', 'Post type singular name', 'mon-theme' ),
            'menuname'             => x( 'Portfolio', 'Admin Menu text', 'mon-theme' ),
            'nameadminbar'        => x( 'Projet', 'Add New on Toolbar', 'mon-theme' ),
            'addnew'               => ( 'Ajouter un projet', 'mon-theme' ),
            'addnewitem'          => ( 'Ajouter un nouveau projet', 'mon-theme' ),
            'newitem'              => ( 'Nouveau projet', 'mon-theme' ),
            'edititem'             => ( 'Modifier le projet', 'mon-theme' ),
            'viewitem'             => ( 'Voir le projet', 'mon-theme' ),
            'allitems'             => ( 'Tous les projets', 'mon-theme' ),
            'searchitems'          => ( 'Rechercher des projets', 'mon-theme' ),
            'parentitemcolon'     => ( 'Projet parent:', 'mon-theme' ),
            'notfound'             => ( 'Aucun projet trouvé.', 'mon-theme' ),
            'notfoundintrash'    => ( 'Aucun projet trouvé dans la corbeille.', 'mon-theme' ),
            'featuredimage'        => x( 'Image du projet', 'Overrides the "Featured Image" phrase', 'mon-theme' ),
            'setfeaturedimage'    => x( 'Définir l'image du projet', 'Overrides the "Set featured image" phrase', 'mon-theme' ),
            'removefeaturedimage' => x( 'Retirer l'image du projet', 'Overrides the "Remove featured image" phrase', 'mon-theme' ),
            'usefeaturedimage'    => x( 'Utiliser comme image du projet', 'Overrides the "Use as featured image" phrase', 'mon-theme' ),
            'archives'              => x( 'Archives des projets', 'The post type archive label used in nav menus', 'mon-theme' ),
            'insertintoitem'      => x( 'Insérer dans le projet', 'Overrides the "Insert into post" phrase', 'mon-theme' ),
            'uploadedtothisitem' => x( 'Téléversé vers ce projet', 'Overrides the "Uploaded to this post" phrase', 'mon-theme' ),
            'filteritemslist'     => x( 'Filtrer la liste des projets', 'Screen reader text for the filter links', 'mon-theme' ),
            'itemslistnavigation' => x( 'Navigation de la liste des projets', 'Screen reader text for the pagination', 'mon-theme' ),
            'itemslist'            => x( 'Liste des projets', 'Screen reader text for the items list', 'mon-theme' ),
        );

        $args = array(
            'labels'             => $labels,
            'public'             => true,
            'publiclyqueryable' => true,
            'showui'            => true,
            'showinmenu'       => true,
            'queryvar'          => true,
            'rewrite'            => array(
                'slug'       => 'portfolio',
                'withfront' => false,
                'feeds'      => true,
                'pages'      => true,
            ),
            'capabilitytype'    => 'post',
            'hasarchive'        => true,
            'hierarchical'       => false,
            'menuposition'      => 5,
            'menuicon'          => 'dashicons-portfolio',
            'supports'           => array(
                'title',
                'editor',
                'thumbnail',
                'excerpt',
                'custom-fields',
                'revisions',
                'author',
                'page-attributes',
            ),
            'showinrest'       => true,
            'restbase'          => 'portfolio',
            'restcontrollerclass' => 'WPRESTPostsController',
            'template'           => array(
                array( 'core/image', array() ),
                array( 'core/paragraph', array(
                    'placeholder' => ( 'Décrivez votre projet...', 'mon-theme' ),
                ) ),
            ),
            'templatelock'      => false,
        );

        registerposttype( $this->posttype, $args );
    }

    /
      Enregistrer les taxonomies
     /
    public function registertaxonomies() {
        // Taxonomie : Catégories de portfolio (hiérarchique)
        $categorylabels = array(
            'name'              => x( 'Catégories', 'taxonomy general name', 'mon-theme' ),
            'singularname'     => x( 'Catégorie', 'taxonomy singular name', 'mon-theme' ),
            'searchitems'      => ( 'Rechercher des catégories', 'mon-theme' ),
            'allitems'         => ( 'Toutes les catégories', 'mon-theme' ),
            'parentitem'       => ( 'Catégorie parente', 'mon-theme' ),
            'parentitemcolon' => ( 'Catégorie parente:', 'mon-theme' ),
            'edititem'         => ( 'Modifier la catégorie', 'mon-theme' ),
            'updateitem'       => ( 'Mettre à jour la catégorie', 'mon-theme' ),
            'addnewitem'      => ( 'Ajouter une nouvelle catégorie', 'mon-theme' ),
            'newitemname'     => ( 'Nom de la nouvelle catégorie', 'mon-theme' ),
            'menuname'         => ( 'Catégories', 'mon-theme' ),
        );

        $categoryargs = array(
            'hierarchical'      => true,
            'labels'            => $categorylabels,
            'showui'           => true,
            'showadmincolumn' => true,
            'queryvar'         => true,
            'rewrite'           => array(
                'slug'         => 'categorie-portfolio',
                'withfront'   => false,
                'hierarchical' => true,
            ),
            'showinrest'      => true,
            'restbase'         => 'portfolio-categories',
        );

        registertaxonomy( 'portfoliocategory', array( $this->posttype ), $categoryargs );

        // Taxonomie : Tags de portfolio (non hiérarchique)
        $taglabels = array(
            'name'                       => x( 'Tags', 'taxonomy general name', 'mon-theme' ),
            'singularname'              => x( 'Tag', 'taxonomy singular name', 'mon-theme' ),
            'searchitems'               => ( 'Rechercher des tags', 'mon-theme' ),
            'popularitems'              => ( 'Tags populaires', 'mon-theme' ),
            'allitems'                  => ( 'Tous les tags', 'mon-theme' ),
            'edititem'                  => ( 'Modifier le tag', 'mon-theme' ),
            'updateitem'                => ( 'Mettre à jour le tag', 'mon-theme' ),
            'addnewitem'               => ( 'Ajouter un nouveau tag', 'mon-theme' ),
            'newitemname'              => ( 'Nom du nouveau tag', 'mon-theme' ),
            'separateitemswithcommas' => ( 'Séparer les tags par des virgules', 'mon-theme' ),
            'addorremoveitems'        => ( 'Ajouter ou retirer des tags', 'mon-theme' ),
            'choosefrommostused'      => ( 'Choisir parmi les tags les plus utilisés', 'mon-theme' ),
            'menuname'                  => ( 'Tags', 'mon-theme' ),
        );

        $tagargs = array(
            'hierarchical'      => false,
            'labels'            => $taglabels,
            'showui'           => true,
            'showadmincolumn' => true,
            'queryvar'         => true,
            'rewrite'           => array(
                'slug' => 'tag-portfolio',
            ),
            'showinrest'      => true,
            'restbase'         => 'portfolio-tags',
        );

        registertaxonomy( 'portfoliotag', array( $this->posttype ), $tagargs );
    }

    /
      Changer le placeholder du titre
     /
    public function changetitleplaceholder( $title, $post ) {
        if ( $post->posttype === $this->posttype ) {
            $title = ( 'Entrez le nom du projet', 'mon-theme' );
        }

        return $title;
    }

    /
      Ajouter des meta boxes
     /
    public function addmetaboxes() {
        addmetabox(
            'portfoliodetails',
            ( 'Détails du projet', 'mon-theme' ),
            array( $this, 'renderdetailsmetabox' ),
            $this->posttype,
            'normal',
            'high'
        );

        addmetabox(
            'portfolioclient',
            ( 'Informations client', 'mon-theme' ),
            array( $this, 'renderclientmetabox' ),
            $this->posttype,
            'side',
            'default'
        );
    }

    /
      Render meta box : Détails du projet
     /
    public function renderdetailsmetabox( $post ) {
        // Nonce pour la sécurité
        wpnoncefield( 'portfoliodetailsnonce', 'portfoliodetailsnonce' );

        // Récupérer les valeurs existantes
        $projecturl = getpostmeta( $post->ID, 'portfolioprojecturl', true );
        $projectdate = getpostmeta( $post->ID, 'portfolioprojectdate', true );
        $technologies = getpostmeta( $post->ID, 'portfoliotechnologies', true );
        $githuburl = getpostmeta( $post->ID, 'portfoliogithuburl', true );

        ?>
        

htmle( 'Séparez les technologies par des virgules', 'mon-theme' ); ?>

Render meta box : Informations client / public function render
clientmetabox( $post ) { wpnoncefield( 'portfolioclientnonce', 'portfolioclientnonce' ); $clientname = getpostmeta( $post->ID, 'portfolioclientname', true ); $clientwebsite = getpostmeta( $post->ID, 'portfolioclientwebsite', true ); $isfeatured = getpostmeta( $post->ID, 'portfolioisfeatured', true ); ?>

Sauvegarder les meta boxes / public function savemetaboxes( $postid, $post ) { // Vérifier le post type if ( $post->posttype !== $this->posttype ) { return; } // Vérifier l'autosave if ( defined( 'DOINGAUTOSAVE' ) && DOINGAUTOSAVE ) { return; } // Vérifier les permissions if ( ! currentusercan( 'editpost', $postid ) ) { return; } // Sauvegarder les détails du projet if ( isset( $POST['portfoliodetailsnonce'] ) && wpverifynonce( $POST['portfoliodetailsnonce'], 'portfoliodetailsnonce' ) ) { if ( isset( $POST['portfolioprojecturl'] ) ) { updatepostmeta( $postid, 'portfolioprojecturl', escurlraw( $POST['portfolioprojecturl'] ) ); } if ( isset( $POST['portfolioprojectdate'] ) ) { updatepostmeta( $postid, 'portfolioprojectdate', sanitizetextfield( $POST['portfolioprojectdate'] ) ); } if ( isset( $POST['portfoliotechnologies'] ) ) { updatepostmeta( $postid, 'portfoliotechnologies', sanitizetextfield( $POST['portfoliotechnologies'] ) ); } if ( isset( $POST['portfoliogithuburl'] ) ) { updatepostmeta( $postid, 'portfoliogithuburl', escurlraw( $POST['portfoliogithuburl'] ) ); } } // Sauvegarder les informations client if ( isset( $POST['portfolioclientnonce'] ) && wpverifynonce( $POST['portfolioclientnonce'], 'portfolioclientnonce' ) ) { if ( isset( $POST['portfolioclientname'] ) ) { updatepostmeta( $postid, 'portfolioclientname', sanitizetextfield( $POST['portfolioclientname'] ) ); } if ( isset( $POST['portfolioclientwebsite'] ) ) { updatepostmeta( $postid, 'portfolioclientwebsite', escurlraw( $POST['portfolioclientwebsite'] ) ); } $isfeatured = isset( $POST['portfolioisfeatured'] ) ? '1' : '0'; updatepostmeta( $postid, 'portfolioisfeatured', $isfeatured ); } } / Colonnes personnalisées dans l'admin / public function customcolumns( $columns ) { // Retirer la colonne date unset( $columns['date'] ); // Ajouter nos colonnes personnalisées $newcolumns = array( 'thumbnail' => ( 'Image', 'mon-theme' ), 'client' => ( 'Client', 'mon-theme' ), 'technologies' => ( 'Technologies', 'mon-theme' ), 'projectdate' => ( 'Date projet', 'mon-theme' ), 'isfeatured' =>
( 'Vedette', 'mon-theme' ), ); // Fusionner avec les colonnes existantes return arraymerge( $columns, $newcolumns ); } / Contenu des colonnes personnalisées / public function customcolumncontent( $column, $postid ) { switch ( $column ) { case 'thumbnail': if ( haspostthumbnail( $postid ) ) { echo getthepostthumbnail( $postid, array( 60, 60 ) ); } else { echo ''; } break; case 'client': $clientname = getpostmeta( $postid, 'portfolioclientname', true ); echo $clientname ? eschtml( $clientname ) : '—'; break; case 'technologies': $technologies = getpostmeta( $postid, 'portfoliotechnologies', true ); if ( $technologies ) { $techarray = arraymap( 'trim', explode( ',', $technologies ) ); echo '
'; foreach ( $techarray as $tech ) { echo '' . eschtml( $tech ) . ' '; } echo '
'; } else { echo '—'; } break; case 'project
date': $projectdate = getpostmeta( $postid, 'portfolioprojectdate', true ); if ( $projectdate ) { echo eschtml( mysql2date( 'd/m/Y', $projectdate ) ); } else { echo '—'; } break; case 'isfeatured': $isfeatured = getpostmeta( $postid, 'portfolioisfeatured', true ); if ( $isfeatured === '1' ) { echo ''; } else { echo ''; } break; } } / Colonnes triables / public function sortablecolumns( $columns ) { $columns['projectdate'] = 'projectdate'; $columns['client'] = 'client'; return $columns; } } // Initialiser le CPT new Portfolio();

Requêtes avancées avec WPQuery

Récupérer des projets portfolio avec filtres


  Exemples de requêtes avancées pour CPT
 /

// Récupérer les 6 projets vedettes les plus récents
$featuredprojects = new WPQuery( array(
    'posttype'      => 'portfolio',
    'postsperpage' => 6,
    'metaquery'     => array(
        array(
            'key'     => 'portfolioisfeatured',
            'value'   => '1',
            'compare' => '=',
        ),
    ),
    'orderby'        => 'date',
    'order'          => 'DESC',
) );

if ( $featuredprojects->haveposts() ) {
    while ( $featuredprojects->haveposts() ) {
        $featuredprojects->thepost();

        // Afficher le projet
        gettemplatepart( 'template-parts/content', 'portfolio' );
    }

    wpresetpostdata();
}

// Récupérer les projets par catégorie et tag
$categoryprojects = new WPQuery( array(
    'posttype'      => 'portfolio',
    'postsperpage' => 10,
    'taxquery'      => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'portfoliocategory',
            'field'    => 'slug',
            'terms'    => 'web-design',
        ),
        array(
            'taxonomy' => 'portfoliotag',
            'field'    => 'slug',
            'terms'    => array( 'wordpress', 'responsive' ),
            'operator' => 'IN',
        ),
    ),
) );

// Récupérer les projets d'un client spécifique
$clientprojects = new WPQuery( array(
    'posttype'      => 'portfolio',
    'postsperpage' => -1,
    'metaquery'     => array(
        array(
            'key'     => 'portfolioclientname',
            'value'   => 'ACME Corporation',
            'compare' => '=',
        ),
    ),
) );

// Requête complexe : projets avec technologies et date
$complexquery = new WPQuery( array(
    'posttype'      => 'portfolio',
    'postsperpage' => 12,
    'metaquery'     => array(
        'relation' => 'AND',
        array(
            'key'     => 'portfoliotechnologies',
            'value'   => 'React',
            'compare' => 'LIKE',
        ),
        array(
            'key'     => 'portfolioprojectdate',
            'value'   => '2024-01-01',
            'compare' => '>=',
            'type'    => 'DATE',
        ),
    ),
    'taxquery'      => array(
        array(
            'taxonomy' => 'portfoliocategory',
            'field'    => 'slug',
            'terms'    => 'web-application',
        ),
    ),
    'orderby'        => array(
        'metavalue' => 'DESC',
        'date'       => 'DESC',
    ),
    'metakey'       => 'portfolioprojectdate',
) );

Optimisation des performances

Indexation de la base de données

Pour améliorer les performances des requêtes sur les meta données :


  Ajouter des index à la base de données pour les metakey fréquemment utilisés
 /

function monthemeaddportfolioindexes() {
    global $wpdb;

    // Vérifier si l'index existe déjà
    $indexexists = $wpdb->getvar(
        "SHOW INDEX FROM {$wpdb->postmeta}
        WHERE Keyname = 'metakeyportfoliofeatured'"
    );

    if ( ! $indexexists ) {
        $wpdb->query(
            "CREATE INDEX metakeyportfoliofeatured
            ON {$wpdb->postmeta} (metakey(191), metavalue(50))
            WHERE metakey = 'portfolioisfeatured'"
        );
    }

    // Index pour les dates de projet
    $dateindexexists = $wpdb->getvar(
        "SHOW INDEX FROM {$wpdb->postmeta}
        WHERE Keyname = 'metakeyportfoliodate'"
    );

    if ( ! $dateindexexists ) {
        $wpdb->query(
            "CREATE INDEX metakeyportfoliodate
            ON {$wpdb->postmeta} (metakey(191), metavalue(20))
            WHERE metakey = 'portfolioprojectdate'"
        );
    }
}
addaction( 'afterswitchtheme', 'monthemeaddportfolioindexes' );

Mise en cache des requêtes


  Système de cache pour les requêtes CPT
 /

class PortfolioCacheManager {

    private $cachegroup = 'portfolioqueries';
    private $cacheduration = 3600; // 1 heure

    /
      Récupérer les projets vedettes avec cache
     /
    public function getfeaturedprojects( $limit = 6 ) {
        $cachekey = 'featuredprojects' . $limit;

        $projects = wpcacheget( $cachekey, $this->cachegroup );

        if ( false === $projects ) {
            $query = new WPQuery( array(
                'posttype'      => 'portfolio',
                'postsperpage' => $limit,
                'metaquery'     => array(
                    array(
                        'key'     => 'portfolioisfeatured',
                        'value'   => '1',
                        'compare' => '=',
                    ),
                ),
                'nofoundrows'  => true, // Optimisation : pas de pagination
                'updatepostmetacache' => true,
                'updateposttermcache' => true,
            ) );

            $projects = $query->posts;

            wpcacheset( $cachekey, $projects, $this->cachegroup, $this->cacheduration );
        }

        return $projects;
    }

    /
      Invalider le cache lors de la sauvegarde
     /
    public function invalidatecache( $postid, $post ) {
        if ( $post->posttype !== 'portfolio' ) {
            return;
        }

        // Supprimer tous les caches du groupe
        wpcacheflushgroup( $this->cachegroup );
    }

    /
      Initialiser les hooks
     /
    public function init() {
        addaction( 'savepostportfolio', array( $this, 'invalidatecache' ), 10, 2 );
        addaction( 'deletepost', array( $this, 'invalidatecacheondelete' ) );
    }

    /
      Invalider le cache lors de la suppression
     /
    public function invalidatecacheondelete( $postid ) {
        $post = getpost( $postid );

        if ( $post && $post->posttype === 'portfolio' ) {
            wpcacheflushgroup( $this->cachegroup );
        }
    }
}

$portfoliocache = new PortfolioCacheManager();
$portfoliocache->init();

Intégration avec Gutenberg

Créer un bloc personnalisé pour afficher les projets


  Enregistrer un bloc Gutenberg pour le portfolio
 /

function monthemeregisterportfolioblock() {
    // Vérifier si Gutenberg est actif
    if ( ! functionexists( 'registerblocktype' ) ) {
        return;
    }

    wpregisterscript(
        'montheme-portfolio-block',
        gettemplatedirectoryuri() . '/assets/js/blocks/portfolio.js',
        array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-editor' ),
        filemtime( gettemplatedirectory() . '/assets/js/blocks/portfolio.js' )
    );

    wpregisterstyle(
        'montheme-portfolio-block-editor',
        gettemplatedirectoryuri() . '/assets/css/blocks/portfolio-editor.css',
        array( 'wp-edit-blocks' ),
        filemtime( gettemplatedirectory() . '/assets/css/blocks/portfolio-editor.css' )
    );

    wpregisterstyle(
        'montheme-portfolio-block',
        gettemplatedirectoryuri() . '/assets/css/blocks/portfolio.css',
        array(),
        filemtime( gettemplatedirectory() . '/assets/css/blocks/portfolio.css' )
    );

    registerblocktype( 'montheme/portfolio-grid', array(
        'editorscript'   => 'montheme-portfolio-block',
        'editorstyle'    => 'montheme-portfolio-block-editor',
        'style'           => 'montheme-portfolio-block',
        'rendercallback' => 'monthemerenderportfolioblock',
        'attributes'      => array(
            'postsToShow' => array(
                'type'    => 'number',
                'default' => 6,
            ),
            'category' => array(
                'type'    => 'string',
                'default' => '',
            ),
            'featuredOnly' => array(
                'type'    => 'boolean',
                'default' => false,
            ),
            'columns' => array(
                'type'    => 'number',
                'default' => 3,
            ),
        ),
    ) );
}
addaction( 'init', 'monthemeregisterportfolioblock' );

/
  Render du bloc portfolio
 /
function monthemerenderportfolioblock( $attributes ) {
    $poststoshow = isset( $attributes['postsToShow'] ) ? absint( $attributes['postsToShow'] ) : 6;
    $category = isset( $attributes['category'] ) ? sanitizetextfield( $attributes['category'] ) : '';
    $featuredonly = isset( $attributes['featuredOnly'] ) ? (bool) $attributes['featuredOnly'] : false;
    $columns = isset( $attributes['columns'] ) ? absint( $attributes['columns'] ) : 3;

    $args = array(
        'posttype'      => 'portfolio',
        'postsperpage' => $poststoshow,
        'nofoundrows'  => true,
    );

    if ( $category ) {
        $args['taxquery'] = array(
            array(
                'taxonomy' => 'portfoliocategory',
                'field'    => 'slug',
                'terms'    => $category,
            ),
        );
    }

    if ( $featuredonly ) {
        $args['metaquery'] = array(
            array(
                'key'     => 'portfolioisfeatured',
                'value'   => '1',
                'compare' => '=',
            ),
        );
    }

    $query = new WPQuery( $args );

    if ( ! $query->haveposts() ) {
        return '

' . eschtml( 'Aucun projet trouvé.', 'mon-theme' ) . '

'; } obstart(); ?>
haveposts() ) : $query->thepost(); ?>
resetpostdata(); return obgetclean(); }

SEO et structured data

Ajouter des données structurées Schema.org


  Ajouter des données structurées pour les projets portfolio
 /

function monthemeportfoliostructureddata() {
    if ( ! issingular( 'portfolio' ) ) {
        return;
    }

    $postid = gettheID();

    $schema = array(
        '@context'    => 'https://schema.org',
        '@type'       => 'CreativeWork',
        'name'        => getthetitle(),
        'description' => gettheexcerpt(),
        'url'         => getpermalink(),
        'dateCreated' => getthedate( 'c' ),
        'dateModified' => getthemodifieddate( 'c' ),
        'author'      => array(
            '@type' => 'Person',
            'name'  => gettheauthor(),
        ),
    );

    // Ajouter l'image
    if ( haspostthumbnail() ) {
        $imageid = getpostthumbnailid();
        $imagedata = wpgetattachmentimagesrc( $imageid, 'full' );

        if ( $imagedata ) {
            $schema['image'] = array(
                '@type'  => 'ImageObject',
                'url'    => $imagedata[0],
                'width'  => $imagedata[1],
                'height' => $imagedata[2],
            );
        }
    }

    // Ajouter les métadonnées personnalisées
    $projecturl = getpostmeta( $postid, 'portfolioprojecturl', true );
    if ( $projecturl ) {
        $schema['workExample'] = $projecturl;
    }

    $projectdate = getpostmeta( $postid, 'portfolioprojectdate', true );
    if ( $projectdate ) {
        $schema['datePublished'] = mysql2date( 'c', $projectdate );
    }

    $technologies = getpostmeta( $postid, 'portfoliotechnologies', true );
    if ( $technologies ) {
        $techarray = arraymap( 'trim', explode( ',', $technologies ) );
        $schema['keywords'] = implode( ', ', $techarray );
    }

    $clientname = getpostmeta( $postid, 'portfolioclientname', true );
    if ( $clientname ) {
        $schema['client'] = array(
            '@type' => 'Organization',
            'name'  => $clientname,
        );

        $clientwebsite = getpostmeta( $postid, 'portfolioclientwebsite', true );
        if ( $clientwebsite ) {
            $schema['client']['url'] = $clientwebsite;
        }
    }

    // Sortir le JSON-LD
    echo '' . "n";
}
addaction( 'wphead', 'monthemeportfoliostructureddata' );

REST API pour CPT

Ajouter des champs personnalisés à l’API REST


  Étendre l'API REST pour les projets portfolio
 /

function monthemeregisterportfoliorestfields() {
    // Enregistrer le champ "featured"
    registerrestfield( 'portfolio', 'isfeatured', array(
        'getcallback'    => function( $post ) {
            return getpostmeta( $post['id'], 'portfolioisfeatured', true ) === '1';
        },
        'updatecallback' => function( $value, $post ) {
            return updatepostmeta( $post->ID, 'portfolioisfeatured', $value ? '1' : '0' );
        },
        'schema'          => array(
            'description' => ( 'Si le projet est vedette', 'mon-theme' ),
            'type'        => 'boolean',
        ),
    ) );

    // Enregistrer tous les champs méta en une seule fois
    registerrestfield( 'portfolio', 'projectmeta', array(
        'getcallback' => function( $post ) {
            return array(
                'projecturl'    => getpostmeta( $post['id'], 'portfolioprojecturl', true ),
                'projectdate'   => getpostmeta( $post['id'], 'portfolioprojectdate', true ),
                'technologies'   => getpostmeta( $post['id'], 'portfoliotechnologies', true ),
                'githuburl'     => getpostmeta( $post['id'], 'portfoliogithuburl', true ),
                'clientname'    => getpostmeta( $post['id'], 'portfolioclientname', true ),
                'clientwebsite' => getpostmeta( $post['id'], 'portfolioclientwebsite', true ),
            );
        },
        'schema' => array(
            'description' => ( 'Métadonnées du projet', 'mon-theme' ),
            'type'        => 'object',
            'properties'  => array(
                'projecturl'    => array( 'type' => 'string', 'format' => 'uri' ),
                'projectdate'   => array( 'type' => 'string', 'format' => 'date' ),
                'technologies'   => array( 'type' => 'string' ),
                'githuburl'     => array( 'type' => 'string', 'format' => 'uri' ),
                'clientname'    => array( 'type' => 'string' ),
                'clientwebsite' => array( 'type' => 'string', 'format' => 'uri' ),
            ),
        ),
    ) );
}
addaction( 'restapiinit', 'monthemeregisterportfoliorestfields' );

Erreurs courantes à éviter

1. Oublier de flush les rewrite rules

PROBLÈME : URLs 404 après création du CPT

SOLUTION :

// Lors de l'activation du thème ou plugin
function monthemeflushrewriterules() {
    // Enregistrer le CPT
    monthemeregisterportfoliocpt();

    // Flush
    flushrewriterules();
}
registeractivationhook( FILE_, 'monthemeflushrewriterules' );

2. Utiliser un slug trop générique

MAUVAIS :

'rewrite' => array( 'slug' => 'item' )

BON :

'rewrite' => array( 'slug' => 'portfolio-projet' )

3. Ne pas penser au SEO

MAUVAIS : Pas de hasarchive, pas de métadonnées

BON : Archive activée, structured data, sitemap XML

Conclusion

La maîtrise des Custom Post Types et des taxonomies est essentielle pour créer des sites WordPress structurés et performants en 2025. En combinant une architecture solide, des optimisations de performance et une intégration SEO appropriée, vous créerez des solutions évolutives et maintenables.

Points clés :

  • Planifiez votre architecture de contenu avant de coder
  • Utilisez des slugs descriptifs et uniques
  • Implémentez la mise en cache pour les performances
  • Ajoutez des données structurées pour le SEO
  • Exposez vos CPT via l’API REST pour les applications découplées
  • Flush les rewrite rules uniquement à l’activation
  • Ressources supplémentaires

  • Post Types Handbook
  • Taxonomies Handbook
  • Share this article

  • Une remarque, un retour ?

    Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.