In the register_post_status, I've already disabled show_in_admin_all_list & show_in_admin_status_list for my custom status my_hidden_status

However, from the query log the post_status my_hidden_status is still not being filtered out (when loading edit.php)


SELECT post_status, COUNT( * ) AS num_posts FROM st_posts WHERE
post_type = 'my_cpt' GROUP BY post_status;

The post_status I wanted to filter is actually over 90% of my CPT, so if the query is rewritten as

SELECT post_status, COUNT( * ) AS num_posts FROM st_posts WHERE
post_type = 'my_cpt' AND post_status != 'my_hidden_status' GROUP BY

It can greatly improve performance.

Is this a bug?


3 Answers 3


TL;DR: It's not a bug (as we generally understand it), rather it's a feature that was never fully implemented in WordPress.

Status of register_post_status()

register_post_status() function was never fully implemented in WordPress. If you check WordPress Codex entry for register_post_status() function, you'll see it's clearly mentioned in a notice:


This function does NOT add the registered post status to the admin panel. This functionality is pending future development. Please refer to Trac Ticket #12706 . Consider the action hook post_submitbox_misc_actions for adding this parameter.

Also, if you visit the Related Ticket, you'll see that the discussion to implement it fully is going on for over 8 years now (starting from March 2010)! However, since WordPress is open source community driven software & there weren't a lot of volunteers willing to work on this feature, it's still pending proper implementation.

The Trac Ticket #12706 states the following in the description:

A developer should be able to register a custom post status using register_post_status(). The admin UI (including post submit box and quick edit) should reflect this new custom post status. Furthermore, there are many hard-coded references to 'draft' and 'pending' statuses in core that should properly use the post status API.

All existing arguments to register_post_status() should be fully implemented, should also support per-post-type arguments. As things get implemented across core, there will likely be a need for supporting capabilities and bits of API.

Now if you look at the WordPress core CODE, you'll see that this description is still valid. That means:

  • The admin UI (including post submit box and quick edit) doesn't reflect custom post status. Check this part of the CODE in core:

    <?php _e( 'Status:' ); ?> <span id="post-status-display">
        switch ( $post->post_status ) {
            case 'private':
                _e( 'Privately Published' );
            case 'publish':
                _e( 'Published' );
            case 'future':
                _e( 'Scheduled' );
            case 'pending':
                _e( 'Pending Review' );
            case 'draft':
            case 'auto-draft':
                _e( 'Draft' );

    The CODE contains no action / filter hook for any custom post status or for any modification whatsoever. So the only logical way at the moment is to use some JavaScript to alter the UI implementation until this feature is implemented in WordPress core.

  • All existing arguments to register_post_status() is not yet fully implemented & hence may look buggy.

  • And a lot more discussion & work is needed for the full implementation of the Post Status API.

What to do about this Lack of Feature or Bug?

Call it a lack of feature or call it a bug, the fact remains: there's a lot more work that needs to be done to complete the Post Status API. So if this is too important for you, it's better to join in the discussion in the Support Ticket mentioned above to expedite the development process.

Although the entire development process will need a lot of voluntary work, you can still comment on it about the argument show_in_admin_all_list not affecting the query, so that at least this part of the anomaly can be improved earlier than the rest of the Status API.

What to do until core doesn't implement the features properly?

As it is now, show_in_admin_all_list argument only affects the All(*) status header part of the All Posts Listing, for example:

All Posts Listing Status Header (All Posts Selected)
Image-1: All Posts Listing Status Header (All Posts Selected)

However, it doesn't affect the posts that appear in the list (although it should).

To make sure that posts with your custom status my_hidden_status doesn't appear in the list as well, you need to set the public argument to false.

This will make the register_post_status() function call look like the following:

register_post_status( 'my_hidden_status', array(
    'label'                     => _x( 'My Hidden Status', 'post' ),
    'public'                    => false,
    'show_in_admin_all_list'    => false, 
    'show_in_admin_status_list' => false,
    'label_count'               => _n_noop( 'My Hidden Status <span class="count">(%s)</span>', 'My Hidden Status <span class="count">(%s)</span>' )
) );

This will show the correct count in All(*) status header and also remove the posts with my_hidden_status from the All Posts or All {custom_post_type} listing.

Of course, this will essentially make posts with my_hidden_status inaccessible from WordPress Admin Panel. If that was the intent, then fine, otherwise, you may use the show_in_admin_status_list to show the posts with my_hidden_status in a separate listing header:

register_post_status( 'my_hidden_status', array(
    'label'                     => _x( 'My Hidden Status', 'post' ),
    'public'                    => false,
    'show_in_admin_all_list'    => false, 
    'show_in_admin_status_list' => true,
    'label_count'               => _n_noop( 'My Hidden Status <span class="count">(%s)</span>', 'My Hidden Status <span class="count">(%s)</span>' )
) );

This will show the Post Lists with the status my_hidden_status in a separate header like the following:

All Posts Listing Status Header (Custom Status Selected)
Image-2: All Posts Listing Status Header (Custom Status Selected)

DB Query Optimization Issue:

The related DB Query comes from wp_count_posts() function in wp-includes/post.php file. If you take a look at the related CODE, you'll see that the arguments from register_post_status() function are not implemented there. Also, there's no way to filter or extend this part of the core CODE either:

$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
if ( 'readable' == $perm && is_user_logged_in() ) {
    $post_type_object = get_post_type_object( $type );
    if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
        $query .= $wpdb->prepare(
            " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
$query .= ' GROUP BY post_status';

Unless both show_in_admin_all_list and show_in_admin_status_list are set to false for a particular status, related posts shouldn't be removed from the query anyway. Otherwise WordPress will not be able to generate the proper status header (as shown in image-2).

However, in the case where both show_in_admin_all_list and show_in_admin_status_list are set to false, WordPress doesn't need the count information for that custom status. So logically in this case you'll have a little query performance improvement with AND post_status != 'my_hidden_status' part, if you have a large number of posts (like Millions) and most of your posts (as you said 90%) are from that custom status.

Still, there are two reasons why core may not update this part of the CODE even though there may be a little performance gain in your particular case:

  1. WordPress Database uses an index named type_status_date that indexes wp-posts table entries based on post_type, post_status, post_date and ID fields. So queries are fast enough even without the AND post_status != 'my_hidden_status' part.

    I've tested with 15K+ posts and it only takes a few milliseconds. Check this MySQL Document for more information on GROUP BY Optimization with index. With some tweaks to MySQL index, you may even get better results without even changing WP Core. For example, adding another index with post_type and post_status column may give you a few milliseconds gain.

  2. Even though adding AND post_status != 'my_hidden_status' may make the query faster (merely by milliseconds) in your particular case, WordPress still may not update it, because WordPress will have to consider the possibility where a hidden custom status has only a few posts. In those cases, adding AND post_status != 'my_hidden_status' will actually make the query results a bit slower (only by a few milliseconds).

So considering everything, WordPress may or may not update the query with AND post_status != 'my_hidden_status' when both show_in_admin_all_list and show_in_admin_status_list are set to false for a custom status. However, in my opinion, at least making the related query filterable will improve the flexibility for the developers.


Is it a bug? It's doing a little more work than it has to, but it's not resulting in any unexpected output, and it avoids potential problems with other site setups. So I would say no (mostly).

The query in question serves to count the number of posts found before displaying them on the edit screen. It counts all posts by status so that it has these numbers handy if the page needs them later.

My hunch is that the query was kept simple on purpose to prevent the possibility of multiple little-used post statuses clogging and slowing down the query. For example, testing on phpMyAdmin, the base query took 0.0011 seconds with around 300 posts. Adding clauses to filter out 5 unused or little-used post statuses raised the time to 0.0024.

In your situation, you're correct that filtering out 90% of potential results would speed up the query. You could try filtering it with the query filter.

As a side note: while researching this I noticed that if "public" => true in register_post_status, it overrides the show_in_admin_all_list value and displays posts with that status on the edit screen. I'd call that part a bug, since in other register_* functions the more specific args tend to override the public arg.


The query you are looking to filter is actually called by wp_count_posts function. Only way to filter this query is by using query filter (found inside wpdb class query method).

As the filter query is used for all of the mysql queries called with $wpdb instance, it would be better to register your filter from the exact place you need it. For that, add your filter on load-edit.php action.

/* register the filter from edit.php page (works for all post types) */
add_action('load-edit.php', 'wpse_initiate_filter');
function wpse_initiate_filter($query) {
    add_filter('query', 'wpse_filter_posts_count_query');

/* modify original query to filter out my_hidden_status */
function wpse_filter_posts_count_query($query) {
    if (strpos($query, 'SELECT post_status') === 0) {
        $query = str_replace(" GROUP BY", " AND post_status != 'my_hidden_status' GROUP BY", $query);
    return $query;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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