Can I insert elements anywhere into the results of wp_nav_menu()?

The question:

I know that it’s easy to add something to the end of a navigation menu using code like the below:

function add_last_nav_item($items) {
  return $items .= '<li><a href="#myModal" role="button" data-toggle="modal">Contact</a></li>';
}
add_filter('wp_nav_menu_items','add_last_nav_item');

However, I need to do something that appears to be more complex.

I have 4 main menus each with child menu items. I need to add an entry to the bottom of the 3rd menu.

Is this possible in PHP having received a menu via wp_nav_menu() ?

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

If you have control over the the wp_nav_menu() call, you can implement a custom Walker_Nav_Menu class for fine-grained control over the rendering of an array of nav menu items to HTML.

If the markup of the items you wish to add is the same as that which the default Walker_Nav_menu already produces, you can use a 'wp_get_nav_menu_items' filter hook in order to add “virtual nav menu item” objects to the array before they are processed into HTML:

/**
 * Add a login/logout link to the nav menu in the `primary` location, as a child
 * of a menu item for a Page with the slug `members` (if present).
 *
 * @see https://wordpress.stackexchange.com/a/390893/25324
 *
 * @param WP_Post[] $items The WP_Post objects representing menu items in this menu.
 * @param WP_Term   $menu The WP_Term object representing this menu.
 * @param array     $args The arguments used to retrieve menu items associated with this menu.
 * @return array The filtered $items array.
 */
function wpse390890_insert_primary_nav_login_link( $items, $menu, $args ) {
  if( is_admin() )
    return $items;

  // Bail out if this menu isn't `primary` or `primary` is being loaded in the dashboard.
  if( $menu->slug !== 'primary' || is_admin() )
    return $items;
  
  // Find the ID of the Page with the slug `members` in the menu items.
  foreach( $items as $item ) {
    if( $item->post_type !== 'page' || $item->post_name !== 'members' )
      continue;
    
    $members_page_id = $item->ID;
    break;
  }

  // Bail if the `members` page is not in this menu.
  if( ! isset( $members_page_id ) )
    return $items;
  
  $logged_in  = is_user_logged_in();
  $link_title = __( $logged_in ? 'Log In' : 'Log Out', 'wpse390890' );
  
  // Insert a new WP_Post object for the link.
  $items[] = new WP_Post(
    (object) [
      'ID'               => 'user-login',
      'menu_item_parent' => $members_page_id,
      'classes'          => [ 'member-auth-link' ],
      'type'             => 'virtual',
      'object'           => 'virtual',
      'title'            => $link_title,
      'attr_title'       => $link_title,
      'url'              => $logged_in ? wp_logout_url() : wp_login_url(),
    ]
  );
  
  return $items;
}

add_filter( 'wp_get_nav_menu_items', 'wpse390890_insert_primary_nav_login_link', 10, 3 );

See wp_setup_nav_menu_item()‘s implementation for hints on which fields to specify, and how WP_Post objects are converted into objects representing nav menu items. var_dump()ing $items in the filter above is also a good way to explore the format and properties of the items which are already in the menu.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment