The block-based Navigation block replaces wp_nav_menu() in Full Site Editing themes, but classic themes can use both simultaneously during migration. Understanding how to register, filter, and extend the Navigation block is essential as the ecosystem moves away from functions.php menu callbacks.
Problem: A WordPress site has classic navigation menus configured in Appearance → Menus, and the theme is being migrated to Full Site Editing — the classic menu system is not compatible with the Navigation block used in FSE block templates.
Solution: Convert classic menus to the Navigation block in the Site Editor: open Appearance → Editor → Navigation, select the location, and import the existing classic menu. For programmatic conversion, use the WP_Navigation_Fallback class or the Gutenberg plugin's menu import tool. Navigation block menus are stored as wp_navigation post type entries.
The examples below register menus that work with both the classic and block navigation systems, add a custom link to the Navigation block via a filter, and output block navigation markup from PHP for headless setups.
__( 'Primary Navigation', 'myplugin' ),
'footer' => __( 'Footer Navigation', 'myplugin' ),
'mobile' => __( 'Mobile Navigation', 'myplugin' ),
] );
}
add_action( 'init', 'myplugin_register_menus' );
// Classic wp_nav_menu() still works in classic themes or hybrid themes
// that use a mix of block templates and PHP templates.
wp_nav_menu( [
'theme_location' => 'primary',
'menu_class' => 'primary-menu',
'container' => 'nav',
'container_class'=> 'site-nav',
'fallback_cb' => false,
'depth' => 2,
'walker' => new My_Nav_Walker(), // custom walker still supported
] );
// Render the Navigation block from PHP (e.g., in a headless PHP template)
// Pass the post ID of a wp_navigation post (created via the block editor)
function myplugin_render_block_navigation( int $nav_post_id ): string {
return render_block( [
'blockName' => 'core/navigation',
'attrs' => [ 'ref' => $nav_post_id ],
'innerBlocks' => [],
] );
}
Filter Navigation block output to add a dynamic item (e.g., a login/logout link):
%s',
$url,
esc_html( $label )
);
// Insert before closing of the nav list
return str_replace( '', $item . '', $block_content );
}, 10, 2 );
// Filter individual Navigation Link blocks to add attributes
add_filter( 'render_block_core/navigation-link', function( string $content, array $block ): string {
// Mark external links with target="_blank" and rel="noopener noreferrer"
if ( ! empty( $block['attrs']['isTopLevelLink'] ) && ! empty( $block['attrs']['opensInNewTab'] ) ) {
$content = str_replace( '
NOTE: Navigation menus created in Appearance → Menus (classic editor) can be imported into the Navigation block by opening the block in the site editor, clicking the three-dot menu, and selecting "Import Classic Menu". The imported menu becomes a wp_navigation post that you can query with get_posts(['post_type' => 'wp_navigation']).