Show WordPress Taxonomy Terms as Links With a Limit

To show WordPress taxonomy terms as links with a limit, say the first four categories or your five biggest product types, you need get_terms(), a link for each term and some care for the cases where the taxonomy has no terms.

This shortcode does that for any taxonomy. The older version of this post printed the PHP warning “Attempt to read property name on array” when get_terms() returned an error instead of terms, for example with the placeholder taxonomy left in. Now it prints nothing in that case, and every name and link is escaped.

  • [dpc_term_links] shows four categories.
  • [dpc_term_links taxonomy="genre" number="5" orderby="count"] shows the five genres with the most posts.
  • [dpc_term_links taxonomy="post_tag" number="0" separator=" · "] shows every tag.

The snippet

<?php
/**
 * [dpc_term_links taxonomy="category" number="4"]
 * Terms of a taxonomy as links, limited to a number.
 */
add_shortcode( 'dpc_term_links', 'dpc_term_links_shortcode' );

function dpc_term_links_shortcode( $atts ): string {
	$atts = shortcode_atts(
		array(
			'taxonomy'   => 'category',
			'number'     => 4,        // 0 = all.
			'hide_empty' => 'yes',
			'orderby'    => 'name',   // name, count, id or term_order.
			'separator'  => ', ',
		),
		$atts,
		'dpc_term_links'
	);

	$taxonomy = sanitize_key( $atts['taxonomy'] );
	if ( ! taxonomy_exists( $taxonomy ) ) {
		return '';
	}

	$orderby = in_array( $atts['orderby'], array( 'name', 'count', 'id', 'term_order' ), true ) ? $atts['orderby'] : 'name';
	$terms   = get_terms(
		array(
			'taxonomy'   => $taxonomy,
			'number'     => absint( $atts['number'] ),
			'hide_empty' => ! in_array( strtolower( (string) $atts['hide_empty'] ), array( 'no', 'false', '0', 'off' ), true ),
			'orderby'    => $orderby,
			'order'      => 'count' === $orderby ? 'DESC' : 'ASC',
		)
	);
	// WP_Error (e.g. a broken query) used to end up in foreach: "Attempt to read property name on array".
	if ( is_wp_error( $terms ) || ! $terms ) {
		return '';
	}

	$current = ( is_category() || is_tag() || is_tax() ) ? get_queried_object_id() : 0;
	$links   = array();
	foreach ( $terms as $term ) {
		$url = get_term_link( $term );
		if ( is_wp_error( $url ) ) {
			continue;
		}
		$is_current = ( (int) $term->term_id === $current );
		$links[]    = sprintf(
			'<a class="dpc-term-links__link%1$s" href="%2$s"%3$s>%4$s</a>',
			$is_current ? ' is-current' : '',
			esc_url( $url ),
			$is_current ? ' aria-current="page"' : '',
			esc_html( $term->name )
		);
	}

	return $links ? '<span class="dpc-term-links">' . implode( esc_html( $atts['separator'] ), $links ) . '</span>' : '';
}

Add it with Scripts Organizer

In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor and publish. Leave Action/Hook empty.

Then use [dpc_term_links] anywhere shortcodes run: post content, a Shortcode block in a block theme template, a widget or your page builder’s shortcode element. A normal shortcode fits better here than a Shortcode location block, because the taxonomy and the limit are attributes: one code block covers every place and taxonomy, instead of one block per combination.

The same code also works unchanged in a small plugin or an mu-plugin.

How it works

  • The taxonomy goes through sanitize_key() and must exist. number goes through absint() (0 means all), hide_empty accepts no, false, 0 or off to show empty terms, and orderby goes through an allowlist. Ordering by count lists the biggest terms first.
  • get_terms() returns a WP_Error on failure. The old code looped over that object, which is where the warning came from; the shortcode now checks for it and for an empty result.
  • get_term_link() can also return a WP_Error, for example for a taxonomy that is not public, so such terms are skipped.
  • On a term archive the matching link gets an is-current class and aria-current="page". Names go through esc_html(), URLs through esc_url() and the separator through esc_html().

Customise it

  • A full list with the active term highlighted, for a filter menu: see List WordPress categories with an active class.
  • Only the terms of the current post: use get_the_terms( get_the_ID(), $taxonomy ) instead of get_terms() and array_slice() for the limit.
  • Separators in CSS instead: pass separator="" and style .dpc-term-links__link+.dpc-term-links__link::before{content:" / "}.

Gotchas

  • Empty terms are hidden by default. The old snippet showed them; pass hide_empty="no" for that.
  • With number and hide_empty together, WordPress limits after hiding empty terms, so you get up to that many terms that have posts.

In the Scripts Organizer library Import it with one click from Scripts Organizer.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You’ll also get DPlugins product updates. No spam, and you can unsubscribe anytime.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You'll also get DPlugins product updates. No spam, and you can unsubscribe anytime.