51

Through API functions, I want to define a new Navigation menu, select it in the current theme, and then insert a few Pages as menu items. This is to be done for instance on a theme activation.

Through a (moderately painful) process of reverse engineering the database inserts and updates after manually setting up the Navigation menu and items, I've pieced together the following steps, where 'footer-nav' is the slug ID of the Navigation menu I'm creating:

if (!term_exists('footer-nav', 'nav_menu')) {

    $menu = wp_insert_term('Footer nav', 'nav_menu', array('slug' => 'footer-nav'));

    // Select this menu in the current theme
    update_option('theme_mods_'.get_current_theme(), array("nav_menu_locations" => array("primary" => $menu['term_id'])));

    // Insert new page
    $page = wp_insert_post(array('post_title' => 'Blog',
                                 'post_content' => '',
                                 'post_status' => 'publish',
                                 'post_type' => 'page'));

    // Insert new nav_menu_item
    $nav_item = wp_insert_post(array('post_title' => 'News',
                                     'post_content' => '',
                                     'post_status' => 'publish',
                                     'post_type' => 'nav_menu_item'));


    add_post_meta($nav_item, '_menu_item_type', 'post_type');
    add_post_meta($nav_item, '_menu_item_menu_item_parent', '0');
    add_post_meta($nav_item, '_menu_item_object_id', $page);
    add_post_meta($nav_item, '_menu_item_object', 'page');
    add_post_meta($nav_item, '_menu_item_target', '');
    add_post_meta($nav_item, '_menu_item_classes', 'a:1:{i:0;s:0:"";}');
    add_post_meta($nav_item, '_menu_item_xfn', '');
    add_post_meta($nav_item, '_menu_item_url', '');

    wp_set_object_terms($nav_item, 'footer-nav', 'nav_menu');
}

This seems to work, but:

  • is it a robust and elegant way of doing it?
  • am I missing something totally obvious that would do all this in one line of code?

4 Answers 4

52

I might be misunderstanding you, but why not use wp_create_nav_menu()?

E.g., this is what I do to create a custom BuddyPress menu when I detect BP as active:

    $menuname = $lblg_themename . ' BuddyPress Menu';
$bpmenulocation = 'lblgbpmenu';
// Does the menu exist already?
$menu_exists = wp_get_nav_menu_object( $menuname );

// If it doesn't exist, let's create it.
if( !$menu_exists){
    $menu_id = wp_create_nav_menu($menuname);

    // Set up default BuddyPress links and add them to the menu.
    wp_update_nav_menu_item($menu_id, 0, array(
        'menu-item-title' =>  __('Home'),
        'menu-item-classes' => 'home',
        'menu-item-url' => home_url( '/' ), 
        'menu-item-status' => 'publish'));

    wp_update_nav_menu_item($menu_id, 0, array(
        'menu-item-title' =>  __('Activity'),
        'menu-item-classes' => 'activity',
        'menu-item-url' => home_url( '/activity/' ), 
        'menu-item-status' => 'publish'));

    wp_update_nav_menu_item($menu_id, 0, array(
        'menu-item-title' =>  __('Members'),
        'menu-item-classes' => 'members',
        'menu-item-url' => home_url( '/members/' ), 
        'menu-item-status' => 'publish'));

    wp_update_nav_menu_item($menu_id, 0, array(
        'menu-item-title' =>  __('Groups'),
        'menu-item-classes' => 'groups',
        'menu-item-url' => home_url( '/groups/' ), 
        'menu-item-status' => 'publish'));

    wp_update_nav_menu_item($menu_id, 0, array(
        'menu-item-title' =>  __('Forums'),
        'menu-item-classes' => 'forums',
        'menu-item-url' => home_url( '/forums/' ), 
        'menu-item-status' => 'publish'));

    // Grab the theme locations and assign our newly-created menu
    // to the BuddyPress menu location.
    if( !has_nav_menu( $bpmenulocation ) ){
        $locations = get_theme_mod('nav_menu_locations');
        $locations[$bpmenulocation] = $menu_id;
        set_theme_mod( 'nav_menu_locations', $locations );
    }
7
  • I didn't know about this function. Yes, I guess it will make the above code much shorter. I guess I should move beyond the Codex and dive into the actual code, as I find that the API functions are often, like in this case, too low-level. Thanks!
    – julien_c
    Mar 7, 2012 at 14:18
  • @julien_c if this is solved, mark it as such to allow those who come after you to benefit from your experience here.
    – mor7ifer
    Mar 7, 2012 at 15:07
  • I just want to actually test it in real life so I'm sure it does what I want. I'll remember to mark it as solved as soon as I'm done!
    – julien_c
    Mar 7, 2012 at 15:51
  • 3
    If you see useful functions like these that aren't in the codex, it's a good idea to add them (yay wiki) =p
    – Tom J Nowell
    Mar 9, 2012 at 19:51
  • Sorry it took me such a long time to check that it worked in my case. Answer accepted! Also, you're defining custom links menu items, I've added an answer below to define page links (which will be more robust to URL changes, for example).
    – julien_c
    May 15, 2012 at 11:06
17

I have a few issues with the accepted answer - that doesn't make it wrong, but I'll post my own code below which I feel might have a better result for some people since I had the same question but wanted to do the same thing with less code.

First, the above code creates "URL" type navigation items, which is fine for some people but I want to link to PAGES, not URLs because this is an important feature of WordPress navigations and clients inevitable move things around so I never use the URL nav item type.

Also, only a flat array of children is handled by the posted code. I've created a function for recursively declaring the new nav items, storing their returned metadata (mainly ID after being created in the loop), and a parameter for accepting children.

Just edit $nav_items_to_add and the rest is handled recursively. There are 3 required keys in each array. First, the array key is the slug, so 'shop' => array( ... ) is what you want for a page with the slug shop. ['title']is the way the nav item will be labeled on the front end. path is the path to the page within the WordPress page hierarchy, so this is identical to the slug if the page is a top-level parent, and if shop were a child of home then it would be 'path' => 'home/shop'.

The last optional array key is ['parent'] where you can declare another key in the array as the parent of the current one. It's important to note that the items are added recursively, so the parent has to exist before you try to create a child. This means the declaration should happen for the parent nav item before it's children.

    $locations = get_nav_menu_locations();

    if (isset($locations['primary_navigation'])) {
        $menu_id = $locations['primary_navigation'];

        $new_menu_obj = array();

        $nav_items_to_add = array(
                'shop' => array(
                    'title' => 'Shop',
                    'path' => 'shop',
                    ),
                'shop_l2' => array(
                    'title' => 'Shop',
                    'path' => 'shop',
                    'parent' => 'shop',
                    ),
                'cart' => array(
                    'title' => 'Cart',
                    'path' => 'shop/cart',
                    'parent' => 'shop',
                    ),
                'checkout' => array(
                    'title' => 'Checkout',
                    'path' => 'shop/checkout',
                    'parent' => 'shop',
                    ),
                'my-account' => array(
                    'title' => 'My Account',
                    'path' => 'shop/my-account',
                    'parent' => 'shop',
                    ),
                'lost-password' => array(
                    'title' => 'Lost Password',
                    'path' => 'shop/my-account/lost-password',
                    'parent' => 'my-account',
                    ),
                'edit-address' => array(
                    'title' => 'Edit My Address',
                    'path' => 'shop/my-account/edit-address',
                    'parent' => 'my-account',
                    ),
            );

    foreach ( $nav_items_to_add as $slug => $nav_item ) {
        $new_menu_obj[$slug] = array();
        if ( array_key_exists( 'parent', $nav_item ) )
            $new_menu_obj[$slug]['parent'] = $nav_item['parent'];
        $new_menu_obj[$slug]['id'] = wp_update_nav_menu_item($menu_id, 0,  array(
                'menu-item-title' => $nav_item['title'],
                'menu-item-object' => 'page',
                'menu-item-parent-id' => $new_menu_obj[ $nav_item['parent'] ]['id'],
                'menu-item-object-id' => get_page_by_path( $nav_item['path'] )->ID,
                'menu-item-type' => 'post_type',
                'menu-item-status' => 'publish')
        );
    }

    }
1
  • 2
    That is a very smart solution. Thanks for adding this. Exactly what I needed. Jun 2, 2020 at 9:47
14

As a complement to ZaMoose's anwser, here's how you would create a "Page-type" menu item (not a "Custom" one):

wp_update_nav_menu_item($menu_id, 0, array('menu-item-title' => 'About',
                                           'menu-item-object' => 'page',
                                           'menu-item-object-id' => get_page_by_path('about')->ID,
                                           'menu-item-type' => 'post_type',
                                           'menu-item-status' => 'publish'));

Assuming you only know the page slug, for instance.

4

To add a menu item Programmatically. you can hook to wp_nav_menu_items filter. place below code in your theme functions.php to add login/logout menu item in the main menu. 'Primary' is the name/id of the menu registered.

/**
 * Add login logout menu item in the main menu.
 * ===========================================
 */

add_filter( 'wp_nav_menu_items', 'lunchbox_add_loginout_link', 10, 2 );
function lunchbox_add_loginout_link( $items, $args ) {
    /**
     * If menu primary menu is set & user is logged in.
     */
    if ( is_user_logged_in() && $args->theme_location == 'primary' ) {
        $items .= '<li><a href="'. wp_logout_url() .'">Log Out</a></li>';
    }
    /**
     * Else display login menu item.
     */
    elseif ( !is_user_logged_in() && $args->theme_location == 'primary' ) {
        $items .= '<li><a href="'. site_url('wp-login.php') .'">Log In</a></li>';
    }
    return $items;
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.