get_terms showing link to category even if all posts are drafts

i have this code below to show links for my custom taxonomy on my custom posts.
I want it to show only the categories that have published posts in them. This mostly works but if i put all the posts in a certain category to draft, it still shows up as a link, but when a user clicks the link it goes to a 404 page because there are no active links on it.
How can i make it just show the link for a category if there are PUBLISHED posts and not if there are only drafts or if the category has no posts?

//list terms in a given taxonomy 
$args = array( 'hide_empty=0' );
$terms = get_terms( 'product_category', $args );
if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
    $count = count( $terms );
    $i = 0;
    $term_list = '<div class="product-category-list">';
    foreach ( $terms as $term ) {
        $term_list .= '<a class="activeunderline" href="' . esc_url( get_term_link( $term ) ) . '" alt="' . esc_attr( sprintf( __( 'View all post filed under %s', 'my_localization_domain' ), $term->name ) ) . '">' . $term->name . '</a>';
        if ( $count != $i ) {
            $term_list .= ' &middot; ';
        else {
            $term_list .= '</div>';
    echo $term_list;

Solutions Collecting From Web of "get_terms showing link to category even if all posts are drafts"

get_terms() doesn’t have built-in feature that excludes draft posts because it keeps track of only total posts term is attached to. I made a quick search and found this snippet but be warned:

  • It affects all get_terms() functions on your site (I excluded admin area)
  • There’s a SQL query in foreach loop – it will affect performance
  • More terms returned == bigger performance hit
  • I do not recommend testing it on live website
  • You might get away with it if your traffic is not super high

This is probably the reason why there’s no native support for that – it’s either query in loop or WordPress would need to keep track of both drafts and public posts count which is not perfect either.


This is a very hacky solution and I wouldn’t use it myself. It might also require few modifications.

If you’re willing to try it, add this to functions.php:

// Make sure that we're not in admin area
if( ! is_admin() ) {

    add_filter( 'get_terms', 'hide_draft_terms' );

    function hide_draft_terms( $terms, $taxonomies, $args ) {

        global $wpdb;
        $taxonomy = $taxonomies[0];

        if( ! is_array( $terms ) && count( $terms ) < 1 )
            return $terms;

        $filtered_terms = array();

        foreach ( $terms as $term ) {

            $result = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts p JOIN $wpdb->term_relationships rl ON p.ID = rl.object_id WHERE rl.term_taxonomy_id = $term->term_id AND p.post_status = 'publish' LIMIT 1" );

            if ( intval( $result ) > 0 ) {

                $filtered_terms[] = $term;

        return $filtered_terms;