DEV Community

David Woolf
David Woolf

Posted on • Originally published at indexforwp.com on

How to create your own REST routes in WordPress

WordPress makes registering custom rest routes really easy with a function called register_rest_route. This function provides options to create a namespace (which you can also use to version your APIs), all of your route names, an args array to create CRUD operations and check security, and even set your route to override or merge with existing routes.

Function definition

Let’s review the function parameters and see what we can do:

register_rest_route(
    $namespace, 
    $route, 
    $args, 
    $override
);
Enter fullscreen mode Exit fullscreen mode
  • namespace : a required string which should be the same for all of your routes in the api you are creating
  • route: the required string for the singular API endpoint, which would include some of the following methods: GET, POST, PUT, DELETE
  • args: optional array of options or array of array of options (we’ll go over this in detail)
  • override : optional true or false. True will replace an existing route while false will merge it and override any duplicate methods (defaults to false, so you can normally omit it)

Registering a route

To register a route, we need to call it inside of the rest_api_init hook:

add_action('rest_api_init', 'register_your_routes');

function register_your_routes() {
    register_rest_route(
        //...we'll work in here
    );
}
Enter fullscreen mode Exit fullscreen mode

The first argument is the namespace. It’s important to note that your namespace should not include a beginning or trailing slash, and should be composed of your namespace and version number (for example, WordPress’s built-in routes use wp/v2):

// we'll omit the action hook line from the rest of the examples:
function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        // route name
        // args
    );
}
Enter fullscreen mode Exit fullscreen mode

Next, we will create our route name. This will be the last part of the URL we call to access this route. Because this is a URL, you should name it the way you would see a URL (dashes instead of underscores, etc) and avoid characters like , % [] { } # ( ) * & and spaces:

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint',
        // args
    );
}
Enter fullscreen mode Exit fullscreen mode

Finally, we add our args. Because of the scope of the args parameter, we’ll be going over the basic pieces in the next section

Adding methods, permissions, and a callback function

The most basic use of the args parameter is this:

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint',
        array(
            'methods' => 'GET',
            'callback' => 'callback_function' // this can any function name,
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

This small amount of code will create a new route with a GET method that calls the callback_function function. However, there are a couple of recommendations to make this better:

Permissions

With newer versions of WordPress, you are asked to set the permissions for your routes. It’s not required but you will get a warning message. To create a public route, just add this line to your args array:

array(
    'methods' => 'GET',
    'callback' => 'callback_function',
    'permission_callback' => '__return_true' // new permissions line
);
Enter fullscreen mode Exit fullscreen mode

Another way to set methods

Because there are multiple methods that seem very similar (POST vs PUT etc), WordPress provides some constants as part of the WP_REST_Server class to make your method names more clear:

WP_REST_Server::READABLE // methods: GET
WP_REST_Server::EDITABLE // methods: POST, PUT, PATCH
WP_REST_Server::DELETABLE // methods: DELETE
Enter fullscreen mode Exit fullscreen mode

You don’t need to instantiate the class so to use these just update the first line of your args statement:

array(
    'methods' => WP_REST_Server::READABLE // was previously 'GET'
    'callback' => 'callback_function',
    'permission_callback' => '__return_true'
);
Enter fullscreen mode Exit fullscreen mode

That’s all you need to declare a basic route (minus the callback function of course). Let’s see the code all together:

add_action('rest_api_init', 'register_your_routes');

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint',
        array(
            'methods' => WP_REST_Server::READABLE,
            'callback' => 'callback_function',
            'permission_callback' => '__return_true'
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

Creating your callback function

The callback function for your route is a normal PHP function, but it receives a full $request object as it’s parameter:

function callback_function($request) {
    // do stuff here
}
Enter fullscreen mode Exit fullscreen mode

The $request parameter is a WP_Rest_Request instance and can contain body data, url parameters, and more. Now, let’s look at how we can return some data.

Returning data correctly

If you haven’t looked at our post on using rest_ensure_response I would give it a glance here. You can skip to the end to see an example. The function returns your data with a 200 OK response header and any type of data you pass back (strings, arrays, etc). Here is a useless return value for our new route:

function callback_function($request) {
    return rest_ensure_response('hello world!');
}
Enter fullscreen mode Exit fullscreen mode

If you are working along with this article, you can test this yourself by adding the following to your base URL: /wp-json/ndx/v1/my-endpoint

Note: if you get a 404, it may be your permalinks. Go to Settings > Permalinks in the dashboard and turn on Pretty Permalinks. The specific style doesn’t matter, any of them will ensure /wp-json works correctly

If your route is setup correctly, you should see hello world! in the browser.

Testing URL parameters

Now that we can return data, it would be nice to read data sent along with the API. If you’ve ever used URL parameters before, this should be straightforward. Change your callback function to this:

function callback_function($request) {
    $name = $request->get_param('name');

    return rest_ensure_response("hello {$name}!");
}
Enter fullscreen mode Exit fullscreen mode

Theget_param method is available from our WP_Rest_Response instance, and can be used to read any URL parameters passed with the route. To test this, add the following to your base URL:

/wp-json/ndx/v1/my-endpoint?name=YOUR NAME

You should see “hello YOUR NAME!”

Basic error handling

If you remove the name parameter from the URL the result looks malformed. We can handle this by checking for the name param in our callback function and returning an error if it’s missing:

function callback_function($request) {
    if(null !== $request->get_param('name')) {
        $name = $request->get_param('name');

        return rest_ensure_response("hello {$name}!");
    } else {
        return new WP_Error('missing_fields', 'please include name as a parameter');
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that there is a better way to handle required input, which we’ll cover in our next article about data sanitization, but this is a completely valid way to check for a value. Also, do not try to use isset with the get_param method, as it already checks for this and returns null if it can’t find the parameter.

Adding additional methods to your route

Let’s go back to our route registration code:

add_action('rest_api_init', 'register_your_routes');

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint',
        array(
            'methods' => WP_REST_Server::READABLE,
            'callback' => 'callback_function',
            'permission_callback' => '__return_true'
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

If you wanted to add a POST method for your route, you might think of adding another register_rest_route function declaration. That would required duplicating a lot of code with the same values. Instead, let’s change our args array to be an array of arrays:

add_action('rest_api_init', 'register_your_routes');

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint',
        array(
            array(
                'methods' => WP_REST_Server::READABLE,
                'callback' => 'callback_function',
                'permission_callback' => '__return_true'
            ),
            array(
                'methods' => WP_REST_Server::EDITABLE,
                'callback' => 'another_callback_function',
                'permission_callback' => '__return_true'
            )
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

Notice how we now have two arrays that are very similar, but the second one’s method is EDITABLE and the callback function is different. This means you’ll need to create another function to handle this endpoint:

function another_callback_function($request) {
    return rest_ensure_response('I show up when you call `my-endpoint` with the POST, PUT, or PATCH method');
}
Enter fullscreen mode Exit fullscreen mode

If you add this code and refresh your browser you’ll notice you don’t see this message. That’s because accessing a WordPress API endpoint in the browser is always a GET request. To test other types of requests, you’ll need to use a library or something like Postman. We just posted a thorough tutorial on how to use Postman here.

Note: generally non-GET requests require authentication, but we are passing in our public permission callback, so you can skip that section of the Postman article and just get set up to play around with it.

Once you get set up in Postman, just make sure you change the method to POST, click the “Send” button and you should see this at the bottom:

Requiring values as part of the route

While URL parameters are a nice and flexible feature to send data to your route, you can also add data as part of the endpoint itself. For example, WordPress lets you access posts by going to wp-jons/wp/v2/posts but it also lets you look at a single post by going to /wp-json/wp/v2/posts/<id>. You can chain as many parameters as you want, although you will run into issues if some are required and some are not (what if the first parameter is optional, but the second isn’t?) It’s better to send multiple fields as URL parameters or body data.

If you do want to add a parameter as part of the endpoint, you do that to the $route argument in your register_rest_routefunction:

add_action('rest_api_init', 'register_your_routes');

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint/(?P<id>\d+)', // ADDED ID HERE
        // ...args
    );
}
Enter fullscreen mode Exit fullscreen mode

The argument is wrapped in a regular expression, so let’s break it down:

'(?P<id>\\d+)' // the full argument (parathenses are to group it)
'?P' // denotes that this is a parameter
'<id>' // the name of the parameter
'\\d+' // indicates the paramter should be an integer
Enter fullscreen mode Exit fullscreen mode

While this is hard to read, it does make sure the parameter is defined and has to be an integer. This change means the following calls are either valid or invalid:

/wp-json/ndx/v1/my-endpoint // not valid (missing the parameter)
/wp-json/ndx/v1/my-endpoint/10 // valid
/wp-json/ndx/v1/my-endpoint/hello // not valid (strings are not allowed)
Enter fullscreen mode Exit fullscreen mode

To access the parameter in your callback function, just use get_param like before:

$request->get_param('id');
Enter fullscreen mode Exit fullscreen mode

What if you wanted to make the parameter completely optional, like how WordPress does for posts? Just make a new route! Here’s the full code with our routes we created before the example above, plus our new one:

add_action('rest_api_init', 'register_your_routes');

function register_your_routes() {
    register_rest_route(
        'ndx/v1',
        'my-endpoint',
        array(
            array(
                'methods' => WP_REST_Server::READABLE,
                'callback' => 'callback_function',
                'permission_callback' => '__return_true'
            ),
            array(
                'methods' => WP_REST_Server::EDITABLE,
                'callback' => 'another_callback_function',
                'permission_callback' => '__return_true'
            )
        )
    );

    // our new route
    register_rest_route(
        'ndx/v1',
        'my-endpoint/(?P<id>\d+)',
        array(
            array(
                'methods' => WP_REST_Server::READABLE,
                'callback' => 'callback_function_with_id',
                'permission_callback' => '__return_true'
            )
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

Wrap Up

You are now ready to start creating your own routes! In our next article, we will go over the security features of register_rest_route like custom permissions, sanitizing data, and validating passed-in parameters, so you can create routes for real-world use.

Author

david_woolf image

Discussion (0)