Contents
Overview
This article is a complete, detailed tutorial on how to register a hierarchical custom taxonomy in PHP for WordPress. It explains concepts, arguments and options, shows multiple real-world code examples (plugin and theme), demonstrates admin and front-end usage, and covers related topics such as rewrite rules, REST/Gutenberg support, capabilities, term meta and programmatic term management.
What is a hierarchical taxonomy?
A hierarchical taxonomy behaves like built-in WordPress categories: terms can have parent/child relationships, nested admin metabox UI, and hierarchical displays on the front end. Contrastingly, a non-hierarchical taxonomy behaves like tags (flat list with no parents).
When to use a hierarchical taxonomy
- When you need parent/child relationships (for example: genres, product categories, locations).
- When you want nested URLs for child terms (when rewriting).
- When you want the familiar category metabox in the post edit screen.
Core function: register_taxonomy()
register_taxonomy() is called (typically on the init hook) to register your taxonomy. The basic signature:
– taxonomy: (string) taxonomy key (slug), lowercase, no spaces.
– object_type: (stringarray) post types (for example post, page, or custom type book).
– args: (array) options (labels, hierarchical, rewrite, capabilities, …).
Key arguments explained
labels | Array of labels for UI (name, singular_name, menu_name, all_items, edit_item, add_new_item, …). Use _x/__ for i18n in real code. |
hierarchical | Boolean. true for hierarchical (category-like), false for non-hierarchical (tag-like). |
public | Whether taxonomy is publicly queryable. Usually true. |
show_ui | Whether to generate default UI in admin. |
show_in_rest | Boolean or array. true to expose via WP REST API and enable Gutenberg taxonomy block. |
rewrite | Array or false. Subkeys: slug (string), with_front (bool), hierarchical (bool). Setting hierarchical => true allows nested term URLs for child terms. |
capabilities | Array of capability keys to control who can manage/edit/delete/assign terms. If set, you may want map_meta_cap => true. |
show_admin_column | Boolean. Adds a column to post list table with term info. |
meta_box_cb | Custom callback to render metabox. By default hierarchical taxonomies use post_categories_meta_box. |
query_var | Boolean/string to enable query var. If true, the taxonomy name is used as query var. |
show_in_nav_menus | Whether the taxonomy can be used in Appearance → Menus. |
Minimal example (themes functions.php or plugin)
This is the simplest working example that registers a hierarchical taxonomy called genre for a custom post type book.
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, ) args = array( labels => labels, hierarchical => true, public => true, show_ui => true, show_in_nav_menus => true, show_in_rest => true, // enable Gutenberg and REST rewrite => array( slug => genre, with_front => false, hierarchical => true ), show_admin_column => true, ) register_taxonomy( genre, array( book ), args ) } ?>
Complete plugin example with activation/deactivation (recommended)
When you register rewrite rules with custom taxonomy rewrite slugs, flush_rewrite_rules on plugin activation so pretty permalinks work immediately. Never flush on every page load — only on activation/deactivation.
Genres, singular_name => Genre, menu_name => Genres, ) args = array( labels => labels, hierarchical => true, public => true, show_ui => true, show_in_rest => true, rewrite => array( slug => book/genre, with_front => false, hierarchical => true, ), show_admin_column => true, ) register_taxonomy( genre, array( book ), args ) } // Activation: register and flush rewrite rules register_activation_hook( __FILE__, bg_activation ) function bg_activation() { // Register on activation, then flush bg_register_genre_taxonomy() flush_rewrite_rules() } // Deactivation: flush rewrite rules register_deactivation_hook( __FILE__, bg_deactivation ) function bg_deactivation() { flush_rewrite_rules() } ?>
Using custom capabilities
If you need to restrict who can manage taxonomy terms (separate from post capabilities), provide a capabilities array and set map_meta_cap => true. Capability names are:
- manage_terms
- edit_terms
- delete_terms
- assign_terms
Example giving only custom role editor plus admin permissions (you must ensure roles exist and capabilities are assigned separately):
true, labels => array( name => Departments ), show_in_rest => true, capabilities => array( manage_terms => manage_departments, edit_terms => edit_departments, delete_terms => delete_departments, assign_terms => assign_departments, ), map_meta_cap => true, ) register_taxonomy( department, array( employee ), args ) } ?>
Note: You need to add these capabilities to roles (e.g., using add_cap on role objects) so users can manage terms.
Rewrite and hierarchical URLs
To have nested term URLs for child terms, include hierarchical => true inside the rewrite array. Example: parent term /genre/fiction/child-term/ will be generated when hierarchical rewrite is enabled and your permalink structure supports it.
Programmatically creating, updating, assigning terms
Common functions:
- wp_insert_term( term, taxonomy, args = array() )
- wp_update_term( term_id, taxonomy, args = array() )
- wp_delete_term( term_id, taxonomy )
- wp_set_object_terms( object_id, terms, taxonomy, append = false )
- get_terms( args ) and wp_get_post_terms( post_id, taxonomy )
Example creating hierarchical terms and assigning to a post:
parent_id ) ) } // Assign terms to a post post_id = 123 wp_set_object_terms( post_id, array( Science Fiction ), genre, false ) // Alternatively assign by term IDs // wp_set_object_terms( post_id, array( child_id ), genre, false ) ?>
Getting terms and traversing hierarchy
Common helpers:
- get_terms( array( taxonomy => genre, parent => 0 ) ) — top-level terms
- get_term_children( term_id, taxonomy ) — returns IDs of children (recursive)
- get_ancestors( term_id, taxonomy, taxonomy ) — returns array of ancestor IDs
- get_term( term_id, taxonomy ) — get a single term object
genre, hide_empty => false, parent => 0, ) ) foreach ( top_terms as term ) { echo esc_html( term->name ) // Get children children = get_terms( array( taxonomy => genre, hide_empty => false, parent => term->term_id, ) ) foreach ( children as child ) { echo nbspnbsp . esc_html( child->name ) } } ?>
Displaying hierarchical taxonomy lists on the front-end
You can use wp_list_categories or wp_dropdown_categories specifying the taxonomy argument.
genre, title_li => , show_count => true, hierarchical => true, ) ) // dropdown of genres wp_dropdown_categories( array( taxonomy => genre, hide_empty => 0, name => genre_dropdown, ) ) ?>
Taxonomy templates and template hierarchy
Template files you can add to your theme to control taxonomy archive displays:
- taxonomy-{taxonomy}-{term}.php (most specific)
- taxonomy-{taxonomy}.php
- taxonomy.php
- archive.php
- index.php
In templates, use get_the_terms( post_id, genre ) to fetch assigned terms, and use is_tax( genre, term-slug ) to conditionally display content.
Term meta (saving additional data on terms)
Modern practice is to use register_term_meta() (since WP 4.4 ) to register term meta keys with proper sanitization and REST visibility. Then use get_term_meta() and update_term_meta().
integer, description => Attachment ID for genre image, single => true, show_in_rest => true, ) ) } // Update or retrieve update_term_meta( term_id, featured_image_id, 456 ) image_id = get_term_meta( term_id, featured_image_id, true ) ?>
Admin list columns for taxonomy and post list column
You can enable the automatic admin column with show_admin_column => true. To customize columns further, use the manage_edit-{taxonomy}_columns filter and manage_{taxonomy}_custom_column action.
Custom metabox for hierarchical taxonomy
By default hierarchical taxonomies use the category-style metabox. To use a custom metabox rendering, pass a callback via meta_box_cb in register_taxonomy() or remove the default and add your custom meta box with add_meta_box in admin_init.
Enabling REST/Gutenberg support
Set show_in_rest => true when registering taxonomy to expose it through the WP REST API and to make it available in Gutenberg editors. You can also provide rest_base and rest_controller_class inside args.
Flushing rewrite rules
– Call flush_rewrite_rules() only on activation/deactivation of plugins, not on every page.
– If registering taxonomies in a theme, you may need to reactivate the theme or flush permalinks once after changes to rewrite slugs.
Unregistering a taxonomy
WordPress provides unregister_taxonomy() but usage is uncommon. It must be called early and carefully — removing a taxonomy from registered list may have side effects. For plugins, its common to remove the taxonomy only on uninstall or deactivate and flush rewrite rules.
Practical tips and gotchas
- Always register taxonomies on init hook. If you register before post types are registered, pass the post type names as strings and ensure post types exist by the time queries run.
- If your custom post type is registered later than the taxonomy, you can attach taxonomy to the post type with register_taxonomy_for_object_type() after both are registered.
- When using custom capabilities, remember to give those capabilities to appropriate roles. Otherwise users may not be able to assign terms.
- Test rewrite/permalink changes after registration flush rewrite rules on activation in plugins.
- Use i18n functions for labels in production code: __(Genres, text-domain) or _x().
- For hierarchical rewrites, set hierarchical => true in the rewrite array to generate nested URLs for child terms.
- Use register_term_meta() for term-level metadata with sanitization and REST exposure.
Examples recap (short snippets)
Register taxonomy for posts (categories-like):
array( name => Genres ), hierarchical => true, show_in_rest => true, rewrite => array( slug => genre, hierarchical => true ), ) ) } ) ?>
Assign terms to a post by name:
List taxonomy terms in a theme template:
. esc_html( term->name ) . } } ?>
Useful references
- register_taxonomy — WordPress Developer Reference
- register_term_meta — WordPress Developer Reference
- WordPress REST API Handbook
Summary
A hierarchical custom taxonomy in WordPress is registered with register_taxonomy(), set with hierarchical => true, and should be registered on init. Consider rewrite rules, REST support (show_in_rest), admin UI options and custom capabilities. Use register_term_meta for term metadata and always flush rewrite rules only on activation/deactivation. The examples above cover typical use cases for registering, managing, and displaying hierarchical taxonomies like genres, categories, and locations.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |