Walker_Nav_Menu Class
The Walker_Nav_Menu class is responsible for rendering navigation menu HTML. It extends the base Walker class and traverses menu items hierarchically to generate properly nested list markup.
Class Definition
class Walker_Nav_Menu extends Walker {
public $tree_type = array( 'post_type', 'taxonomy', 'custom' );
public $db_fields = array(
'parent' => 'menu_item_parent',
'id' => 'db_id',
);
private $privacy_policy_url;
}Location: wp-includes/class-walker-nav-menu.php
Since: WordPress 3.0.0
Properties
$tree_type
public $tree_type = array( 'post_type', 'taxonomy', 'custom' );Defines what types of content this walker handles. Matches menu item types.
$db_fields
public $db_fields = array(
'parent' => 'menu_item_parent',
'id' => 'db_id',
);Maps walker fields to menu item properties:
parent– Field containing parent item IDid– Field containing item’s unique ID
$privacy_policy_url
private $privacy_policy_url;Cached privacy policy URL. Set in constructor and used to add rel="privacy-policy" to privacy policy links.
Methods
__construct()
public function __construct()Constructor that initializes the privacy policy URL.
Since: WordPress 6.8.0
start_lvl()
Opens a new sub-menu level.
public function start_lvl(
string &$output,
int $depth = 0,
stdClass $args = null
): voidParameters:
| Parameter | Type | Description |
|---|---|---|
$output | string | HTML output (by reference) |
$depth | int | Nesting depth (0-based) |
$args | stdClass | wp_nav_menu() arguments |
Output:
<ul class="sub-menu">Filters Applied:
nav_menu_submenu_css_class– Modify sub-menu classesnav_menu_submenu_attributes– Modify sub-menu HTML attributes
Example Override:
class Custom_Walker extends Walker_Nav_Menu {
public function start_lvl( &$output, $depth = 0, $args = null ) {
$indent = str_repeat( "t", $depth );
$output .= "n{$indent}<ul class="dropdown-menu">n";
}
}end_lvl()
Closes a sub-menu level.
public function end_lvl(
string &$output,
int $depth = 0,
stdClass $args = null
): voidOutput:
</ul>start_el()
Renders a menu item’s opening tag and content.
public function start_el(
string &$output,
WP_Post $data_object,
int $depth = 0,
stdClass $args = null,
int $current_object_id = 0
): voidParameters:
| Parameter | Type | Description |
|---|---|---|
$output | string | HTML output (by reference) |
$data_object | WP_Post | Menu item post object |
$depth | int | Nesting depth (0-based) |
$args | stdClass | wp_nav_menu() arguments |
$current_object_id | int | Current item ID (optional) |
Output Structure:
<li id="menu-item-123" class="menu-item menu-item-type-post_type ...">
<a href="/page/" aria-current="page">Page Title</a>Filters Applied (in order):
nav_menu_item_args– Modify args for this itemnav_menu_css_class– Modify item’s CSS classesnav_menu_item_id– Modify item’s ID attributenav_menu_item_attributes– Modify<li>attributesthe_title– Filter the titlenav_menu_item_title– Filter title again (menu-specific)nav_menu_link_attributes– Modify<a>attributeswalker_nav_menu_start_el– Modify entire item output
Link Attributes Built:
| Attribute | Source |
|---|---|
href | $menu_item->url |
target | $menu_item->target |
rel | $menu_item->xfn (+ privacy-policy if applicable) |
aria-current | 'page' if current item |
title | $menu_item->attr_title (if different from title) |
Example Override:
class Bootstrap_Walker extends Walker_Nav_Menu {
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
$indent = ( $depth ) ? str_repeat( "t", $depth ) : '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'nav-item';
if ( in_array( 'menu-item-has-children', $classes ) ) {
$classes[] = 'dropdown';
}
$class_names = implode( ' ', array_filter( $classes ) );
$output .= $indent . '<li class="' . esc_attr( $class_names ) . '">';
$atts = array(
'href' => $item->url,
'class' => 'nav-link',
);
if ( $item->current ) {
$atts['class'] .= ' active';
$atts['aria-current'] = 'page';
}
if ( in_array( 'menu-item-has-children', $item->classes ) && $depth === 0 ) {
$atts['class'] .= ' dropdown-toggle';
$atts['data-bs-toggle'] = 'dropdown';
$atts['aria-expanded'] = 'false';
}
$attributes = $this->build_atts( $atts );
$output .= '<a' . $attributes . '>';
$output .= $args->link_before . $item->title . $args->link_after;
$output .= '</a>';
}
}end_el()
Closes a menu item.
public function end_el(
string &$output,
WP_Post $data_object,
int $depth = 0,
stdClass $args = null
): voidOutput:
</li>build_atts()
Builds an HTML attribute string from an array.
protected function build_atts( array $atts = array() ): stringParameters:
$atts– Associative array of attribute => value pairs
Returns: String like class="nav-link" href="/page/"
Behavior:
- Empty strings and
falsevalues are ignored hrefvalues are passed throughesc_url()- Other values are passed through
esc_attr()
Since: WordPress 6.3.0
Example:
$atts = array(
'href' => 'https://example.com',
'class' => 'nav-link',
'title' => '', // Will be ignored
);
$string = $this->build_atts( $atts );
// Returns: ' href="https://example.com" class="nav-link"'Default HTML Output
For a typical menu, the walker produces:
<ul id="menu-main" class="menu">
<li id="menu-item-10" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-item menu-item-10">
<a href="/about/" aria-current="page">About</a>
</li>
<li id="menu-item-11" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-11">
<a href="/services/">Services</a>
<ul class="sub-menu">
<li id="menu-item-12" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-12">
<a href="/services/consulting/">Consulting</a>
</li>
<li id="menu-item-13" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-13">
<a href="/services/development/">Development</a>
</li>
</ul>
</li>
<li id="menu-item-14" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-14">
<a href="https://external.com" target="_blank" rel="noopener">External Link</a>
</li>
</ul>Custom Walker Examples
Mega Menu Walker
class Mega_Menu_Walker extends Walker_Nav_Menu {
public function start_lvl( &$output, $depth = 0, $args = null ) {
if ( $depth === 0 ) {
$output .= '<div class="mega-menu"><div class="mega-menu-inner"><ul class="mega-menu-list">';
} else {
$output .= '<ul class="sub-menu">';
}
}
public function end_lvl( &$output, $depth = 0, $args = null ) {
if ( $depth === 0 ) {
$output .= '</ul></div></div>';
} else {
$output .= '</ul>';
}
}
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
$classes = array_filter( (array) $item->classes );
if ( $depth === 0 && in_array( 'menu-item-has-children', $classes ) ) {
$classes[] = 'has-mega-menu';
}
$output .= '<li class="' . implode( ' ', $classes ) . '">';
// Add description for top-level items
if ( $depth === 1 && $item->description ) {
$output .= '<a href="' . esc_url( $item->url ) . '">';
$output .= '<strong>' . $item->title . '</strong>';
$output .= '<span class="description">' . $item->description . '</span>';
$output .= '</a>';
} else {
$output .= '<a href="' . esc_url( $item->url ) . '">' . $item->title . '</a>';
}
}
}Accessible Mobile Walker
class Accessible_Mobile_Walker extends Walker_Nav_Menu {
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
$classes = array_filter( (array) $item->classes );
$has_children = in_array( 'menu-item-has-children', $classes );
$output .= '<li class="' . implode( ' ', $classes ) . '">';
if ( $has_children ) {
// Button to toggle submenu
$output .= '<div class="menu-item-link-wrapper">';
$output .= '<a href="' . esc_url( $item->url ) . '">' . $item->title . '</a>';
$output .= '<button class="submenu-toggle" aria-expanded="false" aria-label="' .
sprintf( esc_attr__( 'Toggle submenu for %s' ), $item->title ) . '">';
$output .= '<span class="toggle-icon"></span>';
$output .= '</button>';
$output .= '</div>';
} else {
$output .= '<a href="' . esc_url( $item->url ) . '">' . $item->title . '</a>';
}
}
public function start_lvl( &$output, $depth = 0, $args = null ) {
$output .= '<ul class="sub-menu" aria-hidden="true">';
}
}Icon Menu Walker
class Icon_Menu_Walker extends Walker_Nav_Menu {
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
$classes = array_filter( (array) $item->classes );
// Look for icon class (e.g., "icon-home")
$icon_class = '';
foreach ( $classes as $class ) {
if ( strpos( $class, 'icon-' ) === 0 ) {
$icon_class = $class;
break;
}
}
$output .= '<li class="' . implode( ' ', $classes ) . '">';
$output .= '<a href="' . esc_url( $item->url ) . '">';
if ( $icon_class ) {
$output .= '<i class="' . esc_attr( $icon_class ) . '" aria-hidden="true"></i>';
}
$output .= '<span class="menu-text">' . $item->title . '</span>';
$output .= '</a>';
}
}Using Custom Walkers
// In template
wp_nav_menu( array(
'theme_location' => 'primary',
'walker' => new Bootstrap_Walker(),
) );
// With filter
add_filter( 'wp_nav_menu_args', function( $args ) {
if ( $args['theme_location'] === 'primary' ) {
$args['walker'] = new Custom_Walker();
}
return $args;
} );Performance Considerations
- Walker instances are reused across menu calls
- The
build_atts()method efficiently handles attribute escaping - Privacy policy URL is cached in constructor
- Item spacing (
preserve/discard) affects output size
Related Classes
Walker– Base walker class (wp-includes/class-wp-walker.php)Walker_Page– Similar walker for wp_list_pages()Walker_Category– Walker for category lists