Alex Standiford
Posted on January 6, 2021
What We’ll Be Doing
We’re going to create 3 REST API endpoints, designed to allow us to get our menus in a couple of different ways.
- Get a single menu by its ID
- Get a single menu by its name (slug)
- Get a single menu by its theme location
We’re going to create a single class, NavMenu
, to handle these functions.
I should mention that one limitation this build has is that it doesn’t provide a way to filter results by the
parent. I didn’t use submenus in my personal build of my site, so I didn’t bother adding this functionality. Children
menu items will show up with this build, but you will have to filter out the results with Javascript’s array.filter()
method, or just build that directly into the endpoint via an extra argument, or something like that. I’m sure there’s a
better database query that can be run to only get the results where the parent is X, but that’s outside of the scope of
this lesson.
Build Your NavMenu Class
In my experience, it’s generally easier to create the class that will handle the functionality first, and then build the static methods that will be used as the api callbacks later. Create a new file called NavMenu.php
and drop it in your plugin. Be sure to include it in your core plugin file!
<?php
/**
* Gets a nav menu
* @author: Alex Standiford
* @date : 1/13/18
*/
namespace asmenus\lib\app;
class NavMenu{
public function __construct($menu_name_or_id{
$this->menus = wp_get_nav_menu_items($menu_name_or_id);
}
}
?>
We’ll start simple, and grab the list of menu items using wp_get_nav_menu_items()
Luckily, this function will work with
either a menu name, or a menu ID, which makes our constructor pretty simple for now.
Also note that we are using a namespace called asmenus\lib\app
. This helps to reduce the probability of name collisions
with our code and other plugins, and is why I didn’t name the class something like ASNavMenu
.
Right now, if we run var_dump(new asmenus\lib\app\NavMenu($menu_name_or_id));
where you replace $menu_name_or_id
with a valid menu name or ID, it will dump out all of the relevant information related to the items in that menu.
Create a Way To Get a Menu By its Location
Now that we have a way to get the menu by the menu ID and the menu slug, we need to create a way to get a menu by its
location. With a little digging, I discovered that the menu locations and their correlated menu IDs are stored in the
database as a theme mod. To access that, we just need to add a method to our class.
<?php
/**
* Gets a nav menu
* @author: Alex Standiford
* @date : 1/13/18
*/
namespace asmenus\lib\app;
class NavMenu{
public function __construct($menu_name_or_id){
$this->menus = wp_get_nav_menu_items($menu_name_or_id);
}
/**
* Gets the menu ID from the specified location
*
* @param $location
*
* @return mixed
*/
public function getMenuIdFromLocation($location){
$locations = get_theme_mod('nav_menu_locations');
return $locations[$location];
}
}
?>
Here, we added a new method, getMenuIdFromLocation
. This method uses get_theme_mod
returns an associative array, with
the menu IDs keyed by the menu location. So, by passing a $location
parameter, we can get the desired menu ID.
Now, we just need to use this function in our constructor.
<?php
/**
* Gets a nav menu
* @author: Alex Standiford
* @date : 1/13/18
*/
namespace asmenus\lib\app;
class NavMenu{
public function __construct($menu_name_or_id, $type = 'menu'){
if($type == 'location') $menu_name_or_id = $this->getMenuIdFromLocation($menu_name_or_id);
$this->menus = wp_get_nav_menu_items($menu_name_or_id);
}
/**
* Gets the menu ID from the specified location
*
* @param $location
*
* @return mixed
*/
public function getMenuIdFromLocation($location){
$locations = get_theme_mod('nav_menu_locations');
return $locations[$location];
}
}
?>
On our constructor, we add an optional second argument, $type
, which if the type is set to 'location'
, our
constructor will use the getMenuIdFromLocation
to get the correct menu ID based on the provided location in our first
argument, $menu_name_or_id
.
Right now, if we run var_dump(new asmenus\lib\app\NavMenu($menu_location,'location'));
where you replace
$menu_location
with a valid menu location, it will dump out all of the relevant information related to the items in
that menu.
Create the REST API Callback Functions
Now that our class is capable of getting our menu details, let’s create our functions that will be used by our rest
endpoints when our endpoint is visited.
<?php
/**
* Gets a nav menu
* @author: Alex Standiford
* @date : 1/13/18
*/
namespace asmenus\lib\app;
class NavMenu{
public function __construct($menu_name_or_id, $type = 'menu'){
if($type == 'location') $menu_name_or_id = $this->getMenuIdFromLocation($menu_name_or_id);
$this->menus = wp_get_nav_menu_items($menu_name_or_id);
}
/**
* Gets the menu ID from the specified location
*
* @param $location
*
* @return mixed
*/
public function getMenuIdFromLocation($location){
$locations = get_theme_mod('nav_menu_locations');
return $locations[$location];
}
/**
* Callback function to get the menu by name/id via the REST API
*
* @param \WP_REST_Request $request
*
* @return array|\WP_Error
*/
public static function getMenuFromApi(\WP_REST_Request $request){
$name_or_id = $request->get_param('name') ? $request->get_param('name') : (int) $request->get_param('id');
$self = new self($name_or_id);
return $self->menus;
}
}
?>
We’ll start with the first method, which will handle getting the menu regardless of if it is a name or an id. As you can
see, this is a static method (in other words, we can access it without instantiating our class first), and the first
parameter is type-hinted with \WP_REST_Request
. If you’re
using a namespace, be sure to remember to add the \
beforehand, otherwise it won’t work.
$name_or_id
is set with a ternary operator – which allows us to get the argument regardless of if it is a name or an
id. You could have built this out as two separate methods, but I felt this was cleaner.
Our Completed Class
Now, we just need to repeat this process with a second static method that will get the menu by location.
<?php
/**
* Gets a nav menu
* @author: Alex Standiford
* @date : 1/13/18
*/
namespace asmenus\lib\app;
class NavMenu{
public function __construct($menu_name_or_id, $type = 'menu'){
if($type == 'location') $menu_name_or_id = $this->getMenuIdFromLocation($menu_name_or_id);
$this->menus = wp_get_nav_menu_items($menu_name_or_id);
}
/**
* Gets the menu ID from the specified location
*
* @param $location
*
* @return mixed
*/
public function getMenuIdFromLocation($location){
$locations = get_theme_mod('nav_menu_locations');
return $locations[$location];
}
/**
* Callback function to get the menu by slug/id via the REST API
*
* @param \WP_REST_Request $request
*
* @return array|\WP_Error
*/
public static function getMenuFromApi(\WP_REST_Request $request){
$name_or_id = $request->get_param('name') ? $request->get_param('name') : (int) $request->get_param('id');
$self = new self($name_or_id);
return $self->menus;
}
/**
* Callback function to get the menu by location via the REST API
*
* @param \WP_REST_Request $request
*
* @return array|\WP_Error
*/
public static function getMenuByLocationFromApi(\WP_REST_Request $request){
$location = $request->get_param('location');
$self = new self($location, "location");
return $self->menus;
}
}
?>
The only difference here is that when we instantiated the class, we set the second parameter to 'location'
. This tells
our constructor to look up the ID based on the location provided
Register Our Endpoints
Now that our class is built out, we just need to register our endpoints. This should not be added to NavMenu.php
,
instead this should be either placed in the core plugin file, or in a separate file that is included afterward. In the
example below, I put them in the core plugin file.
<?php
/**
Plugin Name: WordPress Menu REST API Endpoints
Description: Creates RESTful endpoints for WordPress menus
Version: 1.0
Author: Alex Standiford
Author URI: http://www.alexstandiford.com
**/
if(!defined('ABSPATH')) exit;
require_once(plugin_dir_path(__FILE__).'NavMenu.php');
/**
* Registers our api endpoints
*/
add_action('rest_api_init', function(){
/**
* Gets a single menu by ID
*/
register_rest_route('asmenus/v1', '/menu/(?P<id>[\d]+)', [
'methods' => ['GET'],
'callback' => 'asmenus\lib\app\NavMenu::getMenuFromApi',
]);
/**
* Gets a single menu by slug
*/
register_rest_route('asmenus/v1', '/menu/(?P<name>[\w-_]+)', [
'methods' => ['GET'],
'callback' => 'asmenus\lib\app\NavMenu::getMenuFromApi',
]);
/**
* Gets a single menu by its theme location
*/
register_rest_route('asmenus/v1', '/menu/location/(?P<location>[\w-_]+)', [
'methods' => ['GET'],
'callback' => 'asmenus\lib\app\NavMenu::getMenuByLocationFromApi',
]);
});
?>
To register our endpoints, we hook into 'rest_api_init'
, and register our routes in the callback, using
register_rest_route
. Notice that our callbacks have the namespace prepended to them, and that we are setting the
method for these endpoints to GET
.
Voila! When you visit any of the endpoints, you should now get an array of objects containing all of the information
about each menu item. From here, you could build out some React/Angular components that loop through and build your menu.
Looking for more WordPress Resources?
Join WP Dev Academy’s Discord server, and become a part of a growing community of WordPress developers.
Posted on January 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.