Taxonomies
Taxonomies organise posts. WordPress ships category and tag for built-in posts; register_taxonomy lets you create your own (Industries, Genres, Locations) for custom post types — with hierarchy, REST exposure, URL slugs, and editing UI.
register, hierarchical, REST, UI
EXAMPLE
<?php
// 1) Register a custom taxonomy
add_action( 'init', function () {
register_taxonomy(
'industry', // slug; URL: /industry/finance
array( 'post', 'business' ), // post types it applies to
array(
'label' => 'Industries',
'labels' => array(
'name' => 'Industries',
'singular_name' => 'Industry',
'menu_name' => 'Industries',
'all_items' => 'All Industries',
'parent_item' => 'Parent Industry',
'parent_item_colon' => 'Parent Industry:',
'new_item_name' => 'New Industry',
'add_new_item' => 'Add Industry',
'edit_item' => 'Edit Industry',
'update_item' => 'Update Industry',
'view_item' => 'View Industry',
'search_items' => 'Search Industries',
'add_or_remove_items' => 'Add or remove industries',
'choose_from_most_used' => 'Choose from the most used',
),
'hierarchical' => true, // like categories (tree)
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_admin_column' => true, // column in list table
'show_in_nav_menus' => true,
'show_tagcloud' => true,
'show_in_rest' => true, // exposes via /wp-json/wp/v2/industry
'rest_base' => 'industries',
'rewrite' => array( 'slug' => 'industry', 'with_front' => false ),
'capabilities' => array(
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
),
)
);
} );
// 2) Flat (tag-like) taxonomy
register_taxonomy( 'mood', 'product', array(
'label' => 'Moods',
'hierarchical' => false, // tag-style
'public' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'mood' ),
) );
// 3) Attach an existing taxonomy to another post type
register_taxonomy_for_object_type( 'industry', 'product' );
// 4) Create / update terms
$term = wp_insert_term( 'Finance', 'industry', array(
'slug' => 'finance',
'description' => 'Finance-related content',
'parent' => 0,
) );
if ( is_wp_error( $term ) ) error_log( $term->get_error_message() );
// Children
wp_insert_term( 'Banking', 'industry', array( 'parent' => $term['term_id'] ) );
wp_insert_term( 'Insurance', 'industry', array( 'parent' => $term['term_id'] ) );
// 5) Assign terms to a post
wp_set_object_terms( $post_id, array( 'finance', 'banking' ), 'industry' );
wp_set_object_terms( $post_id, array( 'finance' ), 'industry', true ); // append, not replace
// 6) Query posts by term
$query = new WP_Query( array(
'post_type' => 'post',
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'industry',
'field' => 'slug',
'terms' => array( 'finance', 'banking' ),
'operator' => 'IN',
),
array(
'taxonomy' => 'mood',
'field' => 'slug',
'terms' => array( 'serious' ),
'operator' => 'NOT IN',
),
),
) );
// 7) Render terms in templates
<?php
$terms = get_the_terms( get_the_ID(), 'industry' );
if ( $terms && ! is_wp_error( $terms ) ) :
?>
<ul class="industries">
<?php foreach ( $terms as $term ) : ?>
<li><a href="<?php echo esc_url( get_term_link( $term ) ); ?>"><?php echo esc_html( $term->name ); ?></a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
// 8) Term metadata
add_term_meta( $term_id, 'featured_image_id', $attachment_id, true );
$image_id = get_term_meta( $term_id, 'featured_image_id', true );
// 9) UI hooks — fields on the add/edit term screen
add_action( 'industry_add_form_fields', function () {
?>
<div class="form-field term-featured-image-wrap">
<label for="featured_image_id">Featured image ID</label>
<input type="text" id="featured_image_id" name="featured_image_id" />
</div>
<?php
} );
add_action( 'edited_industry', function ( $term_id ) {
if ( isset( $_POST['featured_image_id'] ) ) {
update_term_meta( $term_id, 'featured_image_id', absint( $_POST['featured_image_id'] ) );
}
} );
// 10) Archive templates — template hierarchy
// taxonomy-{taxonomy}-{slug}.php → taxonomy-industry-finance.php
// taxonomy-{taxonomy}.php → taxonomy-industry.php
// taxonomy.php
// archive.php
// index.php
// 11) REST API
// GET /wp-json/wp/v2/industries
// GET /wp-json/wp/v2/industries/{id}
// POST /wp-json/wp/v2/industries (needs auth + manage_categories)
// Filter posts: /wp-json/wp/v2/posts?industry=42
// 12) Block editor support — show in the right sidebar
// 'show_in_rest' => true is required for block editor to fetch the taxonomy.
// Use core/post-terms block in templates: <!-- wp:post-terms {"term":"industry"} /-->
// 13) Querying terms (not posts)
$terms = get_terms( array(
'taxonomy' => 'industry',
'hide_empty' => false,
'parent' => 0, // top-level only
'orderby' => 'name',
'order' => 'ASC',
) );
// 14) Migration / cleanup
wp_delete_term( $term_id, 'industry' );
// Use Tools → Import/Export, or WP-CLI: 'wp term migrate' for bulk operations.
// wp term list industry
// wp term create industry 'Finance' --slug=finance
// wp term update industry 'Banking' --parent=42
// 15) Common bugs
// • Forgetting to flush rewrites after registering with rewrite slug → 404 on /industry/finance
// Visit Settings → Permalinks once, or call flush_rewrite_rules() on plugin activation
// • Taxonomy registered but NOT on init hook — too early; WP not ready
// • show_in_rest = false — block editor doesn't show the taxonomy panel
// • Hierarchical taxonomy used for tag-like data — UX confusing; pick the right model
// • register_taxonomy slug conflicts with built-in (post_tag, category) — pick a unique name
// • wp_set_object_terms with strings — WordPress creates terms on the fly; can lead to typos as new terms
// • Querying terms with hide_empty:true (default) misses unused — set false when listing all
// • Filtering REST endpoint with the wrong param key — slug != ID; check the resource shape
Why it matters
Custom taxonomies are how you organise custom post types — register them on init, mark show_in_rest so the block editor + API can use them, and pair with a template (taxonomy-{name}.php) for archive rendering. Add term meta + custom admin fields for richer editing without third-party plugins.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
register_taxonomy('genre', 'book', [
'public' => true,
'hierarchical' => true,
]);
Try it Yourself »
Discussion
Loading…