Snippets / WordPress

WordPress register nav menu PHP – Custom Navigation

Register a custom menu location with register_nav_menus(), then render it in your template with wp_nav_menu(). Includes fallback handling and a full production header nav.

PHP template Production-tested ~5 min setup WordPress menus Beginner friendly WP 6.0+ · PHP 8.0+ Updated May 20, 2026
TL;DR

Register a nav menu location in functions.php, then render it in any template with wp_nav_menu().

Use this whenever you need a custom header, footer, or sidebar navigation that editors can manage from Appearance → Menus.

01 — Building it

Building it step by step

Register the location in functions.php first, then build the template output in 4 steps.

We’ll build this in 4 steps. Step 1 registers the menu location in functions.php. Steps 2–4 build the template output progressively — each one works on its own.

Step 1: Register the menu location. Before WordPress will let editors assign a menu, you need to declare the location in functions.php. The key you use here (primary) is what you’ll pass to wp_nav_menu() in every template that renders it.

functions.php
php
<?php
add_action( 'after_setup_theme', function() {
    register_nav_menus( array(
        'primary' => 'Primary Navigation',
        'footer'  => 'Footer Navigation',
    ) );
} );

Step 2: The bare call. wp_nav_menu() with just theme_location is enough to render the assigned menu. WordPress outputs a <div> wrapper and a <ul> by default.

step-2-bare-call.php
php
<?php
wp_nav_menu( array(
    'theme_location' => 'primary',
) );

Step 3: Control the container and class. By default WordPress wraps the menu in a <div class="menu-...">. Replace it with a <nav> for semantic HTML, and set the classes you need for your CSS.

step-3-container-class.php
php
<?php
wp_nav_menu( array(
    'theme_location'  => 'primary',
    'container'       => 'nav',
    'container_class' => 'site-nav',
    'menu_class'      => 'site-nav__list',
    'depth'           => 2,
) );

Step 4: Add a fallback. If no menu has been assigned to the location yet, WordPress falls back to listing all pages — which is almost never what you want. Set fallback_cb to false to output nothing instead of a default page list.

step-4-fallback.php
php
<?php
wp_nav_menu( array(
    'theme_location'  => 'primary',
    'container'       => 'nav',
    'container_class' => 'site-nav',
    'menu_class'      => 'site-nav__list',
    'depth'           => 2,
    'fallback_cb'     => false,
) );

Step 5: Everything together. Steps 2–4 are teaching code. This is the production header — logo, primary nav, and a mobile toggle button, all in one file.

template-parts/header/site-header.php
php
<?php
/**
 * Full file: template-parts/header/site-header.php
 * Steps 2–4 combined — copy this into your theme as the production version.
 */
?>
<header class="site-header">

    <a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="site-header__logo">
        <?php bloginfo( 'name' ); ?>
    </a>

    <?php
    // ── Primary navigation ────────────────────────────────────────────────────
    wp_nav_menu( array(
        'theme_location'  => 'primary',
        'container'       => 'nav',
        'container_class' => 'site-nav',
        'menu_class'      => 'site-nav__list',
        'depth'           => 2,
        'fallback_cb'     => false,
    ) );
    ?>

    <button class="site-header__menu-toggle" aria-expanded="false" aria-controls="site-nav">
        Menu
    </button>

</header>

Include this file in your header.php or wherever you want the nav to appear: get_template_part( 'template-parts/header/site-header' );

02 — How it works

How it works

One explanation per build step — what problem each layer solved.

1

Step 1 — register_nav_menus()

Problem solved: tells WordPress this theme supports named menu locations. Without register_nav_menus(), the Appearance → Menus screen shows no locations and editors have nowhere to assign a menu. The array key ('primary') is the internal slug you use in all PHP templates. The value ('Primary Navigation') is the human-readable label editors see in the admin.

2

Step 2 — The bare wp_nav_menu() call

Problem solved: outputs the menu assigned to the registered location. theme_location links this call to the slug you registered in Step 1. WordPress queries the database for the menu assigned to that location and renders it as an HTML list. No menu assigned yet? The default fallback renders a list of all pages — which is why Step 4 suppresses it.

3

Step 3 — Container and classes

Problem solved: replaces the default <div> wrapper with a <nav> element for semantic HTML. container_class sets the class on the wrapper element; menu_class sets the class on the inner <ul> — that’s the list your CSS typically targets. depth => 2 allows one level of dropdown children; set to 0 for unlimited depth or 1 for flat menus only.

4

Step 4 — Fallback

Problem solved: prevents WordPress from outputting an auto-generated page list when no menu is assigned. By default fallback_cb is set to 'wp_page_menu', which generates a list of all pages. On a fresh install this produces unexpected markup. Setting it to false outputs nothing at all until an editor assigns a real menu — much cleaner during development.

03 — Setup

Setup

Add functions.php code, create the template file, and assign a menu in the admin.

1

Add register_nav_menus() to functions.php

Open your theme’s functions.php and paste the Step 1 code. You can add as many locations as you need — header primary, header secondary, footer, mobile, sidebar. Each key must be unique within the theme. Save the file.

2

Create the template file

Create template-parts/header/site-header.php and paste in the Step 5 production code. Then call it from your header.php:

<?php get_template_part( 'template-parts/header/site-header' ); ?>
3

Assign a menu and test

Go to Appearance → Menus. Create a new menu, add a few pages to it, scroll to Menu Settings → Display location, tick Primary Navigation, and click Save Menu. Reload the front end — the menu should now render inside your <nav class="site-nav"> element. If nothing shows, confirm the location key in PHP matches what you registered exactly.

The field names in the PHP must match ACF exactly — and the same rule applies here: the theme_location string in wp_nav_menu() must match the array key you passed to register_nav_menus() exactly. A typo means the menu never renders.

wp-content/themes/your-theme/
│   ├── template-parts/
│   │   └── header/
│   │       └── site-header.php
    └── functions.php
04 — Customise

Making it your own

Multiple locations, accessibility labels, and conditional rendering.

1

Register multiple locations

Your theme can register as many locations as it needs. Common additions are footer for a slimmer footer nav, mobile for a separate mobile-only menu, and secondary for a utility nav in the header. Each location is assigned independently in the Menus admin — editors can assign different menus to each one.

2

Add aria-label for accessibility

When a page has more than one <nav> element, screen readers need labels to distinguish them. Pass a custom container attribute to wp_nav_menu() using the items_wrap argument, or add the aria-label directly via CSS/JS after render. Alternatively, wrap the wp_nav_menu() call in a <nav aria-label="Primary"> and set container => false to skip the auto-generated wrapper:

wp_nav_menu( array(
    'theme_location' => 'primary',
    'container'      => false,
    'menu_class'     => 'site-nav__list',
    'fallback_cb'    => false,
) );
3

Check if a menu is assigned before rendering

Use has_nav_menu() to conditionally show a section only when a menu is actually assigned:

if ( has_nav_menu( 'primary' ) ) {
    wp_nav_menu( array(
        'theme_location' => 'primary',
        'container'      => 'nav',
        'fallback_cb'    => false,
    ) );
}

Useful for footer menus that are optional — no empty markup is rendered on themes that don’t use the location.

Do not call register_nav_menus() directly in your theme file — always hook it to after_setup_theme. WordPress needs the action to have run before it can detect supported features. Calling it outside the hook can cause the location to not appear in the Menus admin on first load.

0 comments

No comments yet. Be the first.

Leave a Reply

What Is WordPress register nav menu PHP?

The WordPress register nav menu PHP API lets you declare named menu locations in your theme and attach any WordPress menu to them. Every theme that supports navigation relies on two core functions: register_nav_menus() to define the locations and wp_nav_menu() to output them. Understanding WordPress register nav menu PHP is the first step to building fully custom navigation.

Official documentation: register_nav_menus() and wp_nav_menu() on the WordPress Developer Reference.

Step 1 — Register Menu Locations with WordPress register nav menu PHP

Call register_nav_menus() inside after_setup_theme so WordPress knows which locations your theme supports. This is the core of WordPress register nav menu PHP setup — without it, no menus can be assigned in Appearance → Menus.

add_action( 'after_setup_theme', function() {
    register_nav_menus( [
        'primary'  => __( 'Primary Navigation', 'your-theme' ),
        'footer'   => __( 'Footer Navigation', 'your-theme' ),
        'mobile'   => __( 'Mobile Navigation', 'your-theme' ),
    ] );
} );

Step 2 — Output the Menu with WordPress register nav menu PHP

Once locations are registered, call wp_nav_menu() in your template to render them. The WordPress register nav menu PHP pattern pairs registration in functions.php with rendering in the template — keeping logic and markup cleanly separated.

wp_nav_menu( [
    'theme_location' => 'primary',
    'menu_class'     => 'nav-menu',
    'container'      => 'nav',
    'container_class'=> 'site-navigation',
    'depth'          => 2,
    'fallback_cb'    => false,
] );

Step 3 — WordPress register nav menu PHP with Custom Walker

For full control over the markup — for example to add Bootstrap or Tailwind classes — extend Walker_Nav_Menu. This is the advanced form of WordPress register nav menu PHP and gives you per-item HTML control.

class Custom_Walker extends Walker_Nav_Menu {
    public function start_el( &$output, $data_object, $depth = 0, $args = null, $current_object_id = 0 ) {
        $classes  = empty( $data_object->classes ) ? [] : (array) $data_object->classes;
        $classes[] = 'nav-item';
        $class_names = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $data_object, $args, $depth ) );
        $output .= '

Step 4 — Conditional Display and Fallbacks

Always check whether a menu is assigned before rendering. When using WordPress register nav menu PHP in production, set fallback_cb to false so nothing renders if the admin has not yet assigned a menu to that location.

if ( has_nav_menu( 'primary' ) ) {
    wp_nav_menu( [
        'theme_location' => 'primary',
        'fallback_cb'    => false,
    ] );
}

Step 5 — Best Practices for WordPress register nav menu PHP

Keep these rules in mind whenever you implement WordPress register nav menu PHP: always register inside after_setup_theme, never hardcode menu IDs, use has_nav_menu() before rendering, and wrap output in semantic HTML (<nav> with aria-label). For more navigation snippets, browse the full WordPress code snippets collection.

Following these five steps makes your WordPress register nav menu PHP implementation robust, accessible, and easy for clients to manage from the WordPress admin.

Chat on WhatsApp