What I have: A custom post type. A front end posting form which creates new posts with post_status = pending until they are manually reviewed and status changed to published.

What I need: While I can go to the listing of posts and see if there are any with "Pending" to authorize; what I want is to add a round icon with a number inside just like what WordPress does when there are new comments. I can write the code to extract the number of pending posts but I have no idea how to insert it in the admin menu.

This is the code created by wp when a new comment is added:

<span class="awaiting-mod count-6"><span class="pending-count">1</span></span>

A screenshot which shows one new comment, i need the same at Items.

I have been looking for a solution to this for a while now and cannot find it anywhere; my next stop if no one can answer me here is to take apart the WP core and see how they do it there.

  • @brasofilo How much unneeded work for me! :) My vote for duplicate an +1 on your answer.
    – gmazzap
    Commented Sep 7, 2013 at 7:09

1 Answer 1


Normally everything is contained in $labels['menu_name'] argument on cpt registration is printed in the menu, so the right place where put the count span is there, but there are 2 problems:

  1. You have to dinamically count posts, so you can't just write there the html
  2. Even if you insert the html in the $labels['menu_name'] option or registration, the span is stripped out, because before printing the menu the value of the param is wrapped in esc_attr

The second 'problem', is not a real problem, it's the solution!

In fact, the esc_attr function fires a filter hook attribute_escape, that we can use to calculate the pending posts count and then output the right html unescaped.

We we must be very careful if we remove the sanitize functionality from esc_attr because this is a function used a lot times in WP and is a function related to secutirity. So, we have to isolate the behaviour only to admin menu and only in specific case.

Moreover we can add filter again when it has done its work, in this way it's removed only once.

The only problem now is how to understand which is the right menu item for which remove the filter and add the count.

The only chance we have, it's insert a sort of placeholder that let us recognize the menu item. In other words, when you register your cpt you have to add an uncommon string to $labels['menu_name']:

$labels = array(
  /** This do the magic ;) ! */
  'menu_name' => _x('My CPT %%PENDING_COUNT%%', 'My CPT post type label menu_name', 'mytextdomain'),

  'name' => _x('My CPT','My CPT label name','mytextdomain'),
  'singular_name' => _x('My CPT','My CPT label singular_name','mytextdomain'),
  'all_items' => _x('All My CPT','My CPT label all_items','mytextdomain'),
  'add_new' => _x('Add New','My CPT label add_new','mytextdomain'),
  'add_new_item' => _x('Add New My CPT','My CPT label add_new_item','mytextdomain'),
  'edit_item' => _x('Edit My CPT','My CPT label edit_item','mytextdomain'),
  'new_item' => _x('New My CPT','My CPT label new_item','mytextdomain'),
  'view_item' => _x('View My CPT','My CPTlabel view_item','mytextdomain'),
  'search_items' => _x('Search My CPT','My CPT label search_items','mytextdomain'),
  'not_found' => _x('My CPT not found','My CPT label not_found','mytextdomain'),
  'not_found_in_trash' => _x('My CPT not found in trash','My CPT label not_found_in_trash','mytextdomain'),
  'parent_item_colon' => _x('Parent:','My CPT label parent_item_colon','mytextdomain')
$args = array(
  'labels' => $labels,
  ... other args here
register_post_type( 'my_cpt', $args );

Once the %%PENDING_COUNT%% is added, when the esc_attr is called for a string containing that text, we run our work:

add_action('auth_redirect', 'add_pending_count_filter'); // modify esc_attr on auth_redirect
add_action('admin_menu', 'esc_attr_restore'); // restore on admin_menu (very soon)

function add_pending_count_filter() {
  add_filter('attribute_escape', 'remove_esc_attr_and_count', 20, 2);

function esc_attr_restore() {
  remove_filter('attribute_escape', 'remove_esc_attr_and_count', 20, 2);

function remove_esc_attr_and_count( $safe_text = '', $text = '' ) {
  if ( substr_count($text, '%%PENDING_COUNT%%') ) {
    $text = trim( str_replace('%%PENDING_COUNT%%', '', $text) );
    // run only once!
    remove_filter('attribute_escape', 'remove_esc_attr_and_count', 20, 2);
    $safe_text = esc_attr($text);
    // remember to set the right cpt name below
    $count = (int)wp_count_posts( 'my_cpt',  'readable' )->pending;
    if ( $count > 0 ) {
      // we have pending, add the count
      $text = esc_attr($text) . '<span class="awaiting-mod count-' . $count . '"><span class="pending-count">' . $count . '</span></span>';
      return $text;
  return $safe_text;

That's all, and it works!

enter image description here

There is only a little issue: if a pending post is published via the quick edit the pending count is not updated, because quick edit use ajax, and to update the numebr you need to use some js. Work on it, if you want.

  • Thanks everyone; i did search for it but it and quite extensively i may add but the wording used in that post is quite different; lets hope that by having two posts more people are able to find the solution should they require it. Commented Sep 7, 2013 at 12:10
  • @Ronnieinspain, having duplicate content is useful for this very reason. Nice write-up, G.M., upvoted at first sight ;)
    – brasofilo
    Commented Sep 7, 2013 at 12:23
  • @G. M. Your solution works perfectly, i have just implemented it and i believe there is just one thing missing to make your solution complete and i would appreciate your help in achieving this. The %%PENDING_COUNT%% is now correctly filtered out of the Top Menu Item and then replaced by the count of pending posts BUT, once clicked upon, the sub pages are shown and here it still shows "My CPT %%PENDING_COUNT%%"; how could i filter that out so it just shows "My CPT" ? Thanks Commented Sep 7, 2013 at 17:10
  • @Ronnieinspain to which label have you added %%PENDING_COUNT%%? It should be only to label menu_name. I've done so, and I don't see %%PENDING_COUNT%% anywhere... If you have added only to label menu_name, please take a screenshot and let me see, just to better understand...
    – gmazzap
    Commented Sep 7, 2013 at 17:19
  • Hi, thanks for offering to assist. As you requested, here are two screenshots, one of the register post type function and one of how it looks in the dashboard. In your screenshots i can see this didnt happen, i have followed your code to the letter and not made any additional changes. valenciafreewifi.com/wp-content/themes/directory/design/… valenciafreewifi.com/wp-content/themes/directory/design/… Commented Sep 7, 2013 at 18:24

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