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.
Table of Contents
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
- Site immobilier :
property(propriété) - Site événementiel :
event(événement) - Site portfolio :
project(projet) - Site e-learning :
course(cours)
Exemples concrets :
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 );
?>
Render meta box : Informations client
/
public function renderclientmetabox( $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 'projectdate':
$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(); ?>
postthumbnail() ) : ?>
title(); ?>
postmeta( gettheID(), 'portfoliotechnologies', true );
if ( $technologies ) :
$techarray = arraymap( 'trim', explode( ',', $technologies ) );
?>
array as $tech ) : ?>
html( $tech ); ?>
excerpt() ) : ?>
excerpt(); ?>
htmle( 'Voir le projet', 'mon-theme' ); ?>
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 :