In the tag archive – display count of posts for each category they belong to

I will try to be as explicit and short as possible. Suppose I have a site for books. I have three categories named adventure, romance and fantasy. Then, I have a tag called old books. In the top of this tag archive I need to display some counts information. Example :


There are a total of 88 old books available.
   18 books are from adventure category.
   50 books are from romance category.
   20 books are from fantasy category.

I solved the first one with $wp_query->found_posts; which gives me the count of total number of books in the tag-archive. Now how do I get the count for rest of the books. A solution or a road map would be great otherwise please tell me what functions or example should I be looking into.

I also expect the count to change dynamically as the changes occur. I will be posting my updates shortly as I am struggling for solution.

Solutions Collecting From Web of "In the tag archive – display count of posts for each category they belong to"

I remembered that once I had to do same thing and wrote a custom sql query… it was painful, as @Rarst said.

However I’ll post the function I wrote, and there isn’t much to explain, is a complex SQL query where there is the intersection of 2 checks to get all the term of a specific taxonomy that are associated in post having a specific term in another taxonomy:

function tax_mix_count( $term, $tax, $to_count, $post_type = '' ) {
  if ( ! did_action( 'wp_loaded' ) && current_filter() !== 'wp_loaded' ) {
    return new WP_Error('too-early', 'Call tax_mix_count() after wp_loaded');
  if ( ! taxonomy_exists( $tax ) || ! taxonomy_exists( $to_count ) ) {
    return new WP_Error('invalid-tax', 'Pass valid taxonomy names to tax_mix_count()');
  $by = is_numeric( $term ) ? 'term_id' : 'slug';
  $term_obj = get_term_by( $by, $term, $tax );
  if ( empty( $term_obj ) || is_wp_error( $term_obj ) ) {
    return new WP_Error( "invalid-term", "Pass valid {$tax} term to tax_mix_count()" );
  global $wpdb;
  $pt_where = '';
  if ( is_string( $post_type ) && post_type_exists( $post_type )  ) {
    $pt_where = $wpdb->prepare(' AND p.post_type = %s', $post_type );
  $query = "
    SELECT t.term_id FROM {$wpdb->term_taxonomy} tt
    INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
    INNER JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
    INNER JOIN {$wpdb->posts} p ON (p.ID = tr.object_id) 
    INNER JOIN {$wpdb->term_relationships} tr2 ON (p.ID = tr2.object_id)
    INNER JOIN {$wpdb->term_taxonomy} tt2 ON (tt2.term_taxonomy_id = tr2.term_taxonomy_id) 
    WHERE tt.taxonomy = %s
      AND p.post_status = 'publish'{$pt_where}
      AND tt2.taxonomy = %s AND tt2.term_id = %d";
  $c = $wpdb->get_col( $wpdb->prepare( $query, $to_count, $tax, $term_obj->term_id ) );
  if ( empty( $c ) ) return false;
  return array_count_values( $c );

As you can see it’s possible to use with different taxonomies and not only tags and categories.

  • The first argument, $term can be a term id or a term slug.
  • The second argument is the taxonomy the term belong.
  • The third argument is the taxonomy to count.
  • The fourth argument (optional) is the post type to limit the count.

Is possible do not to pass any post type as well if the two taxonomies are used by only one custom post type or if one want ot count all post types.

Note that if an invalid CPT is passed it is skipped without return any error.

The function also check if the hook ‘wp_loaded’ was already fired, because before that taxonomies and post types are not registered, so checks for taxonomies or post type existence will fail.

How to use:

$category_old_books = tax_mix_count( 'old', 'post_tag', 'category', 'books' );

if ( $category_old_books === FALSE ) {
  '<p>Posts in "old" tag are not associated with categories.</p>';
} elseif ( is_array( $category_old_books ) ) {
  foreach( $category_old_books as $id => $count ) {
    $term = get_term( $id, 'category' );
    $url = add_query_arg( array('tag'=>'old'), get_term_link($term,'category') );
       '%d book is from <a href="%s">%s</a> category',
       '%d books are from <a href="%s">%s</a> category.',
      $count, esc_url( $url ), esc_html( $term->name )
    echo '<br>';

Of course replace ‘your_textdomain’ with your real text domain 😉