Hiding WordPress custom post type menu items without disabling edit access

WordPress 3.0’s custom post types are really cool, opening up a whole new world of use cases for WordPress. We used custom post types extensively when developing Anthologize. But there are still some rough spots.

For instance, the ‘show_ui’ parameter of register_post_type() is a little bit too coarse-grained for our purposes. For Anthologize, we wanted to allow the user to edit custom post types with the standard Edit page, but we didn’t want users to be able to access most of these post types through the menu items automatically created by register_post_types (all links to the edit pages would appear on our custom Dashboard panel, in order to reduce redundancy and confusion). With ‘show_ui’ set to true, users could access the edit screens, but they could also access the unwanted menu items; with ‘show_ui’ set to false, the menu items were hidden, but navigating to the Edit pages (directly, via URL) threw a “You don’t have permission to access this page” error.

Here’s how we resolved the dilemma. Note that it’s a bit hackish at the moment. In the future, I hope the WordPress team will split ‘show_ui’ gets into multiple, separate arguments.

  1. In your register_post_type() call, set ‘show_ui’ to true. Here’s an example from Anthologize:
    	register_post_type( 'library_items', array(
    		'label' => __('Library Items', 'anthologize' ),
    		'public' => true,
    		'_builtin' => false,
    		'show_ui' => true,
    		'capability_type' => 'page',
    		'hierarchical' => true,
    		'supports' => array('title', 'editor', 'revisions'),
    		'rewrite' => array("slug" => "library_item")
  2. To remove the unwanted menu items, we’ll take advantage of the fact that WordPress has built-in support for custom menu order. First, we have to tell WordPress to expect a custom menu order. (The following two functions are modified from Anthologize, where they’re methods on a loader class.)
    	function toggle_custom_menu_order(){
    		return true;
    	add_filter( 'custom_menu_order', 'toggle_custom_menu_order' );
  3. Once custom_menu_order has been set to true (step 2), WordPress makes a new filter hook available, menu_order. As the name says, it’s really meant to reorder menu items, but we’ll use it to erase menu items altogether.
    	function remove_those_menu_items( $menu_order ){
    		global $menu;
    foreach ( $menu as $mkey => $m ) {
    			$key = array_search( 'edit.php?post_type=library_items', $m );
    if ( $key )
    				unset( $menu[$mkey] );
    return $menu_order;
    	add_filter( 'menu_order', 'remove_those_menu_items' ) );

    Here’s what’s happening. The filter hook is meant to modify $menu_order. That’s why remove_those_menu_item() takes $menu_order as an argument, and returns it back to WordPress untouched on the last line of the function. On the first line of the function, we’re taking advantage of the fact that the $menu variable – where menu items are stored for construction into markup later on – is in the global scope. Once we’ve declared that we’ll be using $menu on the first line, we loop through each of the menu items, and when we find one that matches our custom post type (ie, when we find one that contains the string ‘edit.php?post_type=library_items’ – you’ll have to replace the post_type with your own, obviously), it gets removed from the $menu global.

You can iterate this for as many different custom post types as you’d like – just add more potential keys to the foreach loop in remove_those_menu_items(), eg

	$key = array_search( 'edit.php?post_type=library_items', $m );
	$keyb = array_search( 'edit.php?post_type=some_other_post_type', $m );

if ( $key || $keyb )
		unset( $menu[$mkey] );

8 thoughts on “Hiding WordPress custom post type menu items without disabling edit access

  1. Steve Davies

    Boone, I was getting a white screen PHP error when using your code (above) – I’m using WP3.2. But it runs fine as follows;

    ` function toggle_custom_menu_order(){
    return true;
    add_filter( ‘custom_menu_order’, ‘toggle_custom_menu_order’ );

    function remove_those_menu_items( $menu_order ){
    global $menu;

    foreach ( $menu as $mkey => $m ) {
    $key = array_search( ‘edit.php?post_type=portfolio’, $m );
    $keyB = array_search( ‘edit.php?post_type=bio’, $m );
    $keyC = array_search( ‘edit.php?post_type=philo’, $m );

    if ( $key || $keyB || $keyC )
    unset( $menu[$mkey] );

    return $menu_order;
    add_filter( ‘menu_order’, ‘remove_those_menu_items’ );`

  2. Toure

    Hi there, I wonder if you anyone can help me with this menu?

    I would like to know if it would be possible to implement an option to “populate” each tag menu items of my WordPress site automatically with links to recent post as submenus. In other words, when visitors hover a tag in the menu, I would like my tag menu item to display the latest 3 posts which belong to that tag without me having to do it myself through the admin panel.

    So far after searching for help everywhere, I was able to get the following code bellow that actuality do it for a category, but again I wanna be able to use it on tag instead.

    // Front end only, don’t hack on the settings page
    if ( ! is_admin() ) {
    // Hook in early to modify the menu
    // This is before the CSS “selected” classes are calculated
    add_filter( ‘wp_get_nav_menu_items’, ‘display_lasts_three_posts_for_categories_menu_item’, 10, 3 );

    // Add the three last posts of a categroy menu item in a sub menu
    function display_lasts_three_posts_for_categories_menu_item( $items, $menu, $args ) {

    $menu_order = count($items); /* Offset menu order */
    $child_items = array();

    // Loop through the menu items looking category menu object
    foreach ( $items as $item ) {

    // Test if menu item is a categroy and has no sub-category
    if ( ‘category’ != $item->object || (‘category’ == $item->object && get_category_children($item->object_id)) )

    // Query the lasts three category posts
    $category_three_last_posts = array(
    ‘numberposts’ => 3,
    ‘cat’ => $item->object_id,
    ‘orderby’ => ‘date’,
    ‘order’ => ‘DESC’

    foreach ( get_posts( $category_three_last_posts ) as $post ) {
    // Add sub menu item
    $post->menu_item_parent = $item->ID;
    $post->post_type = ‘nav_menu_item’;
    $post->object = ‘custom’;
    $post->type = ‘custom’;
    $post->menu_order = ++$menu_order;
    $post->title = $post->post_title;
    $post->url = get_permalink( $post->ID );
    /* add children */
    $child_items[]= $post;
    return array_merge( $items, $child_items );
    This is so better than a plugin!

    By the way I would like to query “the post thumbnail” also.

    Thanks for looking.


Leave a Reply

Your email address will not be published. Required fields are marked *