get_terms() is how you output a list of categories or custom taxonomy terms (a filter menu, a tag cloud). What comes back is an array of term objects, so you loop over it with foreach.
<?php
$terms = get_terms([
'taxonomy' => 'season',
'hide_empty' => true,
]);
?>
<?php if ($terms && !is_wp_error($terms)) : ?>
<ul class="term-list">
<?php foreach ($terms as $term) : ?>
<li>
<a href="<?php echo esc_url(get_term_link($term)); ?>">
<?php echo esc_html($term->name); ?> (<?php echo esc_html($term->count); ?>)
</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>What you can get from a term object
From $term inside the foreach, these five are the ones you’ll use.
| How to write it | What it holds |
|---|---|
$term->name | The display name (“Winter” and so on) |
$term->slug | The slug (winter) |
$term->term_id | The ID (a number) |
$term->count | How many posts have that term |
$term->description | The description |
Build URLs with get_term_link($term). The trick is not to assemble a string like /season/winter/ yourself—that way nothing breaks when permalink settings or the taxonomy’s rewrite change.
Cheat sheet of arguments
| Argument | Meaning | Example |
|---|---|---|
taxonomy | Which taxonomy | 'category' / 'post_tag' / 'season' |
hide_empty | Whether to hide terms with zero posts | true (default) / false |
orderby | What to sort by | 'name' (default) / 'count' / 'term_id' / 'include' |
order | Ascending or descending | 'ASC' / 'DESC' |
number | How many to fetch | 10 |
parent | Only direct children | 0 (top level only) |
child_of | All descendants of a parent | 5 |
include / exclude | Include or exclude by ID | [3, 7] |
fields | Change the shape of what’s returned | 'ids' / 'names' / 'id=>name' |
Marking the term you’re currently viewing
A filter menu is much easier to use when you can see which one you’re on.
<?php
$terms = get_terms(['taxonomy' => 'season', 'hide_empty' => false]);
$current = get_queried_object(); // on a taxonomy archive, the current term
?>
<ul class="filter">
<?php foreach ($terms as $term) : ?>
<?php $is_current = (!empty($current->term_id) && $current->term_id === $term->term_id); ?>
<li class="<?php echo $is_current ? 'is-current' : ''; ?>">
<a href="<?php echo esc_url(get_term_link($term)); ?>"><?php echo esc_html($term->name); ?></a>
</li>
<?php endforeach; ?>
</ul>For categories you can also check with the conditional tag is_category($term->term_id).
Hierarchical taxonomies
For taxonomies with hierarchy, like categories, fetching the parents first and then the children builds a nested list.
<?php $parents = get_terms(['taxonomy' => 'category', 'parent' => 0]); ?>
<ul>
<?php foreach ($parents as $parent) : ?>
<li>
<a href="<?php echo esc_url(get_term_link($parent)); ?>"><?php echo esc_html($parent->name); ?></a>
<?php $children = get_terms(['taxonomy' => 'category', 'parent' => $parent->term_id]); ?>
<?php if ($children) : ?>
<ul>
<?php foreach ($children as $child) : ?>
<li><a href="<?php echo esc_url(get_term_link($child)); ?>"><?php echo esc_html($child->name); ?></a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>Choosing among the similar functions
The names look alike and blur together, so remember them by their role.
| Function | What it returns | When to use it |
|---|---|---|
get_terms() | A list of terms across the whole site | Filter menus, tag clouds |
get_the_terms($id, $tax) | The terms on that post (an array of objects) | Showing an article’s categories |
wp_get_post_terms($id, $tax, $args) | Also that post’s terms (supports fields) | Building related-post conditions |
wp_list_categories() | Generates the HTML list for you | Dropping something into a sidebar quickly |
the_category() | Link HTML for that post’s categories | A single line under an article heading |
“Building a list = get_terms; showing this article’s tags = get_the_terms”—mix those up and you get the visual bug where every category on the site is listed on an article.
Showing a post’s terms (get_the_terms)
<?php $terms = get_the_terms(get_the_ID(), 'season'); ?>
<?php if ($terms && !is_wp_error($terms)) : ?>
<ul class="tags">
<?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; ?>Where you don’t need to build the HTML yourself, the_terms() does it in one line.
<?php the_terms(get_the_ID(), 'season', 'Season: ', ', '); ?>Letting WordPress build the HTML (wp_list_categories)
If you just want something in the sidebar quickly, a function that generates the HTML too is the easy route.
<?php
wp_list_categories([
'taxonomy' => 'season',
'title_li' => '', // remove the "Categories" heading li
'show_count' => true, // show counts
'hide_empty' => false,
]);
?>The split is: get_terms() when you want to decide the markup yourself, wp_list_categories() when speed matters most.
Summary
- Term lists come from
get_terms(). What comes back is an array of objects, so loop withforeach - Build URLs with
get_term_link($term). Don’t assemble URL strings yourself hide_emptydefaults totrue—terms with zero posts not appearing is normal behaviorget_termsis the whole site,get_the_termsis that post. For speed,wp_list_categories()
Once you can output lists, you can design your own navigation—filter menus, tag clouds and other paths for people to move around.