WordPress REST API: Navigation Menu Items

A headless site (a React or Vue front end with WordPress as the back end) needs the menu of WordPress too. WordPress has routes for menus, menu items and menu locations. Let us make a menu and read it.

Create a menu

Terminal output

$ wp menu list –fields=term_id,name,slug,count
term_id name slug count
2 Main Menu main-menu 3
$ wp menu item list main-menu –fields=db_id,title,type,link
db_id title type link
6 About us post_type http://example.com/about/
7 Blog custom http://example.com/blog/
8 Contact custom http://example.com/contact/

The menu routes need a login

The routes /wp/v2/menus, /wp/v2/menu-items and /wp/v2/menu-locations are only for users who can edit the theme options, so a guest gets an error:

Terminal output

$ curl -s "http://example.com/wp-json/wp/v2/menu-items" | jq -c '{code, status: .data.status}'
{"code":"rest_cannot_view","status":401}
$ curl -s "http://example.com/wp-json/wp/v2/menus" | jq -c '{code, status: .data.status}'
{"code":"rest_cannot_view","status":401}

Read the menu with a login

Terminal output

$ curl -s -u "admin:APP_PASSWORD" "http://example.com/wp-json/wp/v2/menus?_fields=id,name,slug" | jq -c '.[]'
{"id":2,"name":"Main Menu","slug":"main-menu"}
$ curl -s -u "admin:APP_PASSWORD" "http://example.com/wp-json/wp/v2/menu-items?menus=2&_fields=id,title.rendered,url,parent,menu_order,type" | jq -c '.[]'
{"id":6,"title":{"rendered":"About us"},"url":"http://example.com/about/","type":"post_type","parent":0,"menu_order":0}
{"id":7,"title":{"rendered":"Blog"},"url":"http://example.com/blog/","type":"custom","parent":0,"menu_order":2}
{"id":8,"title":{"rendered":"Contact"},"url":"http://example.com/contact/","type":"custom","parent":0,"menu_order":3}

The menus parameter filters the items of one menu (use the ID of the menu). The parent value (0 for the top level) lets you build the sub menus. A link to a post, page or category also has an object_id.

Menu locations

Terminal output

$ curl -s -u "admin:APP_PASSWORD" "http://example.com/wp-json/wp/v2/menu-locations" | jq -c .
[]

The answer is empty because our block theme does not register any menu location. A classic theme with register_nav_menus() would be listed here. See Register Nav Menus.

Make a public menu endpoint

Most of the times a public site needs to show the menu to all visitors, without a login. The best way is to make your own small endpoint which returns only the data you want to show:

<?php
/**
 * Plugin Name: TC Public Menu API
 */
add_action( 'rest_api_init', function () {
	register_rest_route( 'tc/v1', '/menu/(?P<slug>[a-z0-9-]+)', array(
		'methods'             => 'GET',
		'permission_callback' => '__return_true',
		'callback'            => function ( WP_REST_Request $request ) {
			$items = wp_get_nav_menu_items( $request['slug'] );
			if ( ! $items ) {
				return new WP_Error( 'tc_no_menu', 'Menu not found', array( 'status' => 404 ) );
			}
			return array_map( function ( $item ) {
				return array(
					'id'     => $item->ID,
					'title'  => $item->title,
					'url'    => $item->url,
					'parent' => (int) $item->menu_item_parent,
				);
			}, $items );
		},
	) );
} );
Terminal output

$ wp plugin activate tc-menu-api
Plugin 'tc-menu-api' activated.
Success: Activated 1 of 1 plugins.
$ curl -s "http://example.com/wp-json/tc/v1/menu/main-menu" | jq -c '.[]'
{"id":6,"title":"About us","url":"http://example.com/about/","parent":0}
{"id":7,"title":"Blog","url":"http://example.com/blog/","parent":0}
{"id":8,"title":"Contact","url":"http://example.com/contact/","parent":0}
$ curl -s "http://example.com/wp-json/tc/v1/menu/nope" | jq -c '{code, message}'
{"code":"tc_no_menu","message":"Menu not found"}

Menus in block themes

Block themes keep menus as wp_navigation posts, and the REST route for them is /wp/v2/navigation. It also needs a login:

Terminal output

$ curl -s -u "admin:APP_PASSWORD" "http://example.com/wp-json/wp/v2/navigation?_fields=id,title.rendered,status" | jq -c '.[]'
{"id":4,"status":"publish","title":{"rendered":"Navigation"}}

Note: Only expose what the visitors need. The public menu endpoint above shows titles and URLs, nothing private. You have finished the REST API section. Go back to the full list of chapters from the WordPress Tutorial page.

Congratulations! Chapter Finished. Learn more about the similar topics:
Exercises & Assignments
Get the latest posts with the REST API
Create a post with the REST API and an application password
Build a custom REST endpoint which greets a name
Interview Questions & Answers
What is the WordPress REST API?
How to create a custom REST API endpoint in WordPress?