2026年11月12日
2026年11月12日
WordPress Nav Menu Not Working: Missing Menus and Walker Fatal Errors
This article is a localized edition of the Japanese original. Read the Japanese original
In a classic theme, a navigation menu appears in three steps: the theme registers "locations" with register_nav_menus(), you assign a menu to a location in the admin, and wp_nav_menu() in a template outputs it. If any step is missing, you get symptoms such as nothing being displayed or a list of pages appearing instead.
This article explains how to check the current state and how to fix each symptom. The code was tested on WordPress 6.9 with PHP 8.3 (as of October 2026). Back up functions.php before you edit it.
Check the current state first
If WP-CLI is available, the following commands show at once whether locations are registered, whether menus are assigned to them, and whether the menus have items.
# Menu locations registered by the theme
wp menu location list
# Existing menus, their assigned locations and item counts
wp menu list --fields=term_id,name,slug,locations,count
# Items of a menu (replace main with the menu slug or ID)
wp menu item list main --fields=db_id,title,menu_item_parent
Example output:
location description
primary Main navigation
footer Footer menu
term_id name slug locations count
3 Empty empty footer 0
2 Main main primary 3
In this example, Main, which has three items, is assigned to primary. Empty, assigned to footer, has zero items, so nothing is displayed in the footer.
Without WP-CLI, you can check the assignment of each location on the "Manage Locations" tab of "Appearance" → "Menus".
Symptoms and causes at a glance
| Symptom | Common cause |
|---|---|
| There is no "Menus" under "Appearance" | The theme does not call register_nav_menus() (block themes, for example) |
| No locations appear in "Menu Settings" or "Manage Locations" | register_nav_menus() is not called, or the file that calls it is not loaded |
| A list of pages appears instead of the menu | No menu is assigned to the location, or theme_location is misspelled, so the default fallback_cb (wp_page_menu) runs |
| Nothing is displayed | The same causes with fallback_cb set to false, or the assigned menu has no items |
| A blank screen or a fatal error | The method parameters of a custom Walker do not match the parent class |
| Dropdowns do not open | depth is 1, the CSS framework's JavaScript is not loaded, or required classes are missing |
When no locations are registered
In the theme's functions.php, call register_nav_menus() from the after_setup_theme hook. Once you register locations, the theme is treated as supporting menus and "Appearance" → "Menus" appears.
<?php
add_action( 'after_setup_theme', function () {
register_nav_menus(
array(
'primary' => 'Main navigation',
'footer' => 'Footer menu',
)
);
} );
Then build a menu in "Appearance" → "Menus", and either check a location under "Menu Settings" at the bottom of the screen and click "Save Menu", or assign it on the "Manage Locations" tab.
To add locations in a child theme, write the same code in the child theme's functions.php. The key you use in a template's theme_location (here, primary) must match the registered key exactly.
Block themes
Block themes such as Twenty Twenty-Five manage menus with the Navigation block in the Site Editor. Because the theme does not call register_nav_menus(), "Appearance" → "Menus" is not shown. Open "Appearance" → "Editor", select the Navigation block in the header, and edit the menu there. You can import an older classic menu from the menu selector of the Navigation block.
When wp_nav_menu() outputs nothing or a list of pages
When no menu is assigned to the location given in theme_location, wp_nav_menu() calls the function set in fallback_cb. The default is wp_page_menu, which lists your pages. So if your pages are listed instead of the menu you built, either there is no assignment or theme_location is misspelled. With fallback_cb set to false, the same situation outputs nothing.
Also, when the menu assigned to the location has zero items and theme_location is set, nothing is output.
In templates, checking the assignment with has_nav_menu() before output prevents an unintended list of pages.
<?php
if ( has_nav_menu( 'primary' ) ) {
wp_nav_menu(
array(
'theme_location' => 'primary',
'container' => 'nav',
'container_class' => 'main-navigation',
'menu_class' => 'nav-menu',
'depth' => 2,
'fallback_cb' => false,
)
);
}
has_nav_menu() only tells you whether a menu is assigned. It returns true even if the assigned menu has no items, so when nothing appears, also check the item count with the wp menu list command above.
When a custom Walker causes a fatal error
In a class that extends Walker_Nav_Menu, adding type declarations the parent class does not have triggers a fatal error as soon as the class is loaded, and the site stops displaying. For example, if you write this:
<?php
// Wrong: adds type declarations the parent class does not have
class Bootstrap_Nav_Walker extends Walker_Nav_Menu {
public function start_el( string &$output, WP_Post $item, int $depth = 0, stdClass $args = null, int $id = 0 ): void {
// ...
}
}
PHP 8.3 reports the following error:
PHP Fatal error: Declaration of Bootstrap_Nav_Walker::start_el(string &$output, WP_Post $item, int $depth = 0, ?stdClass $args = null, int $id = 0): void must be compatible with Walker_Nav_Menu::start_el(&$output, $data_object, $depth = 0, $args = null, $current_object_id = 0)
The parent class parameters have no type declarations. PHP does not allow an overriding method to narrow parameter types, so adding string or WP_Post makes the signature incompatible. In addition, PHP 8.4 deprecated giving a typed parameter only a null default, as in stdClass $args = null (implicitly nullable types).
The fix is to keep the same parameter order and default values as the parent class, without type declarations. In WordPress 6.9, the Walker_Nav_Menu methods look like this:
public function start_lvl( &$output, $depth = 0, $args = null )
public function end_lvl( &$output, $depth = 0, $args = null )
public function start_el( &$output, $data_object, $depth = 0, $args = null, $current_object_id = 0 )
public function end_el( &$output, $data_object, $depth = 0, $args = null )
Here is the corrected Walker for Bootstrap 5:
<?php
class Bootstrap_Nav_Walker extends Walker_Nav_Menu {
// Use the same parameters as the parent class (no types, same defaults)
public function start_lvl( &$output, $depth = 0, $args = null ) {
$output .= '<ul class="dropdown-menu">';
}
public function start_el( &$output, $data_object, $depth = 0, $args = null, $current_object_id = 0 ) {
$menu_item = $data_object;
$classes = empty( $menu_item->classes ) ? array() : (array) $menu_item->classes;
$has_child = in_array( 'menu-item-has-children', $classes, true );
$current = in_array( 'current-menu-item', $classes, true );
if ( 0 === $depth ) {
$output .= '<li class="nav-item' . ( $has_child ? ' dropdown' : '' ) . '">';
} else {
$output .= '<li>';
}
if ( 0 === $depth && $has_child ) {
$output .= '<a class="nav-link dropdown-toggle" href="' . esc_url( $menu_item->url ) . '" role="button" data-bs-toggle="dropdown" aria-expanded="false">';
} else {
$link_class = $depth > 0 ? 'dropdown-item' : 'nav-link';
$output .= '<a class="' . $link_class . ( $current ? ' active' : '' ) . '" href="' . esc_url( $menu_item->url ) . '"' . ( $current ? ' aria-current="page"' : '' ) . '>';
}
$output .= esc_html( $menu_item->title ) . '</a>';
}
}
end_lvl() (</ul>) and end_el() (</li>) are inherited unchanged from the parent class. Pass the Walker in the template like this:
<?php
wp_nav_menu(
array(
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'navbar-nav',
'depth' => 2,
'walker' => new Bootstrap_Nav_Walker(),
)
);
If the fatal error also locks you out of the admin, temporarily comment out the code over FTP or SSH, or rename the theme folder so WordPress switches to a default theme, and then fix it. To get the full error text, set WP_DEBUG and WP_DEBUG_LOG to true in wp-config.php; errors are then logged to wp-content/debug.log.
When dropdowns do not open
Check these in order:
- Whether
depthinwp_nav_menu()is1. With1, submenus are not output. For Bootstrap dropdowns, use2. - Whether, in "Appearance" → "Menus", the items you want as children are indented to the right under the parent item (one level below it).
- Whether the CSS framework's JavaScript is loaded. Bootstrap 5 needs
bootstrap.bundle.min.js, the link needsdata-bs-toggle="dropdown", and the submenuulneeds thedropdown-menuclass. Check the generated HTML and console errors in your browser's developer tools.
When changes do not show up
If the display does not change after you save the menu, a page cache (a caching plugin, a server-side cache or a CDN) may be serving an old page. Clear the cache and check again.
If you cache the menu HTML yourself, note that classes such as current-menu-item, which mark the current page, differ from page to page. Caching without distinguishing pages shows a menu that still carries another page's classes.
Summary
- Start by checking registration, assignment and item counts with
wp menu location listandwp menu list - If no locations appear, call
register_nav_menus()onafter_setup_theme. Block themes manage menus with the Navigation block - A list of pages appears because there is no assignment or
theme_locationis misspelled, sowp_page_menuruns. Check the assignment withhas_nav_menu()before output - Give custom Walker methods the same parameters and defaults as the parent class, without type declarations
- For dropdowns, check
depth, the menu hierarchy and the JavaScript loading, in that order
Related articles
- How to customize WordPress navigation menus (filters, Walkers and custom fields)
- How to set up WordPress navigation menus (classic and block themes)
- Why a WordPress nav menu does not display, and how to fix it
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)