Currently, you cannot add post type archives to the navigation block. At least not in a way that it recognizes its state, e.g. whether it’s the current active menu item. This is a known issue in the Gutenberg project (see GitHub) since at least May 2021. And unfortunately, it doesn’t seem to be addressed very soon.

When I needed it for a custom post type archive “job”, I came up with a solution that changes the output of the navigation block. This is solely required to set the current-menu-item and current-menu-ancestor class.

The script basically checks whether the current request is requesting the post archive or a singular page of the desired post type and then adds the correct class to the menu item via PHP’s DOMDocument. I strictly rely on the used URL on the menu item, which in my case is /job/.

So for a single post type this is what the code looks like:

/**
 * Set job archive menu item as active if needed.
 * 
 * Currently, post type archives cannot be added to the navigation block.
 * 
 * @see		https://github.com/WordPress/gutenberg/issues/31452
 * 
 * @param	string	$block_content Block content
 * @return	string Updated block content
 */
static function my_set_job_archive_active( string $block_content ): string {
	if ( ! \is_post_type_archive( 'job' ) && ! \is_singular( 'job' ) ) {
		return $block_content;
	}
	
	$libxml_use_errors = \libxml_use_internal_errors( true );
	
	$dom = new \DOMDocument();
	$dom->loadHTML(
		'<html><meta charset="utf-8">' . $block_content . '</html>',
		LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
	);
	$has_changed = false;
	
	/** @var \DOMElement $list_element */
	foreach ( $dom->getElementsByTagName( 'li' ) as $list_element ) {
		$class_name = '';
		
		// get classes of first link
		foreach ( $list_element->getElementsByTagName( 'a' ) as $child_link ) {
			$class_name = $child_link->getAttribute( 'class' );
			break;
		}
		
		// current item already has desired class
		if (
			\str_contains( $class_name, 'current-menu-item' )
			|| \str_contains( $class_name, 'current-menu-ancestor' )
		) {
			continue;
		}
		
		if ( \str_contains( $list_element->getAttribute( 'class' ), 'has-child' ) ) {
			$is_target = false;
			
			// check if a child is active
			foreach ( $list_element->getElementsByTagName( 'a' ) as $child_link ) {
				if ( \str_ends_with( $child_link->getAttribute( 'href' ), '/job/' ) ) {
					$is_target = true;
				}
			}
			
			if ( $is_target ) {
				foreach ( $list_element->getElementsByTagName( 'a' ) as $link ) {
					$link->setAttribute( 'class', \trim( $link->getAttribute( 'class' ) . ' current-menu-ancestor' ) );
					$has_changed = true;
					
					// only check the first item
					break;
				}
			}
		}
		else {
			foreach ( $list_element->getElementsByTagName( 'a' ) as $link ) {
				if ( \str_ends_with( $link->getAttribute( 'href' ), '/job/' ) ) {
					$new_class = 'current-menu-' . ( \is_singular( 'job' ) ? 'ancestor' : 'item' );
					$list_element->setAttribute( 'class', \trim( $list_element->getAttribute( 'class' ) . ' ' . $new_class ) );
					$has_changed = true;
				}
				
				// only check the first item
				break;
			}
		}
	}
	
	if ( $has_changed ) {
		$block_content = $dom->saveHTML( $dom->documentElement->firstChild->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
	}
	
	\libxml_use_internal_errors( $libxml_use_errors );
	
	return $block_content;
}

\add_filter( 'render_block_core/navigation', 'my_set_job_archive_active' );
Code language: PHP (php)

To use multiple different post types, just loop the code for every post type accordingly.

Leave a Reply

Your email address will not be published. Required fields are marked *

To respond on your own website, enter the URL of your response which should contain a link to this post's permalink URL. Your response will then appear (possibly after moderation) on this page. Want to update or remove your response? Update or delete your post and re-enter your post's URL again. (Learn more about webmentions)