API

WP API: Adding Custom Endpoints

Here at WDS, we’re expanding our usage of the WP API. We have had a number of API posts in the past, and now we want to cover custom API endpoints more thoroughly.

When working with custom content in WordPress, it is likely that you will come to a point where you want to retrieve that custom data via the WordPress REST API. There are a number of methods that you can use to expose your data via the API. This tutorial aims to explain those methods and provide useful examples.

WP API, WordPress, WordPress tutorials, WordPress REST API, WordPress 101, WordPress how-to, learn WordPress

Post Meta

A common way to add custom data to WordPress is to utilize post meta (preferably using CMB2!). If you want to add this custom post meta to the default API output for a post, this is easy to do with register_rest_field(). For a good explanation of how to use register_rest_field(), as well as example usage, it will be helpful to refer to the REST API Documentation.

Adding Custom Post Types

One of the most common way to create custom data in WordPress is to create a Custom Post Type (CPT). When utilizing CPTs, it is very easy to ensure that your data is accessible via the API. When registering your CPT, you can add a few parameters to the register_post_type() function call:

$args = array(
    // other args...
    'show_in_rest'          => true,
    'rest_base'             => 'foo',
    'rest_controller_class' => 'WP_REST_Posts_Controller',
);

register_post_type( 'wds_foo', $args );

Each of these parameters is optional, and you will typically only ever need to use show_in_rest. The show_in_rest parameter allows the REST API to automatically detect your CPT and add it to the registered API Endpoints. Using only the show_in_rest parameter above will give you this for an endpoint: /wp-json/wp/v2/wds_foo.

Suppose you want the API endpoint to be different from the registered post type for some reason. By using the rest_base parameter, you can modify the endpoint to suit your needs. With this parameter, we now have an endpoint that looks like this: /wp-json/wp/v2/foo.

The final parameter, rest_controller_class, is definitely for more advanced usage. This allows you to change the name of the class that the REST API will use to process your CPT. By default, the API will use WP_REST_Posts_Controller for all CPTs. After a bit more explanation below, I’ll provide an example of why you might need this parameter.

Custom Endpoints

There will be times when you find that you need to register your own API endpoints to handle custom functionality. There are two ways of doing this: basic endpoints and the controller pattern. While the REST API Documentation is very good at covering this topic, I would like to add a bit more commentary and some examples.

But first, what exactly is an endpoint? An endpoint has two components: the route (URL), and the method. In this case, method refers to one of the HTTP Methods. Most people are familiar with the GET method, which is what you used to read this page, as well as the POST method, which is what you use to submit data to a website. In the REST API, the same route can do different things depending on what method is used. For example, the posts route /wp-json/wp/v2/posts can list all of the posts on the site using the GET method. But that same route can be used to create a new post when using the PUT or POST method.

When dealing with endpoints, you first specify the route. You can then specify one or more methods that can be used with that route. With that knowledge, let’s take a look at the two ways of setting up your own endpoints.

Basic Endpoints

Basic endpoints are most useful when you have small or uncomplicated pieces of functionality that you need to create. There are endless use cases that you could come up with, but here are a few ideas:

  • Creating a widget that dynamically updates via Ajax and the API
  • Utilizing a custom search engine to return found posts
  • Retrieving data from a custom database table

To create a basic endpoint, you will need to make use of the register_rest_route() function. This function should be called during the rest_api_init action. Here’s a simple example:

add_action( 'rest_api_init', 'myplugin_register_routes' );

/**
 * Register the /wp-json/myplugin/v1/foo route
 */
function myplugin_register_routes() {
    register_rest_route( 'myplugin/v1', 'foo', array(
        'methods'  => WP_REST_Server::READABLE,
        'callback' => 'myplugin_serve_route',
    ) );
}

/**
 * Generate results for the /wp-json/myplugin/v1/foo route.
 *
 * @param WP_REST_Request $request Full details about the request.
 *
 * @return WP_REST_Response|WP_Error The response for the request.
 */
function myplugin_serve_route( WP_REST_Request $request ) {
    // Do something with the $request

    // Return either a WP_REST_Response or WP_Error object
    return $response;
}

For more details, take a look at the documentation on WP-API.org.

Advanced Endpoints

For a more advanced custom API endpoints, you may find that you need multiple endpoints that work together to create your own functionality. The best way to tie all of these together is by utilizing the Controller Pattern for your endpoints. The Controller Pattern is essentially a template for an entire set of routes that are meant to work together. The various routes and supporting functions are all included in a single class that can handle the details. The WP_REST_Controller class that is provided as part of the REST API is meant to provide basic functionality. By extending this class, you can implement the exact features that you need without having to create your own logic for some of the more mundane details, such as the schema of parameters.

To see an example of an entire implementation of the WP_REST_Controller for a custom set of data, take a look at my API Link Manager code. Additionally, the official documentation is a great resource for examples and best practices.

One other use case of an advanced endpoint is to customize an existing controller to suit your needs. For example, suppose you want to add an API endpoint for your own Custom Post Type, but you don’t want those endpoints to be visible to anyone. One option is to extend the existing WP_REST_Posts_Controller class that is part of the API so that you can tweak it without needing to replace all of its functionality. Below is an example of overriding the register_routes() method in your own class. Specifically note the additional show_in_index keys (lines 42, 49, 61, 68, and 80) of the arrays:

<?php

/**
 * Extend the main WP_REST_Posts_Controller to a private endpoint controller.
 */
class JPry_REST_Private_Posts_Controller extends WP_REST_Posts_Controller {

    /**
     * The namespace.
     *
     * @var string
     */
    protected $namespace;

    /**
     * The post type for the current object.
     *
     * @var string
     */
    protected $post_type;

    /**
     * Rest base for the current object.
     *
     * @var string
     */
    protected $rest_base;

    /**
     * Register the routes for the objects of the controller.
     *
     * Nearly the same as WP_REST_Posts_Controller::register_routes(), but all of these
     * endpoints are hidden from the index.
     */
    public function register_routes() {
        register_rest_route( $this->namespace, '/' . $this->rest_base, array(
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'get_items' ),
                'permission_callback' => array( $this, 'get_items_permissions_check' ),
                'args'                => $this->get_collection_params(),
                'show_in_index'       => false,
            ),
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'create_item' ),
                'permission_callback' => array( $this, 'create_item_permissions_check' ),
                'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
                'show_in_index'       => false,
            ),
            'schema' => array( $this, 'get_public_item_schema' ),
        ) );
        register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'get_item' ),
                'permission_callback' => array( $this, 'get_item_permissions_check' ),
                'args'                => array(
                    'context' => $this->get_context_param( array( 'default' => 'view' ) ),
                ),
                'show_in_index'       => false,
            ),
            array(
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => array( $this, 'update_item' ),
                'permission_callback' => array( $this, 'update_item_permissions_check' ),
                'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
                'show_in_index'       => false,
            ),
            array(
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => array( $this, 'delete_item' ),
                'permission_callback' => array( $this, 'delete_item_permissions_check' ),
                'args'                => array(
                    'force' => array(
                        'default'     => false,
                        'description' => __( 'Whether to bypass trash and force deletion.' ),
                    ),
                ),
                'show_in_index'       => false,
            ),
            'schema' => array( $this, 'get_public_item_schema' ),
        ) );
    }
}

When you’re registering your CPT as I described above, you can now use the name of your custom controller class, and the API will automatically make use of that class to include your endpoints in the API.

These are just a few examples to get you thinking about how much you can do with the REST API, as well as how easy it is to customize the API according to your own needs.

Do you have any questions? Feel free to leave a comment below!

Comments

11 thoughts on “WP API: Adding Custom Endpoints

      1. Hey buddy, very good post, I really love it, I have some issue, 1- I need to add a route that receive data in json(or xml) through PUT method usin “register_rest_route()” and “rest_api_init” in order to insert it in my custom tables of wordpress, I need your help ’cause is not working what I did. 2-Besides that I need to know how to ensure the security in this way.
        Thanks in advance…

  1. Hey buddy, 1- I need to add a route that receive data in json(or xml) through PUT method usin “register_rest_route()” and “rest_api_init” in order to insert it in my custom tables of wordpress, I need your help ’cause is not working what I did. 2-Besides that I need to know how to ensure the security in this way.
    Thanks in advance…

  2. Hi, I’m new in this world of wordpress and I want to do the use the advanced endpoints but after create my custom controller I receive the next aswer:

    {
    “code”: “rest_no_route”,
    “message”: “No route was found matching the URL and request method”,
    “data”: {
    “status”: 404
    }
    }

    And I don’t know why?

    1. Hi Rada,

      There could be a few reasons why you may be seeing a 404 status with a “rest_no_route” code.

      First, are your permalinks set up to use pretty links? This has been documented in various places across the web, but essentially, it’s one of the first things I forget to check myself when I’m encountering 404 errors. Here’s a link from Ryan McCue (one of the REST API contributors), that indicates how you should format your routes whether you’re using pretty permalinks or not: https://wordpress.org/support/topic/rest-api-in-wp-4-7/#post-8620246

      Second, are you trying to access a native WordPress post type or a custom one? If it’s custom, make sure that the arguments that you pass to it include the value ‘show_in_rest’ => true. That setting ensures that your post type is available to the REST API.

      Hopefully, one of these two answers will help you on your way. Good luck!

  3. Hai,

    Is it possible to clone all end points of WordPress API?
    I’m developing an Android app and I’m getting a lot of unwanted data in the response. Since it is not recommended to unset the values from the response, I want to clone the posts end point with my own custom end point.

    How can I do it?

    1. Hi Amar,

      All of the default WordPress routes are registered in /wp-includes/rest-api/endpoints (e.g., WP_REST_Posts_Controller, WP_REST_Terms_Controller, WP_REST_Users_Controller, and so on). While there isn’t a way to “clone” those routes directly, something you could do is write a custom plugin that instantiates a set of classes that each “extends” one of those standard controller classes. From there, you can customize the namespace and overwrite any (or all) of the methods inside those classes to return only the values that you need for your Android app.

      Assuming you set everything up correctly, this would mean you could hit /wp-json/my-base-path/v1/posts instead of /wp-json/wp/v2/posts and get whatever data you assigned to be returned by that path.

      I hope that’s enough information to get you started!

  4. how to access args passed in custom route.

    function myplugin_register_routes() {
    register_rest_route( ‘myplugin/v1’, ‘foo’, array(
    ‘methods’ => WP_REST_Server::READABLE,
    ‘callback’ => ‘myplugin_serve_route’,
    ‘args’ = > array(‘test’=>’test’)
    ) );
    }

    function myplugin_serve_route ($request){
    // how to access args here.
    }

    1. You can access the arguments as part of the route’s attributes:

      function mypluigin_server_route( $request ) {
          $attributes = $request->get_attributes();
          echo $attributes['test']; // test
      }

Have a comment?

Your email address will not be published. Required fields are marked *

accessibilityadminaggregationanchorarrow-rightattach-iconbackupsblogbookmarksbuddypresscachingcalendarcaret-downcartunifiedcouponcrediblecredit-cardcustommigrationdesigndevecomfriendsgallerygoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailmaphealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenewsnotificationsperformancephonepluginprofilesresearcharrowscalablescrapingsecuresecureseosharearrowarrowsourcestreamsupporttwitchunifiedupdatesvaultwebsitewordpress