How to get_terms for post type

Ever registered taxonomies for multiple post types in WordPress? Then you already know that the get_terms function has one important drawback: it does not provide post type parameter. It’s impossible to display terms related to selected post type (or post types) or get the correct posts count for those terms.

But we’ve developed a solution, based on WordPress native terms_clauses filter.

// Handle the post_type parameter given in get_terms function
function df_terms_clauses($clauses, $taxonomy, $args) {
	if (!empty($args['post_type']))	{
		global $wpdb;

		$post_types = array();

		foreach($args['post_type'] as $cpt)	{
			$post_types[] = "'".$cpt."'";

	    if(!empty($post_types))	{
			$clauses['fields'] = 'DISTINCT '.str_replace('tt.*', 'tt.term_taxonomy_id, tt.term_id, tt.taxonomy, tt.description, tt.parent', $clauses['fields']).', COUNT(t.term_id) AS count';
			$clauses['join'] .= ' INNER JOIN '.$wpdb->term_relationships.' AS r ON r.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN '.$wpdb->posts.' AS p ON p.ID = r.object_id';
			$clauses['where'] .= ' AND p.post_type IN ('.implode(',', $post_types).')';
			$clauses['orderby'] = 'GROUP BY t.term_id '.$clauses['orderby'];
    return $clauses;
add_filter('terms_clauses', 'df_terms_clauses', 10, 3);

To make use of this just add post_type argument to get_terms function like so:

// Get the categories for post and product post types
$categories = get_terms('category', array(
 	'post_type' => array('post', 'product'),
 	'fields' => 'all'

You may use any of the built-in arguments with one exception: in order to get the correct posts count you must keep the ‘fields’ set to default ‘all’. If you don’t provide the post_type parameter the get_terms results will not get affected by the term_clauses filter.

A new, updated approach – use it if you have issues with the one above:

 * Extend get terms with post type parameter.
 * @global $wpdb
 * @param string $clauses
 * @param string $taxonomy
 * @param array $args
 * @return string
function df_terms_clauses( $clauses, $taxonomy, $args ) {
	if ( isset( $args['post_type'] ) && ! empty( $args['post_type'] ) && $args['fields'] !== 'count' ) {
		global $wpdb;

		$post_types = array();

		if ( is_array( $args['post_type'] ) ) {
			foreach ( $args['post_type'] as $cpt ) {
				$post_types[] = "'" . $cpt . "'";
		} else {
			$post_types[] = "'" . $args['post_type'] . "'";

		if ( ! empty( $post_types ) ) {
			$clauses['fields'] = 'DISTINCT ' . str_replace( 'tt.*', 'tt.term_taxonomy_id, tt.taxonomy, tt.description, tt.parent', $clauses['fields'] ) . ', COUNT(p.post_type) AS count';
			$clauses['join'] .= ' LEFT JOIN ' . $wpdb->term_relationships . ' AS r ON r.term_taxonomy_id = tt.term_taxonomy_id LEFT JOIN ' . $wpdb->posts . ' AS p ON p.ID = r.object_id';
			$clauses['where'] .= ' AND (p.post_type IN (' . implode( ',', $post_types ) . ') OR p.post_type IS NULL)';
			$clauses['orderby'] = 'GROUP BY t.term_id ' . $clauses['orderby'];
	return $clauses;

add_filter( 'terms_clauses', 'df_terms_clauses', 10, 3 );

Leave a Reply

Your email address will not be published. Required fields are marked *

21 comments on "How to get_terms for post type"

  1. hy,
    i saw an issue: if there is only one post linked to a term inside this custom post type but the post is trashed, then it will also return the term although there are no real posts available.

    thanks for your great work!

  2. Extremely helpful! Worked like a charm. WP needs to build this into core :)

  3. Hi,

    For some reason this returns an empty array, i’m using:
    $tax_terms = get_terms(‘category’, array(‘post_type’ => ‘portfolio’,’fields’ => ‘all’));

    But when I change portfolio to post I can see the categories! Why is this?

  4. I think this kind of Save my life in the real sense of it. So many hours spent on trying to figure this out.
    Thank you again!!

  5. PERFECT!!!
    work too with ACF taxonomy filter…
    thats save my live!

    • Welcome :)

  6. Hey, thanks for this snippet !
    It also works for me :)

    But it seems “hide_empty” does not work…? Is there something to add to your function ?
    I got this problem : a taxonomy is shared by 2 post types, and the first post type as at least one post in every term, but the other doesn’t… so I need to add ‘hide_empty’ => 1 for the second post type page

    thanks !

    • Hi Thomas,
      Please try the second method (just added).

      • Hi ! Thanks a lot for the update :)
        Unfortunatly, hide_empty still broken if another post type has post in a term

    • I’ve taken a deeper look into this.
      It’s not possible with native get_terms query modifications (or it would be totally unoptimal to do so).

      WordPress count is stored in single term “count” column – it is not generated dynamically, but updated with _update_post_term_count function once post is added or removed from the term

      So when you have a taxonomy shared by a number of post types there is still only one field for the count.

      The solution would be to store the count for each post type in additional db entry, for example term_meta, update the value when a post is created / removed from the term, and run a meta query on that entry whenever there is “hide_empty” in get_terms args.

      • Ok Bartosz, thank you so much for your time
        I’ll check this out :)

  7. Worked for me, Thanks a lot.

  8. Nice, thanks a lot!

  9. Works perfectly! Thanks for sharing!

  10. Hi it almost work for me..but
    I would appreciate if you could advise is any way to get parent term as well ?
    also how can use args ”hide_empty’=>0′” . regards

    • self-solved…, I realized my question is totally meaningless….

  11. I <3 YOU !
    Thanks :D

  12. Saved my day… Thanks!

  13. thanks!

  14. Thanks for this. I had been trying to figure out an easier way to do this than writing an entirely new function to handle getting terms by post type and I wasn’t sure how to tap into the filters that I had seen in the taxonomy.php file.

    • You’re welcome Mike.