Adding Custom Post Types to the navigation menu in WordPress (3.1)

Custom Post Types in WordPress are awesome! However, making them appear in your main navigation menu is difficult (and until now, some might have said impossible without hacking the WordPress core files). So after days of stumbling, reverse engineering and swearing, I present a solution.

This solution works nicely and involves just adding a filter to your theme functions file.

The following function will automatically grab all your custom post types (which are set to hierarchical= true) and add them to the main nav. You should ensure your custom post type has has_archive set to true also, as the archive page will be the main nav item, with all items in the custom post type appearing as children of this main item. The way this function is written, it writes a Home link, it then loops through the custom post types adding the archive page as the main item and the items as children pages of the archive page. Then it loops through and writes the normal page menu, so your menu will end up looking something like this: HOME | POST TYPE 1 | POST TYPE 2 | PAGE 1 | PAGE 2 | PAGE 3 This is by no means a complete solution, but hopefully it’s a starting point and will save someone the days of work it took me to come up with it. Add the following code to your function.php file in your theme folder:

function custom_page_menu($menu, $args) {

 // get supplied args
 $list_args = $args;

 // Overide some menu settings
 $list_args['echo'] = false;
 $list_args['title_li'] = '';
 $list_args['show_home'] = false;
 $list_args['exclude'] = 4; // excluding the homepage as I am manually adding it to the start below

 // get the current page object as we will need to refer to it when setting current items below
 global $wp_query;
 $current_page = $wp_query->get_queried_object();  

 // Show Home item at the start of the menu
 $menu .= '<li ' . $class . '><a href="' . home_url( '/' ) . '" title="' . esc_attr(__('Home')) . '">' . $args['link_before'] . __('Home') . $args['link_after'] . '</a></li>';

 // Loop through the custom post types and add them to the menu
 foreach(get_post_types(array(
 'public'   => true,
 '_builtin' => false,
 'hierarchical' => true
 )) as $pt) {

 $obj_pt = get_post_type_object($pt);
 $list_args['post_type'] = $pt;
 $menu .= '<li><a href="/' . $obj_pt->rewrite['slug'] . '/">' . $obj_pt->labels->name . '</a><ul>';

 // little bit of hacking here to force wp_list_pages to add current classes to
 // active pages in the generated navs. It's messy, but it means not hacing to
 // hack the WordPress core files
 $original_is_posts_page = $wp_query->is_posts_page;
 $wp_query->is_posts_page = true;
 $menu .= wp_list_pages($list_args);
 $wp_query->is_posts_page = $original_is_posts_page;

 $menu .= '</ul></li>';
 }

 // Now add the normal page collection which belongs in the nav
 $list_args['post_type'] = 'page';
 $menu .= wp_list_pages($list_args) ;

 // glue the menu together and send back
 if ( $menu )
 $menu = '<ul>' . $menu . '</ul>';

 $menu = '<div>' . $menu . "</div>n";

 return $menu;
}
add_filter( 'wp_page_menu', 'custom_page_menu' ,10,2 );