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
): void
Parameters:
| 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
): void
Output:
</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
): void
Parameters:
| 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
): void
Output:
</li>
build_atts()
Builds an HTML attribute string from an array.
protected function build_atts( array $atts = array() ): string
Parameters:
$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