How To Exclude Draft Pages From Your Main Menu
Editorial Note We may earn a commission when you visit links from this website.

Have you ever tried to set a WordPress page to draft status, after you added it to the main menu? I did this recently and expected WordPress to be smart enough also to update the main menu to reflect that change.

However, when I viewed the webpage as a guest, I could still see the menu item. When I opened that page WordPress only displayed a 404 not-found message. This is technically correct; but wouldn’t it be much better when guest users would not even see the menu items for draft posts? In this article, I show you two ways that I found the easiest to fix the draft in the menu problem.

Table of Contents

The Complicated Way

Obviously, WordPress wants us to modify the post state and separately update the menu (remove the page from the menu manually). Possibly this is a good idea for caching – but on the other hand, most caching is usually disabled for logged-in users anyway.

A More Elegant Way

Or maybe this is just a lazy way.

I looked into the file nav-menu-template.php in search of a solution that does the redundant work and modifies the main menu. We hook into the filter wp_nav_menu_objects so we can remove inaccessible menu items right before they are displayed!

Actually, it’s a straightforward process, once we find the correct hook and understand the filter parameter:

add_filter( 'wp_nav_menu_objects', 'pst_nav_menu_objects', 10, 2 );

 * Modify the WordPress menu and remove entries that are not visible for the current
 * user. This applies to all menus (primary, footer, widget ...)
function pst_nav_menu_objects( $items, $args ) {
    // If you do not want to modify ALL menus, you can check for the menu-location 
    // or other criteria here.
    // For example, uncomment following condition to only modify the primary menu:
    # if ( 'primary' !== $args->theme_location ) {
    #     return $items;
    # }
    foreach ( $items as $key => $item ) {
        if ( ! in_array( $item->object, array( 'post', 'page' ) ) ) {
            // We only check visibility state for posts and pages.

        $post_status = get_post_status( $item->object_id );
        if ( 'publish' === $post_status ) {
            // This is a public page or post. No other check is needed.

        if ( is_user_logged_in() ) {
            if ( 'protected' === $post_status ) {
                if ( current_user_can( 'edit_post', $item->object_id ) ) {
                    // The current user is the author of the password protected post.
            } else {
                if ( current_user_can( 'read_post', $item->object_id ) ) {
                    // The current user can read the draft or trashed post.

        // All positive conditions failed.
        // Current visitor has no permission to see this menu entry.
        unset( $items[ $key ] );

    return $items;


Sometimes little problems like this might pop up while developing a client website. That is why I thought it would be a good idea to share the solution with you. I hope you can tick one more problem from your development list! 🙂

Try Divi Areas Pro today

Sounds interesting? Learn more about Divi Areas Pro and download your copy now!
Many pre-designed layouts. Automated triggers. No coding.

Click here for more details