There are many ways to create a custom API endpoint in WordPress. I personally like to build them against the base URL. If I was building one for this site it might be http://kevdees.com/kevdees-api/v1/
.
I like to prefix my APIs 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 is dead simple. You only need to use three hooks: query_vars, template_include.
We will be using PHP 7.4 and WordPress 5.8.
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 = 'kevdees-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).
IMPORTANT: WordPress automatically escapes /
for you and adds ^
to the beginning of your expression. I imagine they are trying to be developer-friendly and keep sloppy plugins from disturbing the user experience.
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 'kevdees-api/v1/([^/]*)/([^/]*)/?'
. We then assign them to the location 'index.php?_api_controller=$matches[1]&_api_action=$matches[2]'
.
A URL request to http://kevdees.com/kevdees-api/v1/posts/update/
would fetch our location and assign the matches as directed http://kevdees.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
of rewrite_rules
we need to be sure ours is the first one it looks for. Otherwise, a page named kevdees-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"}
Dare To Code
Get the tips, links, and tricks on full-stack PHP development in your inbox from me: Kevin Dees.
"*" indicates required fields
Leave a Reply