Adding custom post type archives to a WordPress menu

Is there a way (besides adding a Custom Link) to add a custom post type archive to a menu in WordPress? If it’s added using a custom link (e.g. /cpt-archive-slug/), WordPress does not apply classes like current-menu-item to the list element, which presents challenges when styling the menu.

If the custom link contains the entire URL (e.g. http://site.com/cpt-archive-slug/), those classes are added. However, that’s probably not a ‘best practice’.

Related posts

Leave a Reply

8 comments

  1. your best opption is custom link with full url as Custom post types archives are different form taxonomy based archives (categories,tags,any custom taxonomy) and date based archives which have there own archive slug.

  2. I know this is old but I have this problem too and I found a rather clean way to handle it is to use a custom menu walker

    class KB_Custom_Menu_Walker extends Walker_Nav_Menu {
    
      protected static $custom_post_types = array();
    
      public function start_el(&$output, $item, $depth=0, $args=array(), $id=0) {
        if (isset( self::$custom_post_types[ $item->url ] )) {
          $item->url = get_post_type_archive_link( self::$custom_post_types[$item->url] );
        }
        parent::start_el($output, $item, $depth, $args, $id);
      }
    
      public static function custom_post_types($type=null) {
        if ($type) {
          self::$custom_post_types[ '#post_type_'.$type ] = $type;
        }
        return self::$custom_post_types;
      }
    }
    

    Having a custom link menu item with URL of #post_type_album, you can use it like this:

    # Where you defined your custom post type (could be anywhere anyway)
    KB_Custom_Menu_Walker::custom_post_types('album');
    
    # And display the menu
    wp_nav_menu(array(
      'theme_location' => 'primary-nav',
      'walker' => new KB_Custom_Menu_Walker(),
    ));
    

    Note: This assume that your post type’s slug and name are the same.

  3. I have expanded a bit on tungd’s answer to provide more genericity with that approach. This implementation allows adding arbitrary mappings between menu ‘macros’ and internal WordPress URLs that only the backend knows about.

    I’ve also decided to use ! as the prefix for these macros to avoid them clashing with named anchors – this includes an overhead to strip off the ‘http://’ from the link URL (as WordPress will attempt to normalise these weird links). If that implementation bothers you, you can always remove the preg_replace() call and use # as your link prefix as before.

    class Extendable_Menu_Walker extends Walker_Nav_Menu
    {
        protected static $custom_urls = array();
    
        public static function setupUrls()
        {
            // calls to self::mapPostType($postTypeName) and 
            // self::createMapping($wildcard, $url) go here...
        }
    
        public function start_el(&$output, $item, $depth=0, $args=array(), $id=0)
        {
            $url = preg_replace('@^https?://@', '', $item->url);
            if (isset( self::$custom_urls[ $url ] )) {
                $item->url = self::$custom_urls[ $url ];
            }
            parent::start_el($output, $item, $depth, $args, $id);
        }
    
        public static function createMapping($urlKey, $realUrl)
        {
            self::$custom_urls['!' . $urlKey] = $realUrl;
        }
    
        public static function mapPostType($type)
        {
            self::createMapping('post_type_' . $type, get_post_type_archive_link($type));
        }
    }
    
    add_action('init', array('Extendable_Menu_Walker', 'setupUrls'));
    
  4. You can create a Page and then apply a custom archive template to it. I create and use archive-{post-type}.php fo the template name just like you would for the default archive, I just manually apply it to the Page.

    If you do this, I’d recommend setting your CPT to has_archive=’false’ to avoid permalink collisions, regardless just make sure your Page has a different permalink slug than your CPT archive slug is set to.

  5. I think I’d have to create a new “parent” post-type, so the custom-post-type I want to show becomes a “child”, and that parent WILL be listed in the menu.

    This should be an option. Creating a dummy parent just for getting it listed in the menu is too much work.

    I’d like a way to do it from the template, or functions, so I don’t have to do it in each menu settings for each site in my multisite installation.

  6. What I do instead of adding the link is creating an empty page. In that page I add something like “Content generated from cpt-name” in the editor so my client knows where all the stuff is coming from.

    Then I add that page to the menu and with the following code I ensure that is highlighted when archives-cpt or single-cpt is running.

    /**
     * Fix to add Custom post types to nav menu
     * If you custom post is called "concepts"
     * create a new empty page called concepts and add this
     */
     function additional_active_item_classes($classes = array(), $menu_item = false){
        global $wp_query;
    
    
        if(in_array('current-menu-item', $menu_item->classes)){
            $classes[] = 'current-menu-item';
        }
    
        if ( $menu_item->title == 'PAGE TITLE' && is_post_type_archive('cpt-slug') ) {
            $classes[] = 'current-menu-item';
        }
    
        if ( $menu_item->title == 'PAGE TITLE' && is_singular('cpt-slug') ) {
            $classes[] = 'current-menu-item';
        }
    
    
        return $classes;
    }
    add_filter( 'nav_menu_css_class', 'additional_active_item_classes', 10, 2 );
    

    This will check for menu/page title and if we are actually seeing the archive or individual page of a custom post type. If it’s true it will add the current-menu-item class.