Get WP Navigation Menu from REST API V2

The question:

I’m trying to get the navigation menu from JSON response using WP REST API v2 plugin.

There is no navigation menu plugin extension for the REST API v2, but only for V1.

From codex WordPress Post Types, I learned that the navigation menu is treated as a post type.

From Rest API Doc, this is how we get posts of a type:


I tried to get it like so:

URL : http://localhost/wptest/wp-json/wp/v2/types/nav_menu_item

I received 403 error.

{"code":"rest_cannot_read_type","message":"Cannot view type.","data":{"status":403}}

the server understood my request but it refused to give the data.

Q: How can I fix this?

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

There is a navigation menu plugin extension for the REST API v2:

Method 2

Since I don’t like it myself when the top answer is “Install plugin X”, here is how I solved it:

Menus are currently not available in the WP Rest. So what you need to do is register your own custom endpoint and then just call that route from your application that needs it.

So you would include something like this (in your functions.php, plugin, wherever):

function get_menu() {
    # Change 'menu' to your own navigation slug.
    return wp_get_nav_menu_items('menu');

add_action( 'rest_api_init', function () {
        register_rest_route( 'myroutes', '/menu', array(
        'methods' => 'GET',
        'callback' => 'get_menu',
    ) );
} );

For the example above you would access the data from:

You can use the above method to create any routes you like to get any type of data that is not available in the WP Rest. Also good if you need to process some data before sending it to your application.

Method 3

@Liren answer works well. However few beginners may not able to adjust the route. Here is the code that works well with WordPress Rest API v2 with minimum modification.

Replace your menu name only in wp_get_nav_menu_items() function. If menu name and slug does not work (Return false), use Menu ID (visible in Dashboard while editing that Menu).

function get_my_menu() {
    // Replace your menu name, slug or ID carefully
    return wp_get_nav_menu_items('Main Navigation');

add_action( 'rest_api_init', function () {
    register_rest_route( 'wp/v2', 'menu', array(
        'methods' => 'GET',
        'callback' => 'get_my_menu',
    ) );
} );

Route URL:

More details covered in Tutorial: WordPress Rest API – Get Navigational Menu Items

Method 4

I opted for the simplest way to do it, still keeping it dynamic so that you can fetch more than one menu.

 * Returns menu items in a array based on the navigation menu id passed
 * @param object The actual request where parameters can be accessed.
 * @return array The menu items contained in that specific menu
function expose_navigation($request) {
  $id = $request['id'];
  return wp_get_nav_menu_items($id);

 * Exposes under /navigation/{id} the menu items in the wp-json api
 * @return void
function expose_navigation_to_rest() {
  register_rest_route( 'wp/v2', '/navigation/(?P<id>d+)', [
      'methods' => 'GET',
      'callback' => 'expose_navigation'

add_action('rest_api_init', 'expose_navigation_to_rest');

So that is easily queriable like /navigation/{id}

Method 5

You need to add 'show_in_rest' => true, while registering post type.

See details here

Method 6

I agree with @Lirens answer, but menus should be called by ID, not slug. Also, the slash before the menu path is not needed. So it becomes something more like this:

function get_menu() {
    # Change '2' to your own navigation ID.
    return wp_get_nav_menu_items(2);

add_action( 'rest_api_init', function () {
    register_rest_route( 'myroutes', 'menu', array(
        'methods' => 'GET',
        'callback' => 'get_menu',
    ) );
} );

Like this it worked for me.

Method 7

I don’t think a plugin should be used for these kind of tasks. Also hkc’s answer is actually not that bad, it only needs some further explanation to make this work with the nav_menu_item post type (the one used for wp navigation menus).

This post type is already registered and thus we need to alter it, this is easily done by hooking into the register_post_type_args filter. This filter allows us to change the arguments for a specific post type. The code below shows just that for the nav_menu_item post type.

add_filter('register_post_type_args', function ($args, $post_type) {
    if ($post_type == 'nav_menu_item' &&
        class_exists('WP_REST_Posts_Controller') &&
        !class_exists('WP_REST_NavMenuItem_Controller')) {

        class WP_REST_NavMenuItem_Controller extends WP_REST_Posts_Controller {
            public function get_items( $request ) {
                $args = wp_parse_args($request, [
                    'order' => 'ASC',
                    'orderby' => 'menu_order',

                $output = [];

                if (empty($request['menu'])) {
                    $menus = get_registered_nav_menus();

                    foreach ( $menus as $location => $description ) {
                        $items = wp_get_nav_menu_items($location, $args);
                        $output = array_merge($output, is_array($items) ? $items : []);
                } else {
                    $items = wp_get_nav_menu_items($request['menu'], $args);
                    $output = array_merge($output, is_array($items) ? $items : []);

                return rest_ensure_response($output);

            public function get_collection_params() {
                $query_params = parent::get_collection_params();
                $query_params['menu'] = [
                    'description' => __( 'The name or also known as theme_location of the menu' ),
                    'type' => 'string',
                return $query_params;

        // Alter the post type arguments
        $args['show_in_rest'] = true;
        $args['rest_controller_class'] = 'WP_REST_NavMenuItem_Controller';
    return $args;
}, 10, 2);

As you might have noticed from the code above, the code does a little bit more than just showing the post type in the REST. It also alters the default Posts REST controller to show a somewhat similar output in the REST as described in Liren’s answer. Although next to that it also does what all post type REST controllers do and thus gives you more control and functionality. Also consider this as a more stable options as it wouldn’t conflict with other REST routes and last but not least, it also way more convenient to work with.

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment