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.