Making an API endpoint in WordPress using add_rewrite_rule

There are many ways to build an API in WordPress. I personally like to build them against the base URL. If I was building one for this site it might be http://php-built.com/php-built-api/v1/.

I like to prefix my API's to help prevent conflicts. If I was using a framework like Laravel or Slim I might take a different approach, but this is a post about WordPress.

To build an API in WordPress using your own URL it is dead simple. You only need to use three hooks: query_vars, template_include.

We will be using PHP 5.4 and WordPress 4.2.

Register your API endpoint

For now, let us focus on establishing our endpoint. Later we can get into making use of it.

To register the endpoint against the base we can use add_rewrite_rule. This is the simplest way to get a custom URL going. We can use the admin_init hook to do this.

add_action('init', function() {
$regex = 'php-built-api/v1/([^/]*)/([^/]*)/?';
$location = 'index.php?_api_controller=$matches[1]&_api_action=$matches[2]';
$priority = 'top';
add_rewrite_rule( $regex, $location, $priority );
});

In this example, we are not following a pattern like REST or RPC. We are simply drawing out the basics so we can use these ideas to build any API we want.

Don't forget to flush your rewrite rules.

The basics of add_rewrite_rule

If you are not familiar with add_rewrite_rule it is fine. We only need to know three things when building the example API: matching the URL, fetching our desired location, the priority of the endpoint.

Matching

Matching the URL in WordPress requires regular expressions. If you are not familiar with RegEx you need to be at this point (regex101.com is a great playground).

Note: WordPress hacks RegEx by escaping / for you and adding ^ to the beginning of your expression. I imagine they are trying to be developer friendly and keep sloppy plugins for disturbing user experience.

RegEx 101 API example

Location

Now, every match we find is passed into $location as the $matches variable. This lets us assign them to specific query variables in the URL. In RegEx, matches are defined between parenthesis ().

In our code we are looking for two matches 'php-built-api/v1/([^/]*)([^/]*)/?'. We then assign them to the location 'index.php?_api_controller=$matches[1]&_api_action=$matches[2]'.

API WordPress Figure A1

A URL request to http://php-built.com/php-built-api/v1/posts/update/ would fetch our location and assign the matches as directed http://php-built.com/index.php?_api_controller=posts&_api_action=update.

Priority

Since WordPress has lots of rewrites already registered in the options table under the option_name rewrite_rules we need to be sure ours is the first one it looks for. Otherwise, a page named php-built-api might cause issues for us in the future.

We tell add_rewrite_rule to send our rewrite to the 'top' of the list and all is well.

$priority = 'top';
add_rewrite_rule( $regex, $location, $priority );

Establishing our custom matches as safe query variables

WordPress wants to keep you safe, believe it or not, and so we need to establish that our custom query variables are part of the program. In our case _api_controller and _api_action are the query variables in the $_GET request we want to make available.

Using the query_vars filter hook we can drop them into WordPress as safe. This will let us access them in our templates.

add_filter( 'query_vars', function($vars) {
array_push($vars, '_api_controller');
array_push($vars, '_api_action');
return $vars;
} );

Plain and simple.

Constructing our Response

Finally, we are on to the fun part and really the thing we care about most. The Response.

To construct a response we need to override the default templating engine using the template_include filter hook. This will let us define what we want WordPress to send back.

We don't want the themes index.php template file after all.

To detect a request coming to the API use get_query_var and check for our query variables. If they are present we should send a custom response.

add_filter( 'template_include', function($template) {

$controller = get_query_var('_api_controller', null);
$action = get_query_var('_api_action', null);

if($controller && $action) {
$template = __DIR__ . '/api/v1.php';
}

return $template;
}, 99 );

This example will produce the response using our v1.php file. We can respond with JSON, XML or any other format we like. Just be sure to set the appropriate headers in the new template file.

JSON Response

If we wanted to send JSON we can use wp_send_json in our v1.php file.

wp_send_json(['api' => 'v1'] );

If we look at the HTTP response we will get exactly what we want.

HTTP/1.1 200 OK
Content-Length: 12
Content-Type: application/json; charset=UTF-8

{"api":"v1"}

Show Comments