Tips・WordPress templates and fetching posts

Listing categories and terms with get_terms

How to write get_terms to output a list of categories or custom taxonomy terms. Covers a cheat sheet of arguments, adding links and counts, marking the term you're currently viewing, and how it differs from get_the_terms.

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 itWhat it holds
$term->nameThe display name (“Winter” and so on)
$term->slugThe slug (winter)
$term->term_idThe ID (a number)
$term->countHow many posts have that term
$term->descriptionThe 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

ArgumentMeaningExample
taxonomyWhich taxonomy'category' / 'post_tag' / 'season'
hide_emptyWhether to hide terms with zero poststrue (default) / false
orderbyWhat to sort by'name' (default) / 'count' / 'term_id' / 'include'
orderAscending or descending'ASC' / 'DESC'
numberHow many to fetch10
parentOnly direct children0 (top level only)
child_ofAll descendants of a parent5
include / excludeInclude or exclude by ID[3, 7]
fieldsChange 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.

FunctionWhat it returnsWhen to use it
get_terms()A list of terms across the whole siteFilter 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 youDropping something into a sidebar quickly
the_category()Link HTML for that post’s categoriesA 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.

TipsFiltering by category and taxonomy with tax_queryHow to write the filtering side (tax_query)

Summary

  1. Term lists come from get_terms(). What comes back is an array of objects, so loop with foreach
  2. Build URLs with get_term_link($term). Don’t assemble URL strings yourself
  3. hide_empty defaults to true—terms with zero posts not appearing is normal behavior
  4. get_terms is the whole site, get_the_terms is 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.

FAQ

A term I just created doesn't appear in the list.
get_terms defaults hide_empty to true, so terms with zero posts are excluded. Specify 'hide_empty' => false and empty terms show up too.
What's the difference between get_terms and get_the_terms?
get_terms gives you "the site's whole list of terms" (for filter menus and the like), while get_the_terms gives you "the terms attached to this post" (for showing an article's categories). The names are similar but the purposes are opposite.
How do I build a term's link URL?
Use get_term_link($term). It returns the archive page URL that matches your permalink settings. get_term_link can return a WP_Error, so checking with is_wp_error() first is the safe move.
I want to sort by post count, highest first.
Specify 'orderby' => 'count' and 'order' => 'DESC'. For alphabetical order it's 'orderby' => 'name' (the default).