Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Mark (highlight) custom post type parent as active item in Wordpress Navigation.When you visit a custom post type's single page, the parent menu item (the post type archive) isn't marked as active. This code solves it by comparing the slug of the current post type with the navigation items, and adds a class accordingly.
<?php
function add_current_nav_class($classes, $item) {
// Getting the current post details
global $post;
// Get post ID, if nothing found set to NULL
$id = ( isset( $post->ID ) ? get_the_ID() : NULL );
// Checking if post ID exist...
if (isset( $id )){
// Getting the post type of the current post
$current_post_type = get_post_type_object(get_post_type($post->ID));
// Getting the rewrite slug containing the post type's ancestors
$ancestor_slug = $current_post_type->rewrite ? $current_post_type->rewrite['slug'] : '';
// Split the slug into an array of ancestors and then slice off the direct parent.
$ancestors = explode('/',$ancestor_slug);
$parent = array_pop($ancestors);
// Getting the URL of the menu item
$menu_slug = strtolower(trim($item->url));
// Remove domain from menu slug
$menu_slug = str_replace($_SERVER['SERVER_NAME'], "", $menu_slug);
// If the menu item URL contains the post type's parent
if (!empty($menu_slug) && !empty($parent) && strpos($menu_slug,$parent) !== false) {
$classes[] = 'current-menu-item';
}
// If the menu item URL contains any of the post type's ancestors
foreach ( $ancestors as $ancestor ) {
if (!empty($menu_slug) && !empty($ancestor) && strpos($menu_slug,$ancestor) !== false) {
$classes[] = 'current-page-ancestor';
}
}
}
// Return the corrected set of classes to be added to the menu item
return $classes;
}
add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );
@juddlyon

This comment has been minimized.

Copy link

@juddlyon juddlyon commented Feb 24, 2014

Nicely done, dropped this into my functions.php and it worked like a charm. Thanks!

@kategee

This comment has been minimized.

Copy link

@kategee kategee commented Mar 22, 2014

This is great but

$current_post_type_slug = $current_post_type->rewrite[slug];

should be

$current_post_type_slug = $current_post_type->rewrite['slug'];

(missing brackets)

@rothbert

This comment has been minimized.

Copy link

@rothbert rothbert commented Apr 8, 2014

Concur with kategee - getting a notice: Use of undefined constant slug - assumed 'slug'
which is fixed by use of single quotes around slug.

Nice snippet - thanks for sharing

@bramwillemse

This comment has been minimized.

Copy link

@bramwillemse bramwillemse commented Apr 11, 2014

Great little snippet, thanks!
The only issue I have now is my 'blog' (post type 'posts') menu item getting highlighted too..

@llest

This comment has been minimized.

Copy link

@llest llest commented Apr 16, 2014

The code by gport didn't work for me straight away, but led me to this idea. Thanks!
Add it to your functions.php.

add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );
function add_current_nav_class($classes, $item) {
$post_cat = get_the_category();
$post_cat_slug = $post_cat[0]->slug;

if (    ($item->ID == '  38.....   ' && $post_cat_slug == '    ....your posts' categorys' slug here....   ') ||
    ($item->ID == '  37.....  ' && $post_cat_slug == '    ....another posts' categorys' slug here....   ')
    ) {
    $classes[] = 'current-menu-item';
}

return $classes;

}

@egohost

This comment has been minimized.

Copy link

@egohost egohost commented Aug 14, 2014

This is my version of basically the same thing, except it does not depend on the slug (Sometimes there can be issues with miss matched slugs)

function custom_active_item_classes($classes = array(), $menu_item = false){            
        global $post;
        $classes[] = ($menu_item->url == get_post_type_archive_link($post->post_type)) ? 'current-menu-item active' : '';
        return $classes;
    }
add_filter( 'nav_menu_css_class', 'custom_active_item_classes', 10, 2 );
@jmage

This comment has been minimized.

Copy link

@jmage jmage commented May 26, 2015

Hey thanks! Just had to fix line 12 like this here:

    <?php

      add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );

        function add_current_nav_class($classes, $item) {

            // Getting the current post details
            global $post;

            // Getting the post type of the current post
            $current_post_type = get_post_type_object(get_post_type($post->ID));
            $current_post_type_slug = $current_post_type->rewrite['slug'];

            // Getting the URL of the menu item
            $menu_slug = strtolower(trim($item->url));

            // If the menu item URL contains the current post types slug add the current-menu-item class
            if (strpos($menu_slug,$current_post_type_slug) !== false) {

               $classes[] = 'current-menu-item';

            }

            // Return the corrected set of classes to be added to the menu item
            return $classes;

        }

    ?>
@xeyefex

This comment has been minimized.

Copy link

@xeyefex xeyefex commented Aug 14, 2015

Thanks for the piece of code !
I would add a check to see if the post ID exist otherwise you would get a (Notice: Trying to get property of non-object) error if no post is found.

So:

  // Mark (highlight) custom post type parent as active item in Wordpress Navigation
  add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );

    function add_current_nav_class($classes, $item) {

        // Getting the current post details
        global $post;

        // Get post ID, if nothing found set to NULL
        $id = ( isset( $post->ID ) ? get_the_ID() : NULL );

        // Checking if post ID exist...
        if (isset( $id )){
                        // Getting the post type of the current post
            $current_post_type = get_post_type_object(get_post_type($post->ID));
            $current_post_type_slug = $current_post_type->rewrite['slug'];          

            // Getting the URL of the menu item
            $menu_slug = strtolower(trim($item->url));

            // If the menu item URL contains the current post types slug add the current-menu-item class
            if (strpos($menu_slug,$current_post_type_slug) !== false) {

               $classes[] = 'current-menu-item';

            }
        }
        // Return the corrected set of classes to be added to the menu item
        return $classes;
    }
@philiprichter

This comment has been minimized.

Copy link

@philiprichter philiprichter commented Aug 24, 2015

egohosts works like a charm, thanks!

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented Jan 13, 2016

Awesome, seems to work right out of the box!

@disturbed-pixel

This comment has been minimized.

Copy link

@disturbed-pixel disturbed-pixel commented Feb 5, 2016

Just like many peeps here, works within seconds on first try! Thanks gerbenvandijk

@scepbv

This comment has been minimized.

Copy link

@scepbv scepbv commented Feb 23, 2016

Thanks, nice job!

@ndsh

This comment has been minimized.

Copy link

@ndsh ndsh commented Mar 7, 2016

works like a charm!

@bwjob

This comment has been minimized.

Copy link

@bwjob bwjob commented Apr 3, 2016

Great job!! Thx!

@XLogus

This comment has been minimized.

Copy link

@XLogus XLogus commented Apr 19, 2016

Code works good but if post archive is a submenu don`t add class "current_page_parent" to parent menu item

@Kelderic

This comment has been minimized.

Copy link

@Kelderic Kelderic commented Apr 19, 2016

I had a problem when the rewrite slug is multiple levels, ie 'support/downloads'. I edited the code to fix this, and allow for unlimited levels, along with the ID protection that xeyefex added.

function add_current_nav_class($classes, $item) {

    // Getting the current post details
    global $post;

    // Get post ID, if nothing found set to NULL
    $id = ( isset( $post->ID ) ? get_the_ID() : NULL );

    // Checking if post ID exist...
    if (isset( $id )){

        // Getting the post type of the current post
        $current_post_type = get_post_type_object(get_post_type($post->ID));

        // Getting the rewrite slug containing the post type's ancestors
        $ancestor_slug = $current_post_type->rewrite['slug'];

        // Split the slug into an array of ancestors and then slice off the direct parent.
        $ancestors = explode('/',$ancestor_slug);
        $parent = array_pop($ancestors);

        // Getting the URL of the menu item
        $menu_slug = strtolower(trim($item->url));

        // If the menu item URL contains the post type's parent
        if (strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }

        // If the menu item URL contains any of the post type's ancestors
        foreach ( $ancestors as $ancestor ) {
            if (strpos($menu_slug,$ancestor) !== false) {
                $classes[] = 'current-page-ancestor';
            }
        }
    }
    // Return the corrected set of classes to be added to the menu item
    return $classes;

} add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );
@rebdev

This comment has been minimized.

Copy link

@rebdev rebdev commented Apr 24, 2016

Just what I was looking for! I used Kelderic's version (thanks!), but on non-custom-post-type pages I was getting a PHP warning: "Warning: strpos() [function.strpos]: Empty needle in /Applications/AMPPS/www/mysite/wp-content/themes/mytheme/assets/functions/ancestor-menu-classes-for-cpts.php on line 35".

I resolved this by using toscho's is_custom_post_type() function from here, and then only running the main if statement if it's a custom post type we're viewing:

`
// Checking if post ID exist...

if ( isset( $id ) && is_custom_post_type( $id ) ){

`
Maybe someone with a better handle on WordPress needle errors can come up with something better but this stops the warnings.

@frafor1988

This comment has been minimized.

Copy link

@frafor1988 frafor1988 commented Jun 20, 2016

Had same problem as @rebdev solved by changing:

// If the menu item URL contains the post type's parent
        if (strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }

to:

        // If the menu item URL contains the post type's parent
        if (!empty($menu_slug) && !empty($parent) && strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }
@rneto88

This comment has been minimized.

Copy link

@rneto88 rneto88 commented Jun 29, 2016

Great job, working perfectly!! Thanks!

@dag0310

This comment has been minimized.

Copy link

@dag0310 dag0310 commented Sep 21, 2016

This did the trick for me after inserting it into /wp-includes/functions.php (customize lines 5, 6 or more according to your needs):

The key in the mappings array is the category slug and the value is the class name of the corresponding menu item entry.

function mark_menu_item_as_active($classes, $item) {
    $category_menuitem_mappings = array();

    /* !!! This needs to be customized !!! */
    $category_menuitem_mappings['category-slug-1'] = 'menu-item-xx';
    $category_menuitem_mappings['category-slug-2'] = 'menu-item-yy';

    global $post;
    $category_obj = get_the_category($post->ID);
    $category_str = $category_obj[0]->slug;

    foreach ($category_menuitem_mappings as $key => $value) {
        if (in_array($value, $classes) && $category_str === $key) {
            $classes[] = 'current-menu-item';
        }
    }

    return $classes;
}
add_filter('nav_menu_css_class', 'mark_menu_item_as_active', 10, 2);
@nicholasolsen

This comment has been minimized.

Copy link

@nicholasolsen nicholasolsen commented Nov 6, 2016

Thank you so much! Dropped it in my function file and worked like a charm! HUGE time saver!

@brightspire

This comment has been minimized.

Copy link

@brightspire brightspire commented Dec 4, 2016

Love your work!

@PlutonW

This comment has been minimized.

Copy link

@PlutonW PlutonW commented Dec 30, 2016

I did the task without comparison URL.
I use the terms ID, it does not depend on your permalink settings.
But you must set your Taxonomy in
$cur_terms = get_the_terms( $post->ID, 'your_taxonomy' );

<?php
add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );	

	function add_current_nav_class($classes, $item) {
		// Getting the current post details
		global $post;
		
		// Getting the post type of the current post
		$current_post_type = get_post_type($post->ID);

	if (is_single() && $current_post_type !='post' ) {		
	
		//  Getting the current post taxonomies in prod_cat Taxonomy
		$cur_terms = get_the_terms( $post->ID, 'your_taxonomy' );
		$post_terms_list = array(); 
		$post_terms_parrent_list = array();		
		if ($cur_terms)
			foreach ($cur_terms as $terms) {
				$post_terms_list[] = $terms->term_id;
				if ($terms->parent )
				$post_terms_parrent_list[] = $terms->parent;
			}
							
		// If the menu object ID contains the current post taxonomies ID - add clases
		if (in_array($item->object_id, $post_terms_list)) {		
			$classes[] = 'current-post-ancestor';
		}
		if (in_array($item->object_id, $post_terms_parrent_list)) {		
			$classes[] = 'current-post-ancestor';
			$classes[] = 'current-menu-parent';					
		}

	}		
		// Return the corrected set of classes to be added to the menu item
		return $classes;
}
?>
@wizve

This comment has been minimized.

Copy link

@wizve wizve commented Jan 18, 2017

dropped into functions.php and worked great. thanks

@caduvisentin

This comment has been minimized.

Copy link

@caduvisentin caduvisentin commented Jan 28, 2017

I add an "else" to remove current_page_parent from blog classes. Like this:

add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );

	function add_current_nav_class($classes, $item) {
	
	// Getting the current post details
	global $post;
	
	// Getting the post type of the current post
	$current_post_type = get_post_type_object(get_post_type($post->ID));
	$current_post_type_slug = $current_post_type->rewrite['slug'];
		
	// Getting the URL of the menu item
	$menu_slug = strtolower(trim($item->url));
	
	// If the menu item URL contains the current post types slug add the current-menu-item class
	if (strpos($menu_slug,$current_post_type_slug) !== false) {
	
	   $classes[] = 'current-menu-item';
	
	} else {
		
		$classes = array_diff( $classes, array( 'current_page_parent' ) );
	}
	
	// Return the corrected set of classes to be added to the menu item
	return $classes;

}
@WebbizAdmin

This comment has been minimized.

Copy link

@WebbizAdmin WebbizAdmin commented Mar 22, 2017

Worked great! thanks!

@yositomo2

This comment has been minimized.

Copy link

@yositomo2 yositomo2 commented Apr 14, 2017

Hi!

I used dag0310 Version. Works fine, only that my starting-page now also highlights a menu-item as active, which it shouldn't...
Any idea?

@maznialwan

This comment has been minimized.

Copy link

@maznialwan maznialwan commented Apr 15, 2017

copy to functions.php and its work well... thanks..

@iceteabottle

This comment has been minimized.

Copy link

@iceteabottle iceteabottle commented Apr 18, 2017

I added some error handling...

add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );

function add_current_nav_class($classes, $item) {
	
	// Getting the current post details
	global $post;

        // check if it's a post
        if (empty($post)) {
             return $classes;
        }	

	// Getting the post type of the current post
	$current_post_type = get_post_type_object(get_post_type($post->ID));
	$current_post_type_slug = $current_post_type->rewrite['slug'];
		
	// Getting the URL of the menu item
	$menu_slug = strtolower(trim($item->url));
	
	// If the menu item URL contains the current post types slug add the current-menu-item class
	if (strpos($menu_slug,$current_post_type_slug) !== false) {
	
	   $classes[] = 'current-menu-item';
	
	} else {
		
		$classes = array_diff( $classes, array( 'current_page_parent' ) );
	}
	
	// Return the corrected set of classes to be added to the menu item
	return $classes;

}
@tomsnep

This comment has been minimized.

Copy link

@tomsnep tomsnep commented May 12, 2017

Hey guys,

I used this piece of code and it worked like a charm.
Now i made the website multilingual and translated the custom post types slugs (wpml), the piece of code doesn't seem to work anymore when i translated the custom post type slugs.

I'm not really into php so i don't know by myself how i can handle this problem.
Does anyone have suggestions?

Thanks in advance!
Tom

@hiendo3

This comment has been minimized.

Copy link

@hiendo3 hiendo3 commented May 13, 2017

Worked great! thanks!

@sandrowuermli

This comment has been minimized.

Copy link

@sandrowuermli sandrowuermli commented Jun 4, 2017

I modified the code for posts (blog) for single, tag or category etc. too.

function add_current_nav_class( $classes, $item ) {

	// Getting the current post details
	global $post;

	// check if it's a post
	if ( empty( $post ) ) {
		return $classes;
	}

	// Getting the post type of the current post
	$current_post_type      = get_post_type_object( get_post_type( $post->ID ) );
	$current_post_type_slug = $current_post_type->rewrite['slug'];

	// Getting the post if the post type isn't a custom post type
	if ( is_null( $current_post_type_slug ) ) {
		if ( $current_post_type->name === 'post' ) {
			foreach ( explode( '/', $GLOBALS['wp_rewrite']->front ) as $front_url ) {
				if ( $front_url !== '' ) {
					// if slug is '/blog/tipps/' it will pick the first. In this case it's 'blog'
					$current_post_type_slug = $front_url;
					break;
				}
			}
		}
	}

	// Getting the URL of the menu item
	$menu_slug = strtolower( trim( $item->url ) );

	// If the menu item URL contains the current post types slug add the current-menu-item class
	if ( strpos( $menu_slug, $current_post_type_slug ) !== false ) {
		$classes[] = 'active';
	} else {
		$classes = array_diff( $classes, [ 'current_page_parent' ] );
	}

	// Return the corrected set of classes to be added to the menu item
	return $classes;

}

add_action( 'nav_menu_css_class', 'add_current_nav_class', 10, 2 );
@JakeGonzales

This comment has been minimized.

Copy link

@JakeGonzales JakeGonzales commented Jun 27, 2017

I combined both @egohost and @xeyefex solutions and it seems to be the simplest.

You get the class names on post and custom post type and you also don't get a Trying to get property of non-object error when there are no posts in the archive.

function custom_active_item_classes($classes = array(), $menu_item = false) {
    global $post;

    // Get post ID, if nothing found set to NULL
    $id = ( isset( $post->ID ) ? get_the_ID() : NULL );

    // Checking if post ID exist...
    if (isset( $id )){
	    $classes[] = ($menu_item->url == get_post_type_archive_link($post->post_type)) ? 'current-menu-item active' : '';
    }

    return $classes;
}
add_filter( 'nav_menu_css_class', 'custom_active_item_classes', 10, 2 );
@VlooMan

This comment has been minimized.

Copy link

@VlooMan VlooMan commented Aug 16, 2017

As reported by @XLogus, If CPT archive is a submenu item, the code does not add class "current_page_parent" to the parent menu item. I have created a new piece of code which highlights the menu parents as well.

The Gist:
https://goo.gl/gxBT3w
https://gist.github.com/VlooMan/e9f49bea6cb3d32c054d7ea05b4845f1

Feel free to post a comment if it helped!

@obaodelana

This comment has been minimized.

Copy link

@obaodelana obaodelana commented Feb 14, 2018

Worked perfectly, Thanks!

@OWMC

This comment has been minimized.

Copy link

@OWMC OWMC commented Feb 22, 2018

Great script. I also had a problem with the Blog menu highlighting when on a single CPT post. @iceteabottle's 'else' statement dealt with that but left another problem... Now the Blog menu no longer highlights even when on a single blog post.

The fix was to add an 'if' statement to check to make sure we aren't on a single blog post before running the code.

add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );  
function add_current_nav_class($classes, $item) {  
  // Getting the current post details  
  global $post;  
  // Make sure we're not on a single blog post before running the code...  
  if ( !is_singular( 'post' ) ) {
    // Getting the post type of the current post  
    $current_post_type = get_post_type_object(get_post_type($post->ID));  
    $current_post_type_slug = $current_post_type->rewrite['slug'];  
    // Getting the URL of the menu item  
    $menu_slug = strtolower(trim($item->url));  
    // If the menu item URL contains the current post types slug add the current-menu-item class  
    if (strpos($menu_slug,$current_post_type_slug) !== false) {  
      $classes[] = 'current-menu-item';  
    }   
    // as we are not on a single blog post, stop blog menu from highlighting  
    else {  
      $classes = array_diff( $classes, array( 'current_page_parent' ) );  
    }  
  }
  // Return the corrected set of classes to be added to the menu item  
  return $classes;  
}  
@harshclimate

This comment has been minimized.

Copy link

@harshclimate harshclimate commented Mar 15, 2018

Thanks! Worked great just pasting it in functions.php!

@markhowellsmead

This comment has been minimized.

Copy link

@markhowellsmead markhowellsmead commented May 11, 2018

@fuerio

This comment has been minimized.

Copy link

@fuerio fuerio commented May 12, 2019

Thank you very much! It works great!

@alex-rybachenk0

This comment has been minimized.

Copy link

@alex-rybachenk0 alex-rybachenk0 commented May 28, 2019

Thanks!

@dangelion

This comment has been minimized.

Copy link

@dangelion dangelion commented Jun 3, 2019

Someone could put together all these improvements in one final gist? Thanks

@peytonbgregory

This comment has been minimized.

Copy link

@peytonbgregory peytonbgregory commented Oct 29, 2019

Works perfectly!

@petertwise

This comment has been minimized.

Copy link

@petertwise petertwise commented Dec 14, 2019

Here's what I did to make sure I grabbed the parent item too:
https://gist.github.com/squarecandy/19bcc3faf6552f29bac04670e4b081c4
This only works for one level of depth, but it works if the parent URL is completely different from the post type slug

@ThePixelPixie

This comment has been minimized.

Copy link

@ThePixelPixie ThePixelPixie commented Feb 13, 2020

I'm wondering if there's a way to modify this for taxonomy page parent and children in a non-WP-standard nav? In other words, I have a nav system built using Advanced Custom Fields, querying taxonomies and displaying random images within that taxonomy. But the ACF area allows the user to select several taxonomy terms as the menu items. So my menu is not built using the WP walker. I just need to add "active" to parent links when on children pages, and the jQuery I have is only working for parent pages.

@petertwise

This comment has been minimized.

Copy link

@petertwise petertwise commented Feb 13, 2020

@ThePixelPixie if you're not using wp_nav_menu() and the built in walker system, this code is not going to help at all.

@ThePixelPixie

This comment has been minimized.

Copy link

@ThePixelPixie ThePixelPixie commented Feb 13, 2020

Ok. Got it. I will see if I can modify my menu design to work with wp_nav_menu() then. THANK you!

@reinisg

This comment has been minimized.

Copy link

@reinisg reinisg commented Feb 20, 2020

Perfect solution! Thanks!

@carlhussey

This comment has been minimized.

Copy link

@carlhussey carlhussey commented Feb 29, 2020

This worked great for 90% of my links. Still having trouble with WooCommerece though. When I go to /cart or /checkout, my /shop menu item doesn't have an active class.

@miliberlin

This comment has been minimized.

Copy link

@miliberlin miliberlin commented Apr 20, 2020

Thank you! This saved me so much trouble.

@markvanwijnen

This comment has been minimized.

Copy link

@markvanwijnen markvanwijnen commented May 20, 2020

Below is the type-safe code, this will have all the guards needed to avoid any warning or error.

add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );  
function add_current_nav_class($classes, $item) {  
        if ( ! ( $item instanceof WP_Post ) ) return $classes;

        $post = get_post();
        if ( empty( $post ) ) return $classes;

        $post_type          = get_post_type( $post->ID );
        $post_type_object   = get_post_type_object( $post_type );

        if ( ! ( $post_type_object instanceof WP_Post_Type ) || ! $post_type_object->has_archive ) return $classes;
        
        $post_type_slug = $post_type_object->rewrite['slug'];
        $menu_slug      = strtolower( trim( $item->url ) );

        if ( empty( $post_type_slug ) || empty( $menu_slug ) ) return $classes;
        if ( strpos( $menu_slug, $post_type_slug ) === false ) return $classes;
        
        $classes[] = 'current-menu-item';

        return $classes;
}
@tyrants666

This comment has been minimized.

Copy link

@tyrants666 tyrants666 commented Jul 7, 2020

I added some error handling...

add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );

function add_current_nav_class($classes, $item) {
	
	// Getting the current post details
	global $post;

        // check if it's a post
        if (empty($post)) {
             return $classes;
        }	

	// Getting the post type of the current post
	$current_post_type = get_post_type_object(get_post_type($post->ID));
	$current_post_type_slug = $current_post_type->rewrite['slug'];
		
	// Getting the URL of the menu item
	$menu_slug = strtolower(trim($item->url));
	
	// If the menu item URL contains the current post types slug add the current-menu-item class
	if (strpos($menu_slug,$current_post_type_slug) !== false) {
	
	   $classes[] = 'current-menu-item';
	
	} else {
		
		$classes = array_diff( $classes, array( 'current_page_parent' ) );
	}
	
	// Return the corrected set of classes to be added to the menu item
	return $classes;

}

Thank you this helped me a lot get rid of the errors 👍

@Ricoder92

This comment has been minimized.

Copy link

@Ricoder92 Ricoder92 commented Aug 6, 2020

Had same problem as @rebdev solved by changing:

// If the menu item URL contains the post type's parent
        if (strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }

to:

        // If the menu item URL contains the post type's parent
        if (!empty($menu_slug) && !empty($parent) && strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }

Thanks for the fix and thanks to @Kelderic for the code.
Its exactly what i was looking for. It works like a charm.
But i noticed a funny error. I have a slug for a CPT called "games" and my domain contains the word "games". So the code does always find the slug and highlights every page and mark every menu item as anchestor. So i wrote a little fix. I just remove the domain from the menu slug. It works now perfectly.

function add_current_nav_class($classes, $item) {

    // Getting the current post details
    global $post;

    // Get post ID, if nothing found set to NULL
    $id = ( isset( $post->ID ) ? get_the_ID() : NULL );

    // Checking if post ID exist...
    if (isset( $id )){

        // Getting the post type of the current post
        $current_post_type = get_post_type_object(get_post_type($post->ID));

        // Getting the rewrite slug containing the post type's ancestors
        $ancestor_slug = $current_post_type->rewrite['slug'];

        // Split the slug into an array of ancestors and then slice off the direct parent.
        $ancestors = explode('/',$ancestor_slug);
        $parent = array_pop($ancestors);

        // Getting the URL of the menu item
        $menu_slug = strtolower(trim($item->url));

        // Remove domain from menu slug
        $menu_slug = str_replace($_SERVER['SERVER_NAME'], "", $menu_slug);

        // If the menu item URL contains the post type's parent
        if (!empty($menu_slug) && !empty($parent) && strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }
        
        // If the menu item URL contains any of the post type's ancestors
        foreach ( $ancestors as $ancestor ) {
            if (strpos($menu_slug,$ancestor) !== false) {
                $classes[] = 'current-page-ancestor';
            }
        }
    }
    // Return the corrected set of classes to be added to the menu item
    return $classes;

} add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );
@haydnjames

This comment has been minimized.

Copy link

@haydnjames haydnjames commented Aug 26, 2020

I had a problem when the rewrite slug is multiple levels, ie 'support/downloads'. I edited the code to fix this, and allow for unlimited levels, along with the ID protection that xeyefex added.

function add_current_nav_class($classes, $item) {

    // Getting the current post details
    global $post;

    // Get post ID, if nothing found set to NULL
    $id = ( isset( $post->ID ) ? get_the_ID() : NULL );

    // Checking if post ID exist...
    if (isset( $id )){

        // Getting the post type of the current post
        $current_post_type = get_post_type_object(get_post_type($post->ID));

        // Getting the rewrite slug containing the post type's ancestors
        $ancestor_slug = $current_post_type->rewrite['slug'];

        // Split the slug into an array of ancestors and then slice off the direct parent.
        $ancestors = explode('/',$ancestor_slug);
        $parent = array_pop($ancestors);

        // Getting the URL of the menu item
        $menu_slug = strtolower(trim($item->url));

        // If the menu item URL contains the post type's parent
        if (strpos($menu_slug,$parent) !== false) {
            $classes[] = 'current-menu-item';
        }

        // If the menu item URL contains any of the post type's ancestors
        foreach ( $ancestors as $ancestor ) {
            if (strpos($menu_slug,$ancestor) !== false) {
                $classes[] = 'current-page-ancestor';
            }
        }
    }
    // Return the corrected set of classes to be added to the menu item
    return $classes;

} add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );

This solution is 99% there for me, but doesn't quite work when there are other items that may share part of the CPT slug in their slugs. For example, the slug for my CPT is /my-hr/knowledge/....

In our Nav, we have the main My HR item, but then this has a couple of sub pages in a drop down.

My HR
-- Contact HR
-- Employee Benefits

With this above function, both Contact and Employee benefits show active as well as My HR when viewing a CPT post.

Anyone know a workaround for this?

@mmoollllee

This comment has been minimized.

Copy link

@mmoollllee mmoollllee commented Oct 11, 2020

Thanks for sharing!

Further fixes I came up with:

Added a fallback to empty string if rewrite is false

$ancestor_slug = $current_post_type->rewrite ? $current_post_type->rewrite['slug'] : '';

Check for $menu_slug & $anchestor

if (!empty($menu_slug) && !empty($ancestor) && strpos($menu_slug,$ancestor) !== false) {

Result:

function add_current_nav_class($classes, $item) {

   // Getting the current post details
   global $post;

   // Get post ID, if nothing found set to NULL
   $id = ( isset( $post->ID ) ? get_the_ID() : NULL );

   // Checking if post ID exist...
   if (isset( $id )){

       // Getting the post type of the current post
       $current_post_type = get_post_type_object(get_post_type($post->ID));

       // Getting the rewrite slug containing the post type's ancestors
       $ancestor_slug = $current_post_type->rewrite ? $current_post_type->rewrite['slug'] : '';

       // Split the slug into an array of ancestors and then slice off the direct parent.
       $ancestors = explode('/',$ancestor_slug);
       $parent = array_pop($ancestors);

       // Getting the URL of the menu item
       $menu_slug = strtolower(trim($item->url));

       // Remove domain from menu slug
       $menu_slug = str_replace($_SERVER['SERVER_NAME'], "", $menu_slug);

       // If the menu item URL contains the post type's parent
       if (!empty($menu_slug) && !empty($parent) && strpos($menu_slug,$parent) !== false) {
           $classes[] = 'current-menu-item';
       }
       
       // If the menu item URL contains any of the post type's ancestors
       foreach ( $ancestors as $ancestor ) {
           if (!empty($menu_slug) && !empty($ancestor) && strpos($menu_slug,$ancestor) !== false) {
               $classes[] = 'current-page-ancestor';
           }
       }
   }
   // Return the corrected set of classes to be added to the menu item
   return $classes;

}
add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );
@gerbenvandijk

This comment has been minimized.

Copy link
Owner Author

@gerbenvandijk gerbenvandijk commented Oct 12, 2020

@mmoollllee (and others) thanks! I updated the original gist 👍

@tim9192

This comment has been minimized.

Copy link

@tim9192 tim9192 commented Oct 28, 2020

Thank you. Exactly what I needed! :) 👍

@im-mike

This comment has been minimized.

Copy link

@im-mike im-mike commented Oct 30, 2020

Thank you, this is working great:) I'm surprised Wordpress doesn't do that out of the box...

@jhtjards

This comment has been minimized.

Copy link

@jhtjards jhtjards commented Feb 8, 2021

Great Gist, thanks to everyone for sharing! Saved me a lot of time!

It did however not work for me with child pages that are an ancestor of a menu item.
I think it's because pages don't have a rewrite slug.

So I checked for pages and populated the ancestors array accordingly

if ( $current_post_type->name == 'page'){
	$ancestors = array();
	foreach ( get_post_ancestors($post) as $ancestor ) {
		$ancestors[] = get_post_field( 'post_name', $ancestor );
	}
}

This is the full snippet:

function add_current_nav_class($classes, $item) {

	// Getting the current post details
	global $post;

	// Get post ID, if nothing found set to NULL
	$id = ( isset( $post->ID ) ? get_the_ID() : NULL );

	// Checking if post ID exist...
	if (isset( $id )){

		// Getting the post type of the current post
		$current_post_type = get_post_type_object(get_post_type($post->ID));
		//var_dump($current_post_type->name);

		// Getting the rewrite slug containing the post type's ancestors
		$ancestor_slug = $current_post_type->rewrite ? $current_post_type->rewrite['slug'] : '';

		// Split the slug into an array of ancestors and then slice off the direct parent.
		$ancestors = explode('/',$ancestor_slug);

		// Pages have no rewrite base, so check for parent pages and populate array with all slugs of parents
		if ( $current_post_type->name == 'page'){
			$ancestors = array();
			foreach ( get_post_ancestors($post) as $ancestor ) {
				$ancestors[] = get_post_field( 'post_name', $ancestor );
			}
		}

		$parent = array_pop($ancestors);

		// Getting the URL of the menu item
		$menu_slug = strtolower(trim($item->url));

		// Remove domain from menu slug
		$menu_slug = str_replace($_SERVER['SERVER_NAME'], "", $menu_slug);

		// If the menu item URL contains the post type's parent
		if (!empty($menu_slug) && !empty($parent) && strpos($menu_slug,$parent) !== false) {
			$classes[] = 'current-menu-item';
		}

		// If the menu item URL contains any of the post type's ancestors
		foreach ( $ancestors as $ancestor ) {
			if (!empty($menu_slug) && !empty($ancestor) && strpos($menu_slug,$ancestor) !== false) {
				$classes[] = 'current-page-ancestor';
			}
		}
	}
	// Return the corrected set of classes to be added to the menu item
	return $classes;
}
add_action('nav_menu_css_class', 'add_current_nav_class', 10, 2 );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment