WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
Could you be a little clearer? – TheDeadMedic Jul 4 '12 at 16:42

I'm afraid this isn't possible natively (yet?). See this trac: http://core.trac.wordpress.org/ticket/18106

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) http://core.trac.wordpress.org/ticket/14084

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');
share|improve this answer
It works, but what can I do with my $args? I mean... parent=0&orderby=name&hide_empty=0 – user1443216 Jul 4 '12 at 19:11
no - this has to be an array: $args = array('parent'=>0,'orderby'=>'name','hide_empty'=>0);. I will edit this to allow query strings... – Stephen Harris Jul 4 '12 at 19:44
Where can I put my $args in this example: $terms = wpse57444_filter_terms_by_cpt('flag',array('country','city'));? – user1443216 Jul 4 '12 at 19:59
You can't in that one, only in the new solution:wpse57444_get_terms() – Stephen Harris Jul 4 '12 at 20:21
@user1443216 $args is the second argument. There you just put wpse57444_get_terms( 'flag', array( 'country', 'city' ) ); – kaiser Jul 4 '12 at 20:21

[edit] This is a comment on the excellent answer by Stephen Harris.

It doesn't return any terms if used with multiple post types like this $flags = wpse57444_get_terms('flags', array('post_types' => array('country','city')));. This is because $wpdb->prepare sanitizes the $post_types_str string to p.post_type IN('country,city') while it should be p.post_type IN('country','city'). See this ticket: 11102. Use the solution from this topic to get around this: http://stackoverflow.com/a/10634225

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.