Create Custom Taxonomies and Link Them to CPTs
Create Custom Taxonomies and Link Them to CPTs in WordPress
Custom taxonomies let you group your Custom Post Types (CPTs) with meaningful categories or tags—like Genre for a “Books” CPT, or Skill for a “Portfolio” CPT. Below you’ll learn how to register hierarchical (category-like) and non-hierarchical (tag-like) taxonomies, attach them to CPTs, display them, and query by them.
1) Register a CPT (Example)
We’ll use a simple book CPT to demonstrate. Add to functions.php or a site plugin:
<?php
// 1) Register the "book" CPT
add_action( 'init', function() {
register_post_type( 'book', array(
'labels' => array(
'name' => 'Books',
'singular_name' => 'Book',
),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-book',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'show_in_rest' => true, // Gutenberg + REST
'rewrite' => array( 'slug' => 'books' ),
) );
} );
2) Register Custom Taxonomies
A) Hierarchical taxonomy (category-like): genre
<?php
// 2A) Register "genre" taxonomy and link to "book"
add_action( 'init', function() {
$labels = array(
'name' => 'Genres',
'singular_name' => 'Genre',
'search_items' => 'Search Genres',
'all_items' => 'All Genres',
'parent_item' => 'Parent Genre',
'parent_item_colon' => 'Parent Genre:',
'edit_item' => 'Edit Genre',
'update_item' => 'Update Genre',
'add_new_item' => 'Add New Genre',
'new_item_name' => 'New Genre Name',
'menu_name' => 'Genres',
);
register_taxonomy( 'genre', array( 'book' ), array(
'labels' => $labels,
'hierarchical' => true, // category-like
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true, // block editor + REST
'rewrite' => array( 'slug' => 'genre' ),
) );
} );
B) Non-hierarchical taxonomy (tag-like): writer
<?php
// 2B) Register "writer" taxonomy and link to "book"
add_action( 'init', function() {
$labels = array(
'name' => 'Writers',
'singular_name' => 'Writer',
'search_items' => 'Search Writers',
'popular_items' => 'Popular Writers',
'edit_item' => 'Edit Writer',
'update_item' => 'Update Writer',
'add_new_item' => 'Add New Writer',
'separate_items_with_commas' => 'Separate writers with commas',
'add_or_remove_items' => 'Add or remove writers',
'choose_from_most_used' => 'Choose from the most used',
'menu_name' => 'Writers',
);
register_taxonomy( 'writer', array( 'book' ), array(
'labels' => $labels,
'hierarchical' => false, // tag-like
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'writer' ),
) );
} );
Note: You can attach the same taxonomy to multiple CPTs by listing more post types in the array (e.g., array( 'book','movie' )).
3) Linking a Taxonomy to an Existing CPT
If a taxonomy is registered separately, ensure it’s attached to a post type:
<?php
add_action( 'init', function() {
register_taxonomy_for_object_type( 'genre', 'book' );
register_taxonomy_for_object_type( 'writer', 'book' );
} );
4) Flush Permalinks After Adding Taxonomies
- Go to Settings → Permalinks.
- Click Save Changes (no edits needed).
- This refreshes rewrite rules so archive and term URLs work.
5) Display Terms on Single CPT Pages
In your single template (e.g., single-book.php), output assigned terms:
<?php
// Show genres
$terms = get_the_terms( get_the_ID(), 'genre' );
if ( $terms && ! is_wp_error( $terms ) ) {
echo '<p class="book-genres"><strong>Genres:</strong> ';
$links = array();
foreach ( $terms as $t ) {
$links[] = '<a href="' . esc_url( get_term_link( $t ) ) . '">' . esc_html( $t->name ) . '</a>';
}
echo implode( ', ', $links ) . '</p>';
}
// Show writers
$writers = get_the_terms( get_the_ID(), 'writer' );
if ( $writers && ! is_wp_error( $writers ) ) {
echo '<p class="book-writers"><strong>Writers:</strong> ';
echo esc_html( join( ', ', wp_list_pluck( $writers, 'name' ) ) );
echo '</p>';
}
?>
6) Query CPTs by Taxonomy
A) Direct query with WP_Query
<?php
$q = new WP_Query( array(
'post_type' => 'book',
'posts_per_page' => 6,
'tax_query' => array(
array(
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => array( 'fantasy', 'sci-fi' ),
),
),
) );
if ( $q->have_posts() ) :
echo '<ul class="books">';
while ( $q->have_posts() ) : $q->the_post();
echo '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
endwhile;
echo '</ul>';
wp_reset_postdata();
endif;
?>
B) Modify main query via pre_get_posts (e.g., genre archives show books)
<?php
add_action( 'pre_get_posts', function( $query ) {
if ( is_admin() || ! $query->is_main_query() ) return;
// On genre taxonomy archives, show only "book" CPT
if ( $query->is_tax( 'genre' ) ) {
$query->set( 'post_type', array( 'book' ) );
}
} );
7) Template Files for Taxonomy Archives
Create templates to control taxonomy archive layouts:
taxonomy-genre.php– for all genre term archivestaxonomy-writer.php– for all writer term archivestaxonomy-genre-fantasy.php– specific to the fantasy term
<?php // taxonomy-genre.php (example)
get_header(); ?>
<main id="primary">
<h1><?php single_term_title(); ?></h1>
<?php if ( have_posts() ) : ?>
<ul class="book-archive">
<?php while ( have_posts() ) : the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endwhile; ?>
</ul>
<?php the_posts_pagination(); ?>
<?php else : ?>
<p>No books found in this genre.</p>
<?php endif; ?>
</main>
<?php get_footer(); ?>
8) Add Admin Columns for Taxonomies (Nice UX)
<?php
// Show Genre column in "Books" list table
add_filter( 'manage_book_posts_columns', function( $cols ) {
$cols['genre'] = 'Genres';
return $cols;
} );
add_action( 'manage_book_posts_custom_column', function( $col, $post_id ) {
if ( $col === 'genre' ) {
$terms = get_the_terms( $post_id, 'genre' );
if ( $terms && ! is_wp_error( $terms ) ) {
echo esc_html( join( ', ', wp_list_pluck( $terms, 'name' ) ) );
} else {
echo '—';
}
}
}, 10, 2 );
9) Security & REST Tips
- show_in_rest: Set to
truefor block editor support and REST endpoints (e.g.,/wp-json/wp/v2/genre). - Capabilities: For advanced permissions, define
capabilitiesinregister_taxonomy()and manage roles accordingly. - Validation: When saving terms programmatically, sanitize inputs and verify nonces.
10) Summary
- Register your CPT (e.g.,
book). - Create taxonomies with
register_taxonomy()(hierarchical or tag-like). - Attach taxonomies to CPTs (in the
register_taxonomy()call or withregister_taxonomy_for_object_type()). - Flush permalinks once.
- Display terms on single templates; query CPTs with
tax_query. - Customize taxonomy archive templates and improve admin UX with columns.
With custom taxonomies linked to your CPTs, you’ll have clean, structured content that’s easier to manage, navigate, and scale.
🎨 Want to learn more? Visit our WordPress Customization Hub for tips and advanced techniques.