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 );
@iceteabottle
Copy link

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
Copy link

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
Copy link

hiendo3 commented May 13, 2017

Worked great! thanks!

@sandrowuermli
Copy link

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
Copy link

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
Copy link

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
Copy link

obaodelana commented Feb 14, 2018

Worked perfectly, Thanks!

@OWMC
Copy link

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
Copy link

harshclimate commented Mar 15, 2018

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

@markhowellsmead
Copy link

markhowellsmead commented May 11, 2018

@fuerio
Copy link

fuerio commented May 12, 2019

Thank you very much! It works great!

@alex-rybachenk0
Copy link

alex-rybachenk0 commented May 28, 2019

Thanks!

@dangelion
Copy link

dangelion commented Jun 3, 2019

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

@peytonbgregory
Copy link

peytonbgregory commented Oct 29, 2019

Works perfectly!

@petertwise
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

reinisg commented Feb 20, 2020

Perfect solution! Thanks!

@carlhussey
Copy link

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
Copy link

miliberlin commented Apr 20, 2020

Thank you! This saved me so much trouble.

@markvanwijnen
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Author

gerbenvandijk commented Oct 12, 2020

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

@tim9192
Copy link

tim9192 commented Oct 28, 2020

Thank you. Exactly what I needed! :) 👍

@im-mike
Copy link

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
Copy link

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