I tried looking for info about how to exclude/remove nav menu items from custom menus, and the only thread I found did not have any answers that were useful to me.

1. Background:

I put together a Dock menu using WP custom menus (wp_nav_menu) and jqDock on my site. Since jqDock needs continuous images or image links to work its magic, I' using a custom walker so the nav menu HTML output looks smth like this:

<div id="menu-first" class="nav">
<a><img src="http://path/to/image-1.png"/></a>
<a><img src="http://path/to/image-2.png"/></a>
<a><img src="http://path/to/image-3.png"/></a>

The code for my custom walker is:

class custom_nav_walker extends Walker_Nav_Menu 
    var $tree_type = array( 'post_type', 'taxonomy', 'custom' );
    var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );

    function start_lvl(&$output, $depth) {
        $indent = str_repeat("\t", $depth);
        $output .= "\n$indent<ul class=\"sub-menu\">\n";

    function end_lvl(&$output, $depth) {
        $indent = str_repeat("\t", $depth);
        $output .= "$indent</ul>\n";

    function start_el(&$output, $item, $depth, $args) {
        global $wp_query;
        $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

        $class_names = $value = '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
        $class_names = ' class="' . esc_attr( $class_names ) . '"';

        $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
        $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

        //$output .= $indent . '<li' . $id . $value . $class_names .'>';

        $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
        $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
        $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
        $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';

        $description  = ! empty( $item->description ) ? esc_attr( strtolower( $item->description )) : '';
        $item_title   = ! empty( $item->attr_title )  ? esc_attr( $item->attr_title ) : '';

        if ( strpos($description, ';') !== false ) {
        $description_array = explode (';', $description);
            $image_name = $description_array[0];
            $image_alt = $description_array[1];
        } else {
            $image_name = $description;
            $image_alt = $item_title;

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before .'<img src="'.get_bloginfo('template_url').'/images/skin1/'.$image_name.'" alt="'.$image_alt.'" title="'.$item_title.'" />'.$args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

    function end_el(&$output, $item, $depth) {
        $output .= "";


The jqDock script then catches the menu ID ('menu-first') and replaces the wp_nav_menu output with the Dock menu's. The Dock menu's HTML output changes based on the options specified when loading jqDock.

2. The Question:

I would like to not display (i.e., exclude) certain menu items according to where the user is on the site. For example, I would like to only show the Home item when the user is not in the Home, and the Random post item only when he is.

3. Discarded solutions:

a. Muliple menus: Registering and creating multiple menus and then calling them conditionally could work; however, I don't think this is an ideal nor a clean solution at all for many reasons. Also, multiple menus are not easy to maintain or update.

b. Regex Search and Replace: This might force me to change the needle parameter every time I change the jqDock options because the HTML output is modified.

c. CSS 'display' property: Hiding the items through the CSS display property works, but since it has to be applied to the jqDock menu output, it affects the visual rendering of the menu.

4. Failed Solutions:

a. Filter to wp_nav_menu_items: I tried catching the '$items' variable (string) and assigning it different values through conditional tags with the following code:

function userf_dynamic_nav_menu ($items) {
    $items_array_home = explode('<a', $items);
    $items_array_nothome = $items_array_home;


    $items_home = implode('<a', $items_array_home);
    $items_nothome = implode('<a', $items_array_nothome);

    if ( is_home() ) {
        $items = $items_home;
    } else {
        $items = $items_nothome;
    return $items;
add_filter('wp_nav_menu_first_items', 'userf_dynamic_nav_menu');

This works only partially, because the menu items do change, but the conditional tags are ignored. I guess this makes sense because of the moment in which the filter is applied.

b. Custom nav menu function: I tried creating my own custom nav menu function to be able to add an exclude argument to the $defaults array and to use this slightly modified code from wp_list_pages to populate the additional argument:

$exclude_array = ( $args->exclude ) ? explode(',', $args->exclude) : array();
$args->exclude = implode( ',', apply_filters('wp_nav_menu_excludes', $exclude_array) );

Any ideas?

  • Can you show us your custom walker child class?
    – soulseekah
    Commented Oct 21, 2011 at 17:42
  • Hi Souleseekah, I just added it to my original post. Thanks!
    – Marventus
    Commented Oct 21, 2011 at 17:52
  • I thought of passing an exclude argument too, but, contrary to wp_list_pages and many other WP functions, wp_nav_menu does not include one. So even if I do specify one when calling the menu or in the walker, it wouldn't get picked up inside wp_nav_menu, would it?
    – Marventus
    Commented Oct 21, 2011 at 18:08
  • Sorry, wasn't thinking straight when I wrote that, deleted immediately.
    – soulseekah
    Commented Oct 21, 2011 at 18:11
  • Don't worry about it!
    – Marventus
    Commented Oct 21, 2011 at 18:16

2 Answers 2


Method 1

You can add a constructor to your custom Walker to store some additional exclusion arguments, like:

class custom_nav_walker extends Walker_Nav_Menu {
    function __construct( $exclude = null ) {
        $this->exclude = $exclude;

    function skip( $item ) {
        return in_array($item->ID, (array)$this->exclude);
        // or
        return in_array($item->title, (array)$this->exclude);
        // etc.

    // ...inside start_el, end_el
    if ( $this->skip( $item ) ) return;

Or drop the constructor and set its $exclude property before passing it in as a walker to wp_nav_menu() like so:

$my_custom_nav_walker = new custom_nav_walker;
$my_custom_nav_walker->exclude = array( ... );

Depending on what you're excluding by, supply the correct form to the exclude.

Method 2

This is how you would go about doing this by hooking into the wp_get_nav_menu_items filter.

function wpse31748_exclude_menu_items( $items, $menu, $args ) {
    // Iterate over the items to search and destroy
    foreach ( $items as $key => $item ) {
        if ( $item->object_id == 168 ) unset( $items[$key] );

    return $items;

add_filter( 'wp_get_nav_menu_items', 'wpse31748_exclude_menu_items', null, 3 );

Note: object_id is the object the menu points to, while ID is the menu ID, these are different.

Let me know your thoughts.

  • Thanks! That might work. I'll give it a try and let you know.
    – Marventus
    Commented Oct 21, 2011 at 18:14
  • I tried the constuctor approach and, no matter what I try, I keep getting a "Wrong datatype for second argument" error for the in_array function. Am I doing something wrong?
    – Marventus
    Commented Oct 21, 2011 at 18:41
  • The $exclude property must be an array. So make sure you're passing an array into the constructor, or watch the updated code in my answer. Specifically the typecast for $this->exclude, just in case an array is not passed.
    – soulseekah
    Commented Oct 21, 2011 at 18:43
  • Sorry about that: I had a typo in my function. I just tried $exclude = array ('4', '7'); and using the slugs too, but it is not having any effects on the walker output. I'll try the second approach and let you know.
    – Marventus
    Commented Oct 21, 2011 at 23:20
  • Nope, that didn't work either. I think I'm brain dead from trying to figure this out, so that might be affecting my... "performance", :-)
    – Marventus
    Commented Oct 21, 2011 at 23:40

does this help

$exclude_array = ( $args->exclude ) ? explode(',', $args->exclude) : array();
$args->exclude = implode( ',', apply_filters('wp_nav_menu_excludes', $exclude_array) );

as an example

< ?php wp_nav_menu( array( 'container_class' => 'menu-header', 'theme_location' => 'primary', 'exclude' => '66' ) ); ?>
  • Hi Saq, I forgot to mention that one of the solutions that did not work was to create a custom nav_menu function and add that code as an additional argument to the function's defaults. Sadly, it did not work. I didn't try to include it in the walker, but I don't think that would work either for the same reason I mentioned above, mainly that the wp_nav_menu does not have an "exclude" argument, but I could be wrong.
    – Marventus
    Commented Oct 22, 2011 at 1:35
  • I updated my original post to include this for clarity.
    – Marventus
    Commented Oct 22, 2011 at 2:05
  • what if you dont use a custom walker, instead you use a regular nav_menu and extract the items with wp_get_nav_menu_items() with your custom image
    – saq
    Commented Oct 22, 2011 at 3:45
  • That would be a good workaround in general, but in this particular case, wp_get_nav_menu_items won't retrieve the images because the img tags are not stored in the actual custom menu (only their filenames are in the description field, e.g., "image1.png"). The custom walker is what allows me to insert the img tags in the menu output.
    – Marventus
    Commented Oct 22, 2011 at 11:53

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.