Customizing the REST API Response in WordPress

The WordPress REST API is a core feature introduced in WordPress 4.7 allowing developers to make HTTP requests to access WordPress data as a JSON object. The REST API allows for many different solutions, whether you are looking to create a decoupled website or app with data from a WordPress backend, or to simply allow external websites to read a post feed. In this article is a look at common scenarios for customizing the REST API response for GET requests to posts.

A brief look at the core API

By default, a WordPress site’s post information can be found at yourwebsite.address/wp-json/v2/posts. This endpoint displays the 10 most recent posts of any type. What is actually returned in the default response are the rows and fields in the database table wp_posts of type “post”, plus the ID(s) of the featured image, categories, and tags, if any. Similarly, pages (posts of type "page" in the WordPress database) can be found at yourwebsite.address/wp-json/v2/pages.

Adding custom post types

The REST API supports custom post types, but if the custom post type has been registered without setting the show_in_rest argument to true, the response will return a 404 with the code rest_no_route. Ideally, if the WordPress website is registering the custom post type through a custom theme or plugin, the modification can be made in the original argument array passed to register_post_type():
function pluginname_custom_post_type() {
  $args = array(
    'label' => 'Custom Post Type',
    'public' => true,
    'show_in_rest' => true,
    'rest_base' => 'custom'
  );
  register_post_type( 'custom_post_type', $args );
}
add_action( 'init', 'custom_post_type' );

Note: Most custom post type arguments will and should utilize more of the many post type options available than shown in this short example.

However, if the custom post type has been registered in a 3rd-party or contributed plugin or theme, it is a better practice to modify the arguments list in a function located in a custom theme or plugin and invoking the register_post_type_args hook:

function pluginname_modify_custom_post_type( $args, $post_type ) {
  if ( $post_type == 'custom_post_type' ) {
    $args['show_in_rest'] = true;
    $args['rest_base'] = 'custom';
  }
  return $args;
}
add_action( 'register_post_type_args', 'modify_custom_post_type' );

In either case, setting show_in_true to true allows a new endpoint to be accessed using the rest_base value. In this example: /wp-json/wp/v2/custom.

Adding fields to the WordPress core response

There are cases where it is necessary or just convenient to modify the fields returned in the response. For example, while any data included in the _links section of a post with "embeddable": true can be included in its entirety without making another HTTP request by adding _embed=1 to the query, the embedded data might be too many layers deep to easily deal with from the API client's perspective:
"_embedded": {
  "author": { { 
    "id": 1,
    "name": "Aten Design Group",
    "url": "https://www.aten.io",
    "description": "",
    "link": "https://www.aten.io",
    "slug": "atendesigngroup"
 } }
}

In an example case where the API client needs the post's author name presented at the same level as the post title and publish date, it is best to add a new field to the response. It is important to not modify the existing author field to ensure that API clients are able to continue working with the standard core response, regardless of any one client's needs.

register_rest_field() should be used in a custom function to add fields to the REST API response, and invoking that function with the rest_api_init hook:

function pluginname_get_author_name( $post, $field_name, $request ) {
  return get_the_author_meta( 'display_name', (int) $post['author'] );
}
 
function pluginname_add_author_name_to_api() {
  register_rest_field( 'post',
    'author_name',
    array(
      'get_callback' => 'pluginname_get_author_name'
    )
  );
}
add_action( 'rest_api_init', 'pluginname_add_author_name_to_api' );

Creating a custom endpoint

There are cases when the WordPress core REST API endpoints and fields are not suitable for a project:
  • A need to turn off the core endpoints,
  • require authentication against a custom endpoint and not core endpoints or vice versa,
  • limit or change fields returned,
  • return a very specific set of fields, or
  • some combination of these reasons.
In any of those cases it may be necessary to create a custom endpoint using register_rest_route:
function pluginname_get_post_items() {
  $args = array (
    'post_status' => 'publish'
  );
 
  $items = array();
 
  if ( $posts = get_posts( $args ) ) {
    foreach ( $posts as $post ) {
      $items[] = array(
        'id' => $post->ID,
        'title' => $post->post_title,
        'author' => get_the_author_meta( 'display_name', $post->post_author ),
        'content' => apply_filters( 'the_content', $post->post_content ),
        'teaser' => $post->post_excerpt
      );
    }
  }
  return $items;
}
 
function pluginname_get_page_items() {
  $items = array();
 
  if ( $pages = get_pages( $args ) ) {
    foreach ( $pages as $page ) {
      $items[] = array(
        'id' => $page->ID,
        'title' => $page->post_title,
        'author' => get_the_author_meta( 'display_name', $page->post_author ),
        'content' => apply_filters( 'the_content', $page->post_content ),
        'teaser' => $page->post_excerpt
      );
    }
  }
  return $items;
}
 
function pluginname_register_api_endpoints() {
  register_rest_route( 'pluginname/v2', '/posts', array(
    'methods' => 'GET',
    'callback' => 'pluginname_get_post_items',
  ) );
  register_rest_route( 'pluginname/v2', '/pages', array(
    'methods' => 'GET',
    'callback' => 'pluginname_get_page_items',
  ) );
}
 
add_action( 'rest_api_init', 'pluginname_register_api_endpoints' );

Getting to know the capabilities of the WordPress REST API is a first step toward thinking outside the box with the WordPress CMS and creating new applications leveraging WordPress' standard structured data and backend.

Code WordPress

Read This Next