Demystifying the WordPress Rest API Endpoints – Part I

This post has been revised to reflect the merging of the core endpoints into WordPress 4.7

The WordPress REST API is a very ambitious project that will not only benefit the WordPress ecosystem, but ultimately, the entire web. This guide will focus on the structure of core endpoints, which were merged in WordPress 4.7, and will dive into registering and writing endpoints of your own. This guide assumes you have a basic understanding of why you would want to use the WordPress REST API. Let’s briefly break down where the WordPress REST API project currently stands.

What is the WordPress REST API?

The WordPress REST API is an attempt to expand and modernize how people consume and interact with WordPress. The project is largely spearheaded by Lead Developers Ryan McCue, and Rachel Baker, along with contributing developers Daniel Bachhuber, and Joe Hoyle. There are many more contributors to the project as well. The project is comprised of two parts: infrastructure and endpoints.

The WordPress REST API infrastructure was merged into WordPress core in version 4.4 and the core endpoints were merged in WordPress 4.7.  The core endpoints are registered via controller classes and at first glance, they can be somewhat daunting to comprehend. This guide will hopefully demystify confusion and bring clarity regarding the flexibility the API endpoint design provides.

Endpoint Development

It’s hard to say what the major adoption point of the WordPress REST API will be, given its incredibly flexible nature; akin to WordPress. Registering your own endpoints is incredibly powerful, but there are some key concepts to understand before diving in too deep. There are three important endpoint components: routes, schema, and controllers.

Routes

Let’s talk routes! The WordPress REST API provides a way to interact with WordPress internals solely over HTTP (hopefully you are using S!). What does that mean? Basically, you send an HTTP request to an endpoint route, let’s say GET https://example.com/wp-json/wp/v2/posts/1 and you are returned a post object as JSON. Magic. Now we can break up the example endpoint route into smaller components.

  • Protocol: The https:// represents the protocol that is used to make the request.
  • Domain: The example.com/ portion is the desired domain.
  • API Index: wp-json/ is the index endpoint where the WP REST API lives. Accessing this index exposes the available namespaces, routes, methods and arguments that can be used to communicate with WordPress through the API. This endpoint becomes a source for programmatic access to each WordPress installation, allowing clients to see how unknown sites have configured their WP REST API. The process of utilizing the index data is known as discovery. Super neat stuff. Discovery opens up all kinds of possibilities. The API index provides a machine readable blueprint of your WordPress install, enabling external apps to integrate more easily with WordPress and on the flip-side it enables your WordPress install to more easily digest content from other platforms by using a common communication interface of HTTP; all automatically configured for you. Note: there are not currently many auto discovery client libraries built yet, but the possibility is there.
  • Namespace Index: The wp/v2/ portion represents the namespace index. This endpoint does all of the same things as the API endpoint but for a specific namespace. Each namespace is registered via register_rest_route(), which we will get to later.  For good form you should be registering your endpoints in a namespace other than wp/v2/
  • REST Base: The posts/ portion represents the rest base for the route. Typically each rest base route serves as the endpoints for collections and creating new resources; i.e. GET example.com/wp-json/wp/v2/posts, will return a collection of posts. Likewise, POST example.com/wp-json/wp/v2/posts?content=OMG&title=RESTAPIisAwesome, will return a newly created post. You may be wondering if anyone can just come in and make a post on your blog? Thankfully, no. The WordPress REST API can use many different Authentication/Authorization methods to make sure certain actions and data are only accessible to particular users. Auth is outside the scope of this guide, but we will touch on it here and there.

Let’s reflect. As you can see the URI structure has endpoints tacked onto endpoints, in an intuitive fashion. The namespace and rest base serve as a way to separate different branches of what your API can do and what resources are available.

All responses from the API are returned in JSON formatting. JSON is an open standard format of human readable data. This seemingly minute detail, in tandem with the use of HTTP, is what makes the API so powerful. Now, WordPress can be more easily consumed by different programming languages and application infrastructure, helping spread the awesomeness of WordPress to a much broader audience, which in turn will diversify and enrich the WordPress community with new ideas and people. Rather grandiose for such a simple thing.

Let’s dig into routes a little bit more. RESTful infrastructure is based around HTTP. Originally, in HTTP 1.0, there were three methods: GET, HEAD, and POST.  POST ended up becoming a catchall of sorts for many kinds of requests. HTTP 1.1 introduced a number of more descriptive methods, often referred to as HTTP verbs, which enable us to have a more semantic way to interact with APIs via HTTP.  The WordPress REST API makes use of POST, for creating, GET for reading, PUT for updating, and DELETE, which is self-explanatory. OPTIONS requests will return a very special kind of data; our schema.

Schema

Before we write our own endpoints, it is important to understand schema and its significance. Schema is metadata. It describes the current resource. An OPTIONS request to the posts endpoint will spit back the namespace, allowed HTTP methods for endpoints and most importantly the schema. Schema data is currently provided via JSON Schema, so we have JSON telling us how our other JSON data is structured. Kind of silly, but extremely important.

Let’s take a quick step back. The WordPress REST API in some ways can be viewed as taking SQL data and transforming it into JSON. MySQL, and other RDBMSs by default provide data with schema.  Each column has certain criteria that describes what its content can be.  Most Primary Key columns are INT NOT NULL AUTO_INCREMENT or something similar.  That information tells us and the database that the data for that column is supposed to be an integer within a certain range of values, can’t be an empty value, and in each row the value will automatically increment, creating a unique ID for each row. Schema adds constraints. Why does that matter?

You may have heard all the rage about MongoDB or other NoSQL databases, but there are many, many drawbacks to them and the endpoints that we create also suffer from similar problems.  Obviously NoSQL has its merit, and when implemented properly, it is incredible, but that is an entirely different discussion (the more I learn about NoSQL the less I want to use any SQL).

Without schema we have no way to understand any of our data. Let’s take a simple JSON object.


{
    "shouldBeString": 3,
    "shouldBeArray": "LOLz you need schema.",
    "shouldBeInt": ["I","is","array"]  
}

This is perfectly valid JSON and any parser will continue along as if there is no problem. There is really no way for us, let alone a machine, to tell what the structure of the data should be in the object above. It is arbitrary (even the key names). We can’t validate our data or form logical relations in our application without schema. Well you can, sort of, not really, not recommended, but schema just makes it a whole lot easier and more practical. Reading more about the JSON schema draft 4 spec is highly recommended.  For a friendly JSON schema intro check this out.

This hopefully outlines the basic importance of schema, which should be created for every endpoint. How do we add said schema? The best way is to follow the structure of the controller classes for the WordPress REST API.

Controllers

Each core endpoint in the WordPress REST API uses a controller class to manage its functionality.  By following the patterns presented in the controller classes, you will be taking advantage of a lot of foresight regarding the usability of your endpoints. First lets talk about some of the WP REST Controller methods.  There are quite a few and it can be quite confusing what exactly their relevance is at first.

WP_REST_Controller Methods

public WP_REST_Controller::__construct()

This is the object constructor method for an endpoint controller class.  For most use cases, you will only do two things in the constructor: set $this->namespace to your desired namespace, and set $this->rest_base to the desired rest_base of your endpoint. If you are doing an endpoint for a custom post type you can register $this->post_type or potentially just do this WP_REST_Posts_Controller( 'my-cpt' ). If you are working with custom post types I highly recommend just extending WP_REST_Posts_Controller and overwriting what methods you need to. The constructor should just set up some basic properties that will be used throughout your endpoints.

public WP_REST_Controller::register_routes()

This is an extremely important part to writing the controller class for your endpoint. This is where we will be calling register_rest_route() for each of our routes. For a collection it would look something like this.

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(),
    ),
    'schema' => array( $this, 'get_public_item_schema' ),
) );

The first parameter is the namespace where the endpoint lives. We defined the namespace in our constructor method. Next comes a leading slash concatenated to our rest base, which we also defined in our constructor. If we had a digitized library of books maybe these first two parameters would make something like this: library/v1/books. library/v1 is our namespace and books is our rest base. Now, we need to know what can happen at /wp-json/library/v1/books. In register_rest_route() the third parameter can either be an array of options for the endpoint or an array of arrays of options supporting different HTTP methods.

In the above example we are registering an endpoint route that supports GET, HEAD, and OPTIONS requests. The first array provides the options for our collection endpoint. It supports the GET method. Any GET request to this route will trigger our permission check and $this->get_items() callback. We can pass special arguments to this endpoint, which will be documented in $this->get_collection_params(). Last the schema is registered to the route, so when an OPTIONS request is made, the schema is exposed.

public WP_REST_Controller::get_items_permissions_check( $request )

This is a permissions check for the collection endpoint we have created. You can use logic like.

if ( ! current_user_can( 'edit_posts' ) ) {
    return new WP_Error( 'rest_forbidden', esc_html__( 'Sorry, you cannot view this post resource.', 'my-text-domain' ), array( 'status' => rest_authorization_required_code() ) );
}

This enables you to restrict access to the collection based on whether the authenticated user has the edit_posts capability. Returning a WP_Error in the permissions check will trigger an error to be returned to the client, preventing unauthorized access. In cases where Auth is involved it is a best practice to use rest_authorization_required_code() to get either a 401, or 403 status. 401 is for non authenticated users, in the API’s eyes no one is logged in. 403 is for unauthorized users, meaning a user is logged in but doesn’t have the proper privileges.

public WP_REST_Controller::get_items( $request )

This is the callback that is triggered to produce our desired response. It will typically return a collection of resources. The context parameter can be used to expose different data in the collection. Currently the context parameter supports view, edit, and embed contexts. The context should be registered in the schema for the property and can be automatically filtered using $this->filter_response_by_context(). It is important to note that for other actions there will be a corresponding callback and permissions callback so instead of get_items there can be get_item, create_item, update_item, delete_item, and whatever floats your boat. We will get into this more in part II, where we create a mock custom endpoint.

public WP_REST_Controller::prepare_item_for_database( $request )

This method is used to prepare a resource for the database after either being created or updated. The arguments passed in should already be sanitized and validated by this point but additional processes can be done to map the request to an actual object that is stored in the database. prepare_item_for_database() should be called directly before the resource is created or updated.

public WP_REST_Controller::prepare_item_for_response( $item, $request )

WordPress can have strange naming conventions for many resources. Using this method we can create a mapping of better naming for the resource. We want our response to match the schema and this is where all of that logic goes. After we have created a resource that matches our schema, we want to also call $this->add_additional_fields_to_object and $this->filter_response_by_context(). By calling these two methods we will add extra fields registered to our endpoint via register_rest_field() and then filter the entire response based on the provided context. We then wrap our data in a rest_ensure_response() and add any relevant links. We will get into links more in part II, they are used to create relationships between resources.

public WP_REST_Controller::prepare_response_for_collection( $data )

When creating a collection in our get_items() method we would do something like this:

$collection = array();
foreach ( $items as $item ) {
    $data = $this->prepare_item_for_response( $item, $request );
    $collection[] = $this->prepare_response_for_collection( $data )
}

Because $data is supposed to be a WP_REST_Response object, we need to make a collection of the responses data wrapped in a WP_REST_Response object, as opposed to a collection of WP_REST_Response objects. Kind of confusing, but important to understand the difference. For the most part, I don’t think this method will ever need to be overridden so just call it whenever you are creating the collection and let the magic happen. This method will be directly inherited from the WP_REST_Controller class.

public WP_REST_Controller::filter_response_by_context( $data, $context )

This method is pretty straightforward and should be called within prepare_item_for_response(). Basically filter_response_by_context() takes the response data and the request’s context parameter and filters it based on the schema. If a property has a context set to edit in its schema, then it will only show up on requests where the context param is set to 'edit'.

public WP_REST_Controller::get_item_schema()

We have talked a lot about schema. Finally, we are at the method where it is defined for our endpoint. Lets take a look at a basic schema object.

$schema = array(
    '$schema' => 'http://json-schema.org/draft-04/schema#',
    'title'      => 'example',
    'type'       => 'object',
    /*
     * Base properties for every example.
     */
    'properties' => array(
        'id' => array(
	    'description' => __( 'Unique identifier for the object.' ),
	    'type'        => 'integer',
	    'context'     => array( 'view', 'edit', 'embed' ),
	    'readonly'    => true,
	),
    ),
);

Our schema defines what should be returned in the response. If we make a request to our collection endpoint then each item would only have an id property. The schema follows JSON Schema, which we talked about above. The root part of our schema contains $schema, the spec of our schema, title, the title of the resource, type, the type of resource, and properties. Since the schema is an object itself we set its various properties. Our first and only property is id. We specify a human readable description, type, context, and whether it is a readonly property. At the very end of get_item_schema() you will want to add this statement.

return $this->add_additional_fields_schema( $schema );

By adding this statement we will allow any schema registered by register_rest_field() to be added to our endpoint. The title property in our root schema serves as the object, which register_rest_field() uses, so it is important to keep that unique per namespace. We used 'example' as our value.

public WP_REST_Controller::get_public_item_schema()

This is what you want to call for schema when you are using register_rest_route(). It will strip out any arg_options you have specified for properties in the schema. This way you end up with a very clean schema, which presents information that is only useful to clients. arg_options can be specified for a schema property so that the schema can easily be transformed into request arguments, when creating or updating by using $this->get_endpoint_args_for_item_schema(). We will get to that a bit later.

public WP_REST_Controller::get_collection_params()

get_collection_params() should start with something like the following:

$params = parent::get_collection_params();
$params['context']['default'] = 'view';

The call to the parent classes get_collection_params() will bring in any previously registered collection parameters. If you are just extending WP_REST_Controller then this will add the context param, page param, per_page param, and search param. We have already touched on what context is used for. page and per_page are pagination parameters and probably offset should be a part of this group too, but is currently not. search can be used to search through the collection. If the resources you are exposing do not make use of WP_Query then you will have to write custom pagination and search functionality.

public WP_REST_Controller::get_context_param( $args )
This allows for a consistency across endpoints in regards to the context param. You won’t really have to worry about it too much. In part two, we will see where this plays a role.

protected WP_REST_Controller::add_additional_fields_to_object( $object, $request )
This registers the additional fields registered in register_rest_field() for the response. Phew, that’s a lot of registering. Basically if you call this in $this->prepare_item_for_response() your registered fields will now magically work.

protected WP_REST_Controller::update_additional_fields_to_object( $object, $request )
This does pretty much the same thing as above, but there is a small difference. It will register the update_callback portion of register_rest_field(). $this->update_additional_fields_to_object() should be called before $this->prepare_item_for_response in the create_item and update_item callbacks.

protected WP_REST_Controller::add_additional_fields_schema( $schema )
When using rest_register_field() you will want to register schema for the field. Inside of $this->get_item_schema() at the end return the call $this->add_additional_fields_schema( $schema ) and any registered schema will now appear.

protected WP_REST_Controller::get_additional_fields( $object_type = null )
An internal method to handle any additional fields registered by rest_register_field() for a particular object type.

protected WP_REST_Controller::get_object_type()
An internal method that will get the object type of a schema, which is the title property in our schema root.

public WP_REST_Controller::get_endpoint_args_for_item_schema( $method )
get_endpoint_args_for_item_schema() is a pretty cool method that I have mixed feelings about. Basically it will take our schema we established for a resource and transform it into data that can be used as request arguments. Let’s expand on our $this->register_routes() example above for library/v1, to add an endpoint for creating books.

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(),
    ),
    array(
        'methods'             => WP_REST_Server::CREATABLE,
        'callback'            => array( $this, 'create_item' ),
        'permission_callback' => array( $this, 'create_item_permissions_check' ),
        'args'                => $this->get_endpoint_args_from_item_schema( WP_REST_Server::CREATABLE ),          
    ),
    'schema' => array( $this, 'get_public_item_schema' ),
) );

We have now set up our route to run on POST requests. When a POST request is issued it will trigger $this->create_item_permissions_check() and $this->create_item(). The request arguments will be generated by $this->get_endpoint_args_from_item_schema(). Any schema specified as required will become required request parameters for creating and any defaults will be used. Schema properties marked as readonly will not be turned into request arguments because this aspect of the resource should never be modified by a request. A great example of a readonly property would be a resource’s id.

Wrapping Up.

As you can see, to truly get the most use out of your endpoints there is a lot that goes into it, and following the conventions laid out in WP_REST_Controller will go a long way towards making your endpoints usable, extensible, and secure. In part II we will focus on the actual coding of an endpoint. In the meantime I recommend reading Daniel Bachhuber’s guide to unit testing and WP REST API Endpoint development. Stay tuned for part II. If you would like to help contribute to the WordPress REST API, head over to Trac on WordPress.org. The project is in need of your contributions.