Menus

This article is part of the WordPress guide. Read the introduction.

Table of Contents

Menus look simple, but the way they are implemented is surprising. Menus are a standard part of every website. They are the navigation links (usually placed in a <nav> element in the header and the footer). WordPress provides a way of creating a menu in the Appearance > Menus tab. A menu is just a list of links. You can add pages, posts, taxonomies, or even custom links. A theme provides menu locations, kind of like sidebars for widgets. The user then selects a registered place they want their menu to be rendered in. A <ul> element with all the links is then output.

To register a menu location, the theme has to start with calling the register_nav_menus($locations) function. This function takes an associative array as an argument, where the key is a programmatic name of the menu (e.g., header-menu) and the value is the corresponding human readable label (e.g., Header Menu). Upon registration, the menu location becomes available in the “Manage Locations” tab. Every location has a <select> element allowing the user to choose their menu.

To render the menu in a template, the theme has to call the wp_nav_menu( $args ) function. The $args array provides a lot of options. You can specify a user-created menu to be rendered, specify a container HTML element, CSS classes, and even additional text to be rendered before and after every element. The most important option for us is ‘theme_location’. The value associated with that key is the menu location name you want to render (e.g., header-menu). The menu selected for that location by the user will then be displayed.

A code example could be:

PHP
// in functions.php
function thm_register_menus() {
	register_nav_menus( array(
		'header-menu' => 'Header Menu',
		'footer-menu'   => 'Footer Menu',
	) );
}
add_action( 'after_setup_theme', 'thm_register_menus' );

// somewhere in the header.php template
wp_nav_menu( array(
	'theme_location' => 'header-menu',
) );

Everything was simple so far, right? Well, brace yourself. The way menus are stored in the database is probably not what you think. A note before reading this section: you will probably not find much practical use for a deep knowledge of how menus are stored., but it will build your understanding of the WordPress database and what really is possible with its default schema.

The first thing to understand is that menus use 6 tables in the database: wp_terms, wp_term_taxonomy, wp_term_relationships, wp_posts, wp_postmeta, and wp_options.

The menu itself is actually a term. It’s stored in the wp_terms table with its name and slug. It belongs to a special taxonomy called nav_menu, and this connection is stored in the wp_term_taxonomy table. The wp_term_relationships table then links the menu with its items.

If you’re attentive, you might be thinking “hmmm, but the wp_term_relationships table connects terms with posts”. That is exactly right. Every item you add to a menu is a post. More precisely, it’s a post of a special nav_menu_item post type. But the wp_posts table doesn’t have nearly enough columns to cover all the data a menu item requires, such as the ID to the linked post or the custom URL. This is where wp_postmeta shines, storing all this additional information as metadata associated with the menu item.

Okay, so we have our menu stored in the database, but how is it connected to a menu location? Try to remember the way settings were stored in the Customizer. One of the ways of storing a setting was using theme_mod – an option in the wp_options table for the active theme. That is exactly how these assignments are stored. The theme_mod_{theme-slug} row in the wp_options table has a ‘nav_menu_locations’ entry (remember, the entire row is just a serialized array). This entry connects the names of the registered locations with the IDs of the menus stored in the wp_terms table.

Table of Contents