Create Your Own WordPress Menu API
Tags: wordpress, php, rest
Published: 2021-01-06 19:45:09

## 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.

1.  Get a single menu by its ID
2.  Get a single menu by its name (slug)
3.  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
<?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
<?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
<?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
<?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](http://php.net/manual/en/language.oop5.typehinting.php) 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
<?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
<?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](https://discord.gg/ArqzZmqfYZ), and become a part of a growing community of WordPress developers.