How to Make Custom REST API (Beginner’s Guide)

Wordpress REST API is a very powerful tool with the right implementation. Learn how to create custom GET and POST request here.

REST API is a set of rules that helps a client communicates with the server. For example, displaying or saving data through a mobile app.

Every WordPress site comes with some default API endpoints like this one for getting recent posts:

https://wptips.dev/wp-json/wp/v2/posts

Try pasting that to your browser and you will see a bunch of JSON data.

In this tutorial we will learn how to make a custom endpoint.

Register GET Route

GET Request means receiving data from the server.

In this example, we have a Custom Post Type (CPT) called project with project_category taxonomy and we need 2 new endpoints:

Note: If you would like to follow along in this tutorial, here’s the code to create that CPT. Then create some random projects and categories.

1. Static Route at /my/v1/projects to get recent projects:

add_action( 'rest_api_init', function() {
  register_rest_route( 'my/v1', '/projects', [
    'methods' => 'GET',
    'callback' => 'get_projects',
  ] );
} );

// Get recent projects
function get_projects( $params ) {
  $projects =  get_posts( [
    'post_type' => 'project',
    'posts_per_page' => 10
  ] );

  foreach( $projects as &$p ) {
    $p->thumbnail = get_the_post_thumbnail_url( $p->ID );
  }

  return $projects;
}

2. Dynamic Route at /my/v1/project/[id] to get a specific project:

add_action( 'rest_api_init', function() {
  register_rest_route( 'my/v1', '/project/(?P<id>\d+)', [
    'methods' => 'GET',
    'callback' => 'get_project',
  ] );
} );

// Get single project
function get_project( $params ) {
  $project = get_post( $params['id'] );
  $project->thumbnail = get_the_post_thumbnail_url( $project->ID );
  return $project;
}

The breakdown of (?P<id>\d+):

  • ?P<id> means it will save the value as ‘id’.
  • \d+ is the regex validation and means it only accept numbers. You can read more about regex in MDN article here.

Register POST Route

POST Request means sending data to the server.

Continuing from the example above, we need a Search endpoint where we can filter by submitting title and/or category.

Here’s how:

add_action( 'rest_api_init', function() {
  register_rest_route( 'my/v1', '/projects_search', [
    'methods' => 'POST',
    'callback' => 'post_projects_search'
  ] );
} );

// Search projects
function post_projects_search( $request ) {
  // Get sent data and set default value
  $params = wp_parse_args( $request->get_params(), [
    'title' => '',
    'category' => null
  ] );

  $args = [
    'post_type' => 'project',
    's' => $params['title'],
  ];

  if( $params['category'] ) {
    $args['tax_query'] = [[
      'taxonomy' => 'project_category',
      'field' => 'id',
      'terms' => $params['category']
    ]];
  }

  return get_posts( $args );
}

It’s difficult to use your browser to simulate POST requests. So I suggest using a software called Postman.

Debugging with Postman

Postman is an amazing free software to try out API requests. First, download and register for an account:

After that, create a new POST request and enter the appropriate data. The screenshot below is the Postman setting for our Search endpoint:

Postman config for POST request

Click SEND and you will see the returned data in the panel below:

The response from POST request above.

If there’s an error, move to “Preview” tab to see the error message in a clearer way.

Create a Wrapper Class (Optional)

If the endpoints are related, I recommend putting them in a class. Not only it’s tidier it also relieves you from worrying about duplicate method names.

Here’s what it looks like when grouping the 3 endpoints above:

if( !class_exists( 'MyAPI' ) ) {

class MyAPI {
  function __construct() {
    add_action( 'rest_api_init', [$this, 'init'] );
  }

  function init() {
    register_rest_route( 'my/v1', '/projects', [
      'methods' => 'GET',
      'callback' => [$this, 'get_projects'],
    ] );

    register_rest_route( 'my/v1', '/project/(?P<id>\d+)', [
      'methods' => 'GET',
      'callback' => [$this, 'get_project'],
    ] );

    register_rest_route( 'my/v1', '/projects_search', [
      'methods' => 'POST',
      'callback' => [$this, 'post_projects_search']
    ] );
  }

  // Get recent projects
  function get_projects( $params ) {
    $projects =  get_posts( [
      'post_type' => 'project',
      'posts_per_page' => 10
    ] );

    foreach( $projects as &$p ) {
      $p->thumbnail = get_the_post_thumbnail_url( $p->ID );
    }

    return $projects;
  }

  // Get single project
  function get_project( $params ) {
    $project = get_post( $params['id'] );
    $project->thumbnail = get_the_post_thumbnail_url( $project->ID );
    return $project;
  }

  // Search projects
  function post_projects_search( $request ) {
    // Get sent data and set default value
    $params = wp_parse_args( $request->get_params(), [
      'title' => '',
      'category' => null
    ] );

    $args = [
      'post_type' => 'project',
      's' => $params['title'],
    ];

    if( $params['category'] ) {
      $args['tax_query'] = [[
        'taxonomy' => 'project_category',
        'field' => 'id',
        'terms' => $params['category']
      ]];
    }

    return get_posts( $args );
  }
}

new MyAPI();
}

Conclusion

WordPress REST API can be very powerful. It has been used by many plugins like WooCommerce to create an interactive experience.

The Gutenberg editor also used the REST API in many areas such as updating your post without a page refresh.

In my next few posts, I will cover about using JavaScript to call these APIs. This will lead to Headless WordPress, a topic most requested by our readers 🙂

Useful links:

Default image
Henner Setyono
A web developer who mainly build custom theme for WordPress since 2013. Young enough to never had to deal with using Table as layout.
Leave a Reply