get_terms by custom post type

I have two custom post types ‘country’ and ‘city’ and a shared taxonomy ‘flag’.

If I use:

<?php $flags = get_terms('flag', 'orderby=name&hide_empty=0');

I get a list of all terms in the taxonomy, but I want to limit the list to the post type ‘country’.

How can I do it?

Using the new solution

$flags = wpse57444_get_terms('flags',array('parent' => 0,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($flags as $flag) {
    $childTerms = wpse57444_get_terms('flags',array('parent' => $flag->term_id,'hide_empty' => 1,'post_types' =>array('country')));
    foreach ($childTerms as $childTerm) {
        echo $childTerm->name.'<br />';

I can’t echo $childTerm->name. Why?


I’m afraid this isn’t possible natively (yet?). See this trac:

Similarly on the taxonomy admin page the post count reflects all post types. (I’m pretty sure there is a trac ticket for that too)

See also, this related post.

New solution

Having written the one below, I’ve released a much better way (alteast in the sense that you can do more) is to use the filters provided in the get_terms() call. You can create a wrapper function that uses get_terms and (conditionally) adds a filter to manipulate the SQL query (to restrict by post type).

The function takes the same arguments as get_terms($taxonomies, $args). $args takes the additional argument of post_types which takes an array|string of post types.

But I can’t gurantee that everything works ‘as expected’ (I’m thinking padding the count). It does appear to work using just default the $args for get_terms.

function wpse57444_get_terms( $taxonomies, $args=array() ){
    //Parse $args in case its a query string.
    $args = wp_parse_args($args);

    if( !empty($args['post_types']) ){
        $args['post_types'] = (array) $args['post_types'];
        add_filter( 'terms_clauses','wpse_filter_terms_by_cpt',10,3);

        function wpse_filter_terms_by_cpt( $pieces, $tax, $args){
            global $wpdb;

            // Don't use db count
            $pieces['fields'] .=", COUNT(*) " ;

            //Join extra tables to restrict by post type.
            $pieces['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 ";

            // Restrict by post type and Group by term_id for COUNTing.
            $post_types_str = implode(',',$args['post_types']);
            $pieces['where'].= $wpdb->prepare(" AND p.post_type IN(%s) GROUP BY t.term_id", $post_types_str);

            remove_filter( current_filter(), __FUNCTION__ );
            return $pieces;
    } // endif post_types set

    return get_terms($taxonomies, $args);           


$args =array(
    'hide_empty' => 0,
    'post_types' =>array('country','city'),

$terms = wpse57444_get_terms('flag',$args);

Original work-around

Inspired from the above trac ticket, (tested, and it works for me)

function wpse57444_filter_terms_by_cpt($taxonomy, $post_types=array() ){
    global $wpdb;

    $post_types=(array) $post_types;
    $key = 'wpse_terms'.md5($taxonomy.serialize($post_types));
    $results = wp_cache_get($key);

    if ( false === $results ) {
       $where =" WHERE 1=1";
       if( !empty($post_types) ){
            $post_types_str = implode(',',$post_types);
            $where.= $wpdb->prepare(" AND p.post_type IN(%s)", $post_types_str);

       $where .= $wpdb->prepare(" AND tt.taxonomy = %s",$taxonomy);

       $query = "
          SELECT t.*, COUNT(*) 
          FROM $wpdb->terms AS t 
          INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id 
          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 
          GROUP BY t.term_id";

       $results = $wpdb->get_results( $query );
       wp_cache_set( $key, $results );

    return $results;


 $terms = wpse57444_filter_terms_by_cpt('flag',array('country','city'));


 $terms = wpse57444_filter_terms_by_cpt('flag','country');

Leave a Comment