A taxonomy is a way to group content. Categories and tags are the two taxonomies WordPress ships with. When events need grouping by type or by speaker, you register your own. Each taxonomy gets an admin screen, archive pages, REST endpoints and efficient querying for free.
Hierarchical and flat taxonomies
Taxonomies come in two shapes:
| Hierarchical | Flat | |
|---|---|---|
| Like | Categories | Tags |
| Terms can have parents | Yes, such as Workshops then Beginner workshops | No |
| Editor interface | A checklist of existing terms | A free-text field that creates terms as you type |
| Good for | A fixed, curated structure | Open-ended labels |
For the events site you will register one of each: a hierarchical myplugin_event_type (Workshops, with Beginner workshops below it, Talks and Meetups) and a flat myplugin_speaker.
Registering a taxonomy
Taxonomies are content structure, so they belong in the plugin, registered on init like post types:
<?php
// wp-content/plugins/myplugin/includes/taxonomies.php
defined( 'ABSPATH' ) || exit;
add_action( 'init', 'myplugin_register_taxonomies' );
function myplugin_register_taxonomies() {
register_taxonomy(
'myplugin_event_type',
array( 'myplugin_event' ),
array(
'labels' => array(
'name' => _x( 'Event Types', 'taxonomy general name', 'myplugin' ),
'singular_name' => _x( 'Event Type', 'taxonomy singular name', 'myplugin' ),
'search_items' => __( 'Search Event Types', 'myplugin' ),
'all_items' => __( 'All Event Types', 'myplugin' ),
'parent_item' => __( 'Parent Event Type', 'myplugin' ),
'edit_item' => __( 'Edit Event Type', 'myplugin' ),
'add_new_item' => __( 'Add New Event Type', 'myplugin' ),
'menu_name' => __( 'Event Types', 'myplugin' ),
),
'hierarchical' => true,
'public' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => array(
'slug' => 'event-type',
'with_front' => false,
'hierarchical' => true,
),
)
);
register_taxonomy(
'myplugin_speaker',
array( 'myplugin_event' ),
array(
'labels' => array(
'name' => _x( 'Speakers', 'taxonomy general name', 'myplugin' ),
'singular_name' => _x( 'Speaker', 'taxonomy singular name', 'myplugin' ),
'search_items' => __( 'Search Speakers', 'myplugin' ),
'add_new_item' => __( 'Add New Speaker', 'myplugin' ),
'separate_items_with_commas' => __( 'Separate speakers with commas', 'myplugin' ),
'menu_name' => __( 'Speakers', 'myplugin' ),
),
'hierarchical' => false,
'public' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => array( 'slug' => 'speaker' ),
)
);
}
Load the file from your main plugin file with require_once MYPLUGIN_PATH . 'includes/taxonomies.php';.
The arguments that matter most:
- The key (
myplugin_event_type): prefixed, lower case, at most 32 characters, and never changed once terms exist. - Object types (
array( 'myplugin_event' )): the post types the taxonomy attaches to. One taxonomy can serve several post types. hierarchical:truefor categories-style,falsefor tags-style.show_in_rest: required for the taxonomy panel to appear in the block editor, and for REST endpoints such as/wp-json/wp/v2/myplugin_event_type.show_admin_column: adds a column for the taxonomy to the events list screen.rewrite: sets term archive URLs. An event type lives at/event-type/workshops/, and with'hierarchical' => truea child appears at/event-type/workshops/beginner-workshops/.
As with post types, flush rewrite rules once after registering, on plugin activation. Call myplugin_register_taxonomies() in your activation function before flush_rewrite_rules().
Attaching an existing taxonomy
You can also attach a taxonomy to a post type that is registered elsewhere, such as core tags on events:
// wp-content/plugins/myplugin/includes/taxonomies.php
add_action( 'init', 'myplugin_attach_tags', 20 );
function myplugin_attach_tags() {
register_taxonomy_for_object_type( 'post_tag', 'myplugin_event' );
}
Priority 20 makes sure the post type exists before you attach to it.
Taxonomy or meta?
The most important modelling decision for custom data is whether it is a term or a meta value. Use this rule of thumb:
| Question | Taxonomy | Meta |
|---|---|---|
| Will you list or filter content by this value? | Yes | Rarely |
| Is the value shared by many posts? | Yes, such as an event type | No, such as an event date |
| Does it need its own archive page? | Yes | No |
| Is it a free-form or numeric value? | No | Yes, such as a price |
| Does it need a description or its own fields? | Terms support both | No |
For the events site:
- Event type: taxonomy. Many events share a type, and visitors browse by it.
- Speaker: taxonomy. You want an archive of every event one person speaks at.
- Event date: meta. It is specific to each event and used for sorting and date ranges, not grouping.
- Price: meta. It is a number, used for filtering and display rather than as a label.
Querying by term is fast, because the term relationship table is indexed for it. Filtering by meta value is slower, as the meta queries lesson explains. If you catch yourself filtering by a meta value on every archive, it probably wants to be a taxonomy.
Working with terms in code
Create terms and assign them with the taxonomy API:
// wp-content/plugins/myplugin/includes/taxonomies.php
function myplugin_seed_event_types() {
if ( ! current_user_can( 'manage_categories' ) ) {
return;
}
$workshops = wp_insert_term( 'Workshops', 'myplugin_event_type' );
if ( ! is_wp_error( $workshops ) ) {
wp_insert_term(
'Beginner workshops',
'myplugin_event_type',
array( 'parent' => $workshops['term_id'] )
);
}
wp_insert_term( 'Talks', 'myplugin_event_type' );
wp_insert_term( 'Meetups', 'myplugin_event_type' );
}
wp_insert_term() returns an array with the new term_id, or a WP_Error if, for example, the term already exists. Always check with is_wp_error().
To set the terms on an event:
// Replace the event's types.
wp_set_object_terms( $event_id, array( 'beginner-workshops' ), 'myplugin_event_type' );
// Add a speaker without removing existing ones (the fourth argument appends).
wp_set_object_terms( $event_id, array( 'Priya Shah' ), 'myplugin_speaker', true );
Strings are matched against existing slugs and names, and a string that matches nothing creates a new term. To be precise, pass integer term IDs instead.
Displaying terms
In a template, get_the_term_list() returns linked term names as HTML. Pass it through wp_kses_post() when you print it, which keeps the links and strips anything unsafe:
<?php
// wp-content/themes/mytheme/single-myplugin_event.php, inside the Loop
echo wp_kses_post( get_the_term_list( get_the_ID(), 'myplugin_event_type', '<p class="event-types">', ', ', '</p>' ) );
For full control, get_the_terms() returns term objects:
<?php
$myplugin_speakers = get_the_terms( get_the_ID(), 'myplugin_speaker' );
if ( $myplugin_speakers && ! is_wp_error( $myplugin_speakers ) ) {
foreach ( $myplugin_speakers as $myplugin_speaker ) {
printf(
'<a href="%s">%s</a> ',
esc_url( get_term_link( $myplugin_speaker ) ),
esc_html( $myplugin_speaker->name )
);
}
}
get_the_terms() returns false when there are no terms and a WP_Error if the taxonomy does not exist, so check both.
To list all terms in a taxonomy, such as a menu of event types, use get_terms():
<?php
$myplugin_types = get_terms(
array(
'taxonomy' => 'myplugin_event_type',
'hide_empty' => true,
'parent' => 0,
)
);
'parent' => 0 returns top-level event types only.
Term archives
Each term gets an archive page listing its posts, handled by the main query. The template hierarchy looks for taxonomy-myplugin_event_type-workshops.php, then taxonomy-myplugin_event_type.php, then taxonomy.php, then archive.php and index.php. In a template, get_queried_object() returns the current term, so you can print its name and description.
Querying posts by term
To fetch events of one type yourself, use tax_query in WP_Query:
<?php
$myplugin_workshops = new WP_Query(
array(
'post_type' => 'myplugin_event',
'tax_query' => array(
array(
'taxonomy' => 'myplugin_event_type',
'field' => 'slug',
'terms' => array( 'workshops' ),
),
),
)
);
By default, child terms are included: a query for Workshops also returns Beginner workshops. Add 'include_children' => false to the inner array to prevent that. The meta and tax queries lesson covers combining several conditions.
Summary
- Register taxonomies in a plugin, on
init, with a prefixed key and full labels. - Hierarchical taxonomies work like categories; flat ones work like tags.
show_in_restis needed for the block editor panel and REST endpoints.- Use a taxonomy for shared values you group and filter by; use meta for unique or numeric values.
- Manage terms with
wp_insert_term()andwp_set_object_terms(); display them withget_the_term_list()andget_the_terms(). - Query by term with
tax_query.
In the next lesson you will write custom queries with WP_Query.