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
- 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 » :
post_type . '*posts_columns', array( $this, 'custom_columns' ) );
add_action( 'manage*' . $this->post_type . '*posts_custom_column', array( $this, 'custom_column_content' ), 10, 2 );
add_filter( 'manage_edit-' . $this->post_type . '*sortable_columns', array( $this, 'sortable_columns' ) );
}
/**
* Enregistrer le Custom Post Type
*/
public function register_post_type() {
$labels = array(
'name' => *x( 'Projets Portfolio', 'Post type general name', 'mon-theme' ),
'singular_name' => *x( 'Projet', 'Post type singular name', 'mon-theme' ),
'menu_name' => *x( 'Portfolio', 'Admin Menu text', 'mon-theme' ),
'name_admin_bar' => *x( 'Projet', 'Add New on Toolbar', 'mon-theme' ),
'add_new' => **( 'Ajouter un projet', 'mon-theme' ),
'add_new_item' => **( 'Ajouter un nouveau projet', 'mon-theme' ),
'new_item' => **( 'Nouveau projet', 'mon-theme' ),
'edit_item' => **( 'Modifier le projet', 'mon-theme' ),
'view_item' => **( 'Voir le projet', 'mon-theme' ),
'all_items' => **( 'Tous les projets', 'mon-theme' ),
'search_items' => **( 'Rechercher des projets', 'mon-theme' ),
'parent_item_colon' => **( 'Projet parent:', 'mon-theme' ),
'not_found' => **( 'Aucun projet trouvé.', 'mon-theme' ),
'not_found_in_trash' => **( 'Aucun projet trouvé dans la corbeille.', 'mon-theme' ),
'featured_image' => *x( 'Image du projet', 'Overrides the "Featured Image" phrase', 'mon-theme' ),
'set_featured_image' => *x( 'Définir l'image du projet', 'Overrides the "Set featured image" phrase', 'mon-theme' ),
'remove_featured_image' => *x( 'Retirer l'image du projet', 'Overrides the "Remove featured image" phrase', 'mon-theme' ),
'use_featured_image' => *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' ),
'insert_into_item' => *x( 'Insérer dans le projet', 'Overrides the "Insert into post" phrase', 'mon-theme' ),
'uploaded_to_this_item' => *x( 'Téléversé vers ce projet', 'Overrides the "Uploaded to this post" phrase', 'mon-theme' ),
'filter_items_list' => *x( 'Filtrer la liste des projets', 'Screen reader text for the filter links', 'mon-theme' ),
'items_list_navigation' => *x( 'Navigation de la liste des projets', 'Screen reader text for the pagination', 'mon-theme' ),
'items_list' => *x( 'Liste des projets', 'Screen reader text for the items list', 'mon-theme' ),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array(
'slug' => 'portfolio',
'with_front' => false,
'feeds' => true,
'pages' => true,
),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
'menu_icon' => 'dashicons-portfolio',
'supports' => array(
'title',
'editor',
'thumbnail',
'excerpt',
'custom-fields',
'revisions',
'author',
'page-attributes',
),
'show_in_rest' => true,
'rest_base' => 'portfolio',
'rest_controller_class' => 'WP_REST_Posts_Controller',
'template' => array(
array( 'core/image', array() ),
array( 'core/paragraph', array(
'placeholder' => **( 'Décrivez votre projet...', 'mon-theme' ),
) ),
),
'template_lock' => false,
);
register_post_type( $this->post_type, $args );
}
/**
* Enregistrer les taxonomies
*/
public function register_taxonomies() {
// Taxonomie : Catégories de portfolio (hiérarchique)
$category_labels = array(
'name' => *x( 'Catégories', 'taxonomy general name', 'mon-theme' ),
'singular_name' => *x( 'Catégorie', 'taxonomy singular name', 'mon-theme' ),
'search_items' => **( 'Rechercher des catégories', 'mon-theme' ),
'all_items' => **( 'Toutes les catégories', 'mon-theme' ),
'parent_item' => **( 'Catégorie parente', 'mon-theme' ),
'parent_item_colon' => **( 'Catégorie parente:', 'mon-theme' ),
'edit_item' => **( 'Modifier la catégorie', 'mon-theme' ),
'update_item' => **( 'Mettre à jour la catégorie', 'mon-theme' ),
'add_new_item' => **( 'Ajouter une nouvelle catégorie', 'mon-theme' ),
'new_item_name' => **( 'Nom de la nouvelle catégorie', 'mon-theme' ),
'menu_name' => **( 'Catégories', 'mon-theme' ),
);
$category_args = array(
'hierarchical' => true,
'labels' => $category_labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array(
'slug' => 'categorie-portfolio',
'with_front' => false,
'hierarchical' => true,
),
'show_in_rest' => true,
'rest_base' => 'portfolio-categories',
);
register_taxonomy( 'portfolio_category', array( $this->post_type ), $category_args );
// Taxonomie : Tags de portfolio (non hiérarchique)
$tag_labels = array(
'name' => *x( 'Tags', 'taxonomy general name', 'mon-theme' ),
'singular_name' => *x( 'Tag', 'taxonomy singular name', 'mon-theme' ),
'search_items' => **( 'Rechercher des tags', 'mon-theme' ),
'popular_items' => **( 'Tags populaires', 'mon-theme' ),
'all_items' => **( 'Tous les tags', 'mon-theme' ),
'edit_item' => **( 'Modifier le tag', 'mon-theme' ),
'update_item' => **( 'Mettre à jour le tag', 'mon-theme' ),
'add_new_item' => **( 'Ajouter un nouveau tag', 'mon-theme' ),
'new_item_name' => **( 'Nom du nouveau tag', 'mon-theme' ),
'separate_items_with_commas' => **( 'Séparer les tags par des virgules', 'mon-theme' ),
'add_or_remove_items' => **( 'Ajouter ou retirer des tags', 'mon-theme' ),
'choose_from_most_used' => **( 'Choisir parmi les tags les plus utilisés', 'mon-theme' ),
'menu_name' => **( 'Tags', 'mon-theme' ),
);
$tag_args = array(
'hierarchical' => false,
'labels' => $tag_labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array(
'slug' => 'tag-portfolio',
),
'show_in_rest' => true,
'rest_base' => 'portfolio-tags',
);
register_taxonomy( 'portfolio_tag', array( $this->post_type ), $tag_args );
}
/**
* Changer le placeholder du titre
*/
public function change_title_placeholder( $title, $post ) {
if ( $post->post_type === $this->post_type ) {
$title = **( 'Entrez le nom du projet', 'mon-theme' );
}
return $title;
}
/**
* Ajouter des meta boxes
*/
public function add_meta_boxes() {
add_meta_box(
'portfolio_details',
**( 'Détails du projet', 'mon-theme' ),
array( $this, 'render_details_meta_box' ),
$this->post_type,
'normal',
'high'
);
add_meta_box(
'portfolio_client',
**( 'Informations client', 'mon-theme' ),
array( $this, 'render_client_meta_box' ),
$this->post_type,
'side',
'default'
);
}
/**
* Render meta box : Détails du projet
*/
public function render_details_meta_box( $post ) {
// Nonce pour la sécurité
wp_nonce_field( 'portfolio_details_nonce', 'portfolio_details_nonce' );
// Récupérer les valeurs existantes
$project_url = get_post_meta( $post->ID, '*portfolio_project_url', true );
$project_date = get_post_meta( $post->ID, '*portfolio_project_date', true );
$technologies = get_post_meta( $post->ID, '*portfolio_technologies', true );
$github_url = get_post_meta( $post->ID, '*portfolio_github_url', true );
?>
ID, '*portfolio_client_name', true );
$client_website = get_post_meta( $post->ID, '*portfolio_client_website', true );
$is_featured = get_post_meta( $post->ID, '*portfolio_is_featured', true );
?>
post_type !== $this->post_type ) {
return;
}
// Vérifier l'autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Vérifier les permissions
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Sauvegarder les détails du projet
if ( isset( $*POST['portfolio_details_nonce'] ) &&
wp_verify_nonce( $*POST['portfolio_details_nonce'], 'portfolio_details_nonce' ) ) {
if ( isset( $*POST['portfolio_project_url'] ) ) {
update_post_meta( $post_id, '*portfolio_project_url', esc_url_raw( $*POST['portfolio_project_url'] ) );
}
if ( isset( $*POST['portfolio_project_date'] ) ) {
update_post_meta( $post_id, '*portfolio_project_date', sanitize_text_field( $*POST['portfolio_project_date'] ) );
}
if ( isset( $*POST['portfolio_technologies'] ) ) {
update_post_meta( $post_id, '*portfolio_technologies', sanitize_text_field( $*POST['portfolio_technologies'] ) );
}
if ( isset( $*POST['portfolio_github_url'] ) ) {
update_post_meta( $post_id, '*portfolio_github_url', esc_url_raw( $*POST['portfolio_github_url'] ) );
}
}
// Sauvegarder les informations client
if ( isset( $*POST['portfolio_client_nonce'] ) &&
wp_verify_nonce( $*POST['portfolio_client_nonce'], 'portfolio_client_nonce' ) ) {
if ( isset( $*POST['portfolio_client_name'] ) ) {
update_post_meta( $post_id, '*portfolio_client_name', sanitize_text_field( $*POST['portfolio_client_name'] ) );
}
if ( isset( $*POST['portfolio_client_website'] ) ) {
update_post_meta( $post_id, '*portfolio_client_website', esc_url_raw( $*POST['portfolio_client_website'] ) );
}
$is_featured = isset( $*POST['portfolio_is_featured'] ) ? '1' : '0';
update_post_meta( $post_id, '*portfolio_is_featured', $is_featured );
}
}
/**
* Colonnes personnalisées dans l'admin
*/
public function custom_columns( $columns ) {
// Retirer la colonne date
unset( $columns['date'] );
// Ajouter nos colonnes personnalisées
$new_columns = array(
'thumbnail' => **( 'Image', 'mon-theme' ),
'client' => **( 'Client', 'mon-theme' ),
'technologies' => **( 'Technologies', 'mon-theme' ),
'project_date' => **( 'Date projet', 'mon-theme' ),
'is_featured' => **( 'Vedette', 'mon-theme' ),
);
// Fusionner avec les colonnes existantes
return array_merge( $columns, $new_columns );
}
/**
* Contenu des colonnes personnalisées
*/
public function custom_column_content( $column, $post_id ) {
switch ( $column ) {
case 'thumbnail':
if ( has_post_thumbnail( $post_id ) ) {
echo get_the_post_thumbnail( $post_id, array( 60, 60 ) );
} else {
echo '';
}
break;
case 'client':
$client_name = get_post_meta( $post_id, '*portfolio_client_name', true );
echo $client_name ? esc_html( $client_name ) : '-';
break;
case 'technologies':
$technologies = get_post_meta( $post_id, '*portfolio_technologies', true );
if ( $technologies ) {
$tech_array = array_map( 'trim', explode( ',', $technologies ) );
echo '';
foreach ( $tech_array as $tech ) {
echo '' . esc_html( $tech ) . ' ';
}
echo '';
} else {
echo '-';
}
break;
case 'project_date':
$project_date = get_post_meta( $post_id, '*portfolio_project_date', true );
if ( $project_date ) {
echo esc_html( mysql2date( 'd/m/Y', $project_date ) );
} else {
echo '-';
}
break;
case 'is_featured':
$is_featured = get_post_meta( $post_id, '*portfolio_is_featured', true );
if ( $is_featured === '1' ) {
echo '';
} else {
echo '';
}
break;
}
}
/**
* Colonnes triables
*/
public function sortable_columns( $columns ) {
$columns['project_date'] = 'project_date';
$columns['client'] = 'client';
return $columns;
}
}
// Initialiser le CPT
new Portfolio();
Requêtes avancées avec WP_Query
Récupérer des projets portfolio avec filtres
'portfolio',
'posts_per_page' => 6,
'meta_query' => array(
array(
'key' => '*portfolio_is_featured',
'value' => '1',
'compare' => '=',
),
),
'orderby' => 'date',
'order' => 'DESC',
) );
if ( $featured_projects->have_posts() ) {
while ( $featured_projects->have_posts() ) {
$featured_projects->the_post();
// Afficher le projet
get_template_part( 'template-parts/content', 'portfolio' );
}
wp_reset_postdata();
}
// Récupérer les projets par catégorie et tag
$category_projects = new WP_Query( array(
'post_type' => 'portfolio',
'posts_per_page' => 10,
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'portfolio_category',
'field' => 'slug',
'terms' => 'web-design',
),
array(
'taxonomy' => 'portfolio_tag',
'field' => 'slug',
'terms' => array( 'wordpress', 'responsive' ),
'operator' => 'IN',
),
),
) );
// Récupérer les projets d'un client spécifique
$client_projects = new WP_Query( array(
'post_type' => 'portfolio',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => '*portfolio_client_name',
'value' => 'ACME Corporation',
'compare' => '=',
),
),
) );
// Requête complexe : projets avec technologies et date
$complex_query = new WP_Query( array(
'post_type' => 'portfolio',
'posts_per_page' => 12,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '*portfolio_technologies',
'value' => 'React',
'compare' => 'LIKE',
),
array(
'key' => '*portfolio_project_date',
'value' => '2024-01-01',
'compare' => '>=',
'type' => 'DATE',
),
),
'tax_query' => array(
array(
'taxonomy' => 'portfolio_category',
'field' => 'slug',
'terms' => 'web-application',
),
),
'orderby' => array(
'meta_value' => 'DESC',
'date' => 'DESC',
),
'meta_key' => '*portfolio_project_date',
) );
Optimisation des performances
Indexation de la base de données
Pour améliorer les performances des requêtes sur les meta données :
get_var(
"SHOW INDEX FROM {$wpdb->postmeta}
WHERE Key_name = 'meta_key_portfolio_featured'"
);
if ( ! $index_exists ) {
$wpdb->query(
"CREATE INDEX meta_key_portfolio_featured
ON {$wpdb->postmeta} (meta_key(191), meta_value(50))
WHERE meta_key = '*portfolio_is_featured'"
);
}
// Index pour les dates de projet
$date_index_exists = $wpdb->get_var(
"SHOW INDEX FROM {$wpdb->postmeta}
WHERE Key_name = 'meta_key_portfolio_date'"
);
if ( ! $date_index_exists ) {
$wpdb->query(
"CREATE INDEX meta_key_portfolio_date
ON {$wpdb->postmeta} (meta_key(191), meta_value(20))
WHERE meta_key = '*portfolio_project_date'"
);
}
}
add_action( 'after_switch_theme', 'montheme_add_portfolio_indexes' );
Mise en cache des requêtes
cache_group );
if ( false === $projects ) {
$query = new WP_Query( array(
'post_type' => 'portfolio',
'posts_per_page' => $limit,
'meta_query' => array(
array(
'key' => '*portfolio_is_featured',
'value' => '1',
'compare' => '=',
),
),
'no_found_rows' => true, // Optimisation : pas de pagination
'update_post_meta_cache' => true,
'update_post_term_cache' => true,
) );
$projects = $query->posts;
wp_cache_set( $cache_key, $projects, $this->cache_group, $this->cache_duration );
}
return $projects;
}
/**
* Invalider le cache lors de la sauvegarde
*/
public function invalidate_cache( $post_id, $post ) {
if ( $post->post_type !== 'portfolio' ) {
return;
}
// Supprimer tous les caches du groupe
wp_cache_flush_group( $this->cache_group );
}
/**
* Initialiser les hooks
*/
public function init() {
add_action( 'save_post_portfolio', array( $this, 'invalidate_cache' ), 10, 2 );
add_action( 'delete_post', array( $this, 'invalidate_cache_on_delete' ) );
}
/**
* Invalider le cache lors de la suppression
*/
public function invalidate_cache_on_delete( $post_id ) {
$post = get_post( $post_id );
if ( $post && $post->post_type === 'portfolio' ) {
wp_cache_flush_group( $this->cache_group );
}
}
}
$portfolio_cache = new PortfolioCacheManager();
$portfolio_cache->init();
Intégration avec Gutenberg
Créer un bloc personnalisé pour afficher les projets
'montheme-portfolio-block',
'editor_style' => 'montheme-portfolio-block-editor',
'style' => 'montheme-portfolio-block',
'render_callback' => 'montheme_render_portfolio_block',
'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,
),
),
) );
}
add_action( 'init', 'montheme_register_portfolio_block' );
/**
* Render du bloc portfolio
*/
function montheme_render_portfolio_block( $attributes ) {
$posts_to_show = isset( $attributes['postsToShow'] ) ? absint( $attributes['postsToShow'] ) : 6;
$category = isset( $attributes['category'] ) ? sanitize_text_field( $attributes['category'] ) : '';
$featured_only = isset( $attributes['featuredOnly'] ) ? (bool) $attributes['featuredOnly'] : false;
$columns = isset( $attributes['columns'] ) ? absint( $attributes['columns'] ) : 3;
$args = array(
'post_type' => 'portfolio',
'posts_per_page' => $posts_to_show,
'no_found_rows' => true,
);
if ( $category ) {
$args['tax_query'] = array(
array(
'taxonomy' => 'portfolio_category',
'field' => 'slug',
'terms' => $category,
),
);
}
if ( $featured_only ) {
$args['meta_query'] = array(
array(
'key' => '*portfolio_is_featured',
'value' => '1',
'compare' => '=',
),
);
}
$query = new WP_Query( $args );
if ( ! $query->have_posts() ) {
return '' . esc_html**( 'Aucun projet trouvé.', 'mon-theme' ) . '
';
}
ob_start();
?>
SEO et structured data
Ajouter des données structurées Schema.org
'https://schema.org',
'@type' => 'CreativeWork',
'name' => get_the_title(),
'description' => get_the_excerpt(),
'url' => get_permalink(),
'dateCreated' => get_the_date( 'c' ),
'dateModified' => get_the_modified_date( 'c' ),
'author' => array(
'@type' => 'Person',
'name' => get_the_author(),
),
);
// Ajouter l'image
if ( has_post_thumbnail() ) {
$image_id = get_post_thumbnail_id();
$image_data = wp_get_attachment_image_src( $image_id, 'full' );
if ( $image_data ) {
$schema['image'] = array(
'@type' => 'ImageObject',
'url' => $image_data[0],
'width' => $image_data[1],
'height' => $image_data[2],
);
}
}
// Ajouter les métadonnées personnalisées
$project_url = get_post_meta( $post_id, '*portfolio_project_url', true );
if ( $project_url ) {
$schema['workExample'] = $project_url;
}
$project_date = get_post_meta( $post_id, '*portfolio_project_date', true );
if ( $project_date ) {
$schema['datePublished'] = mysql2date( 'c', $project_date );
}
$technologies = get_post_meta( $post_id, '*portfolio_technologies', true );
if ( $technologies ) {
$tech_array = array_map( 'trim', explode( ',', $technologies ) );
$schema['keywords'] = implode( ', ', $tech_array );
}
$client_name = get_post_meta( $post_id, '*portfolio_client_name', true );
if ( $client_name ) {
$schema['client'] = array(
'@type' => 'Organization',
'name' => $client_name,
);
$client_website = get_post_meta( $post_id, '*portfolio_client_website', true );
if ( $client_website ) {
$schema['client']['url'] = $client_website;
}
}
// Sortir le JSON-LD
echo '' . "n";
}
add_action( 'wp_head', 'montheme_portfolio_structured_data' );
REST API pour CPT
Ajouter des champs personnalisés à l'API REST
function( $post ) {
return get_post_meta( $post['id'], '*portfolio_is_featured', true ) === '1';
},
'update_callback' => function( $value, $post ) {
return update_post_meta( $post->ID, '*portfolio_is_featured', $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
register_rest_field( 'portfolio', 'project_meta', array(
'get_callback' => function( $post ) {
return array(
'project_url' => get_post_meta( $post['id'], '*portfolio_project_url', true ),
'project_date' => get_post_meta( $post['id'], '*portfolio_project_date', true ),
'technologies' => get_post_meta( $post['id'], '*portfolio_technologies', true ),
'github_url' => get_post_meta( $post['id'], '*portfolio_github_url', true ),
'client_name' => get_post_meta( $post['id'], '*portfolio_client_name', true ),
'client_website' => get_post_meta( $post['id'], '*portfolio_client_website', true ),
);
},
'schema' => array(
'description' => **( 'Métadonnées du projet', 'mon-theme' ),
'type' => 'object',
'properties' => array(
'project_url' => array( 'type' => 'string', 'format' => 'uri' ),
'project_date' => array( 'type' => 'string', 'format' => 'date' ),
'technologies' => array( 'type' => 'string' ),
'github_url' => array( 'type' => 'string', 'format' => 'uri' ),
'client_name' => array( 'type' => 'string' ),
'client_website' => array( 'type' => 'string', 'format' => 'uri' ),
),
),
) );
}
add_action( 'rest_api_init', 'montheme_register_portfolio_rest_fields' );
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 montheme_flush_rewrite_rules() {
// Enregistrer le CPT
montheme_register_portfolio_cpt();
// Flush
flush_rewrite_rules();
}
register_activation_hook( **FILE__, 'montheme_flush_rewrite_rules' );
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 has_archive, 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 :
Ressources supplémentaires
Mots-clés: Custom Post Types WordPress, CPT WordPress, taxonomies WordPress, WP_Query avancé, post types personnalisés, WordPress metadata, structured data WordPress, WordPress SEO, portfolio WordPress, CPT performance