With the help of the wp_get_nav_menu_items filter, you can easily add dynamic links to a WordPress menu without having to concatenate html strings, or write a custom nav walker.

This example shows how to use the filter and construct a pseudo menu-item object (to meet the expectations of a nav walker class) to add a dynamic “My Profile” link to an existing menu.

The first thing we need is a simple helper function that creates a menu item object according to WordPress’ expectations.

/**
 * Simple helper function for make menu item objects
 * 
 * @param $title      - menu item title
 * @param $url        - menu item url
 * @param $order      - where the item should appear in the menu
 * @param int $parent - the item's parent item
 * @return \stdClass
 */ 
function _custom_nav_menu_item( $title, $url, $order, $parent = 0 ){
  $item = new stdClass();
  $item->ID = 1000000 + $order + parent;
  $item->db_id = $item->ID;
  $item->title = $title;
  $item->url = $url;
  $item->menu_order = $order;
  $item->menu_item_parent = $parent;
  $item->type = '';
  $item->object = '';
  $item->object_id = '';
  $item->classes = array();
  $item->target = '';
  $item->attr_title = '';
  $item->description = '';
  $item->xfn = '';
  $item->status = '';
  return $item;
}

To use this helper function, you need to take advantage of the wp_get_nav_menu_items filter so we can add menu items as desired:

add_filter( 'wp_get_nav_menu_items', 'custom_nav_menu_items', 20, 2 );

function custom_nav_menu_items( $items, $menu ){
  // only add item to a specific menu
  if ( $menu->slug == 'menu-1' ){
    
    // only add profile link if user is logged in
    if ( get_current_user_id() ){
      $items[] = _custom_nav_menu_item( 'My Profile', get_author_posts_url( get_current_user_id() ), 3 ); 
    }
  }
    
  return $items;
}

That’s it! To create a hierarchy (dropdown) of menu items, you could write your hook like this:

add_filter( 'wp_get_nav_menu_items', 'custom_nav_menu_items2', 20, 2 );

function custom_nav_menu_items2( $items, $menu ) {
  if ( $menu->slug == 'menu-1' ) {
    $top = _custom_nav_menu_item( 'Top level', '/some-url', 100 );

    $items[] = $top;
    $items[] = _custom_nav_menu_item( 'First Child', '/some-url', 101, $top->ID );
    $items[] = _custom_nav_menu_item( 'Third Child', '/some-url', 103, $top->ID );
    $items[] = _custom_nav_menu_item( 'Second Child', '/some-url', 102, $top->ID );
  }

  return $items;
}

Be careful. It’s important that no menu items share the same menu_order property.

Finally, I’ve created a class and example of its usage as a gist: WordPress class to add custom menu items dynamically.

About the Author

Jonathan Daggerhart

Long time Drupal and WordPress developer. I like to write modules and plugins, and I dabble in frontend and design.

10 thoughts on “Dynamically add items to WordPress menus

  • Janos Ver

    Thanks for this article. I have found it very useful. The only thing I’d like to add is that I discovered it today that once I added the above code to my site to replace a menu item based on user action broke the Appearance-> Customize menu and I got an error (Exception) saying some properties are missing. By looking at the object definition at https://developer.wordpress.org/reference/functions/wp_setup_nav_menu_item/ and the error message itself I figured out that the following were missing from _custom_nav_menu_item function (header and return just added to provide some context):

    function _custom_nav_menu_item( $title, $url, $order, $parent = 0 ){

    $item->target = ”;
    $item->attr_title = ”;
    $item->description = ”;
    $item->xfn = ”;
    $item->status = ”;


    return $item;
    }

    I hope it saves some time and headache for others looking to use this filter.

  • Dave Spencer

    I too found this useful. Care to expand it to so as to be able to create a menu with submenus? I would like to be able to inject an item in to the menu bar that has a dropdown with submenus below it. Just a thought – it would make this the ultimate guide!

    • Jonathan Daggerhart

      Hi Dave,

      I’ve updated the post to show how to create submenu items. Note: I had to make a small update to the “_custom_nav_menu_item” function (db_id) for this to work.

      Let me know how it goes!

  • alan

    Thanks for this snippet, just an small issue in your helper

    $item->menu_parent_item = $parent;

    should be

    $item->menu_item_parent = $parent;

  • Cédric

    Thank you very much for your article.
    I create some pages dynamically using a PhP file. Adding your code at the beginning of the file, the page is correctly inserted in the menu but it’s not highlighted in menu and breadcrumb is not correct?
    Any idea ?

  • Litbea

    Jonathan, thanks a lot for this information.

    I’m using your article to try and SUCCESSFULLY create sub-menu items with this changes and specifying an already created parent item:

    function _custom_nav_menu_item( $title, $url, $order, $parent = 0 ){
    $item = new stdClass();
    $item->ID = 1000000 + $order + parent;
    $item->db_id = $item->ID;
    $item->title = $title;
    $item->url = $url;
    $item->menu_order = $order;
    $item->menu_item_parent = PARENT_MENU_ITEM_ID;//$parent;
    $item->type = ”;
    $item->object = ”;
    $item->object_id = ”;
    $item->classes = array();
    $item->target = ”;
    $item->attr_title = ”;
    $item->description = ”;
    $item->xfn = ”;
    $item->status = ”;
    return $item;
    }

    add_filter( ‘wp_get_nav_menu_items’, ‘custom_nav_menu_items2’, 20, 2 );

    function custom_nav_menu_items2( $items, $menu ) {
    if ( $menu->slug == ‘menu-1’ ) {
    //$top = _custom_nav_menu_item( ‘Top level’, ‘/some-url’, 100 );

    //$items[] = $top;
    //$items[] = _custom_nav_menu_item( ‘First Child’, ‘/some-url’, 101, $top->ID );
    //$items[] = _custom_nav_menu_item( ‘Third Child’, ‘/some-url’, 103, $top->ID );
    //$items[] = _custom_nav_menu_item( ‘Second Child’, ‘/some-url’, 102, $top->ID );
    // the query
    $wpb_all_query = new WP_Query(array(‘post_type’=>’custom_post_type_name’, ‘post_status’=>’publish’, ‘posts_per_page’=>-1, ‘orderby’ => ‘title’, ‘order’ => ‘ASC’));
    if ( $wpb_all_query->have_posts() ) :
    $subposition = 201;
    while ( $wpb_all_query->have_posts() ) : $wpb_all_query->the_post();
    // get the custom post data
    $title = get_the_title();
    $slug = basename(get_permalink());
    // create the menu sub item
    $items[] = _custom_nav_menu_item( $title, ‘/’.$slug, $subposition++, $top->ID );
    endwhile;
    wp_reset_postdata();
    else :
    echo ”. _e( ‘Sorry, no posts matched your criteria.’ ).”;
    endif;
    }

    return $items;
    }

    Working great!

  • Litbea

    Hi again, the only problem I’ve found is that a “The given object ID is not that of a menu item.” error shows up when saving the menu from /wp-admin/nav-menus.php

    Any suggestion?

  • Pingback: Populating main menu from external JSON API

Leave a Reply

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