WordPress REST API versus Custom Request Handlers


Last year I wrote a blog post comparing the performance of using admin-ajax.php and the WordPress REST API, and found that the REST API was about 16% faster than using the traditional AJAX API. While that was a solid improvement, quite a few developers (myself included) were wondering how the REST API compares to using completely custom endpoints. Since the REST API loads most of WordPress core and any active plugins, it should be quite a difference!

There’s little doubt that a custom endpoint will be faster, but it should be interesting to see how much faster it will be. It’s also worth taking a look to see if a custom endpoint makes sense from a development and future support perspective.

Defining Custom Request Handlers

There are a few different ways to set up custom endpoints. The fastest possible endpoint would likely be completely independent of WordPress, and could be as simple as a standalone PHP file that is uploaded to the web server. While fast, it probably wouldn’t be all that helpful, because we won’t have access to any WordPress core functions which often save a great deal of development time.

Speeding Things Up with SHORTINIT

The next fastest approach would be to manually load enough of WordPress to be able to use core functions while not loading things like themes and plugins. AJAX requests would target this file specifically, so it should be placed somewhere on the server where it is publicly accessible.

There’s a few ways of doing this, but this is the most common approach I see:

// custom-ajax-endpoint.php

define( 'DOING_AJAX', true );

// Tell WordPress to load as little as possible
define( 'SHORTINIT', true );

require_once '../../wp-load.php';

wp_send_json( array( 'time' => time() ) );

The above file does a few things. First it defines the DOING_AJAX constant to tell WordPress that an AJAX request is being made. Then it defines the SHORTINIT constant to tell WordPress to load the basics – some of WordPress core and not much else. Then it manually requires the wp-load.php file (which is generally bad practice as the location of this file can vary between installs) which loads some other files needed to properly serve the request. Finally it responds to the request with the JSON encoded time.

While looking at this, my colleague Evan noticed another approach that doesn’t require knowing the location of the wp-load.php file. This can be accomplished by listening for a parameter that defines a custom request in the wp-config.php file:

 * Define SHORTINIT and DBI_AJAX constants if this is 
 * a request to our custom request handler
if ( filter_input( INPUT_GET, 'dbi-ajax' ) ) {
    define( 'SHORINIT', true );
    define( 'DBI_AJAX', true );

/* That's all, stop editing! Happy blogging. */

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
    define('ABSPATH', dirname(__FILE__) . '/');

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

if ( defined( 'DBI_AJAX' ) ) {
    wp_send_json( array( 'time' => time() ) );

This is similar to the first approach above. If the request contains a dbi-ajax parameter (so we know that this is a custom request), we define the SHORTINIT constant to tell WordPress to load the bare essentials. We also define a DBI_AJAX constant so we can easily tell if a custom request is being made without checking for the dbi-ajax parameter again. Then once WordPress has been loaded (after the wp-settings.php file has been required), we can serve the request. Usually we would recommend including another file at this point which contains that functionality, but for the purpose of this benchmark we’re just returning the JSON encoded time like in the first example above.

Loading so little of WordPress comes with a unique set of challenges. Not all WordPress core functions will be available, so if a function that you need isn’t available, you’ll need to track down the file it resides in and include it manually. Depending on what your request handler needs to do, this could become a burden. Also, manually requiring the wp-load.php file or editing the wp-config.php file isn’t portable and likely shouldn’t be done in a plugin or theme that will be distributed across a large number of installs.

A Better Alternative?

Another possible approach is to create a custom request handler within a Must Use plugin. That way it can respond to any custom requests after WordPress core loads, but before the plugins and themes are loaded. This should both improve performance and prevent conflicts from other plugins that are using the REST API or admin-ajax.php.

 * Plugin Name: DBI AJAX Endpoint
 * Description: Custom Request endpoint
 * Author: Delicious Brains Inc
 * Version: 1.0
 * Author URI: https://deliciousbrains.com

if ( ! filter_input( INPUT_GET, 'dbi-ajax' ) ) {

// Define the WordPress "DOING_AJAX" constant.
if ( ! defined( 'DOING_AJAX' ) ) {
    define( 'DOING_AJAX', true );

wp_send_json( array( 'time' => time() ) );

The above plugin listens for requests that contain the dbi-ajax parameter (to prevent it from responding to any request), and then uses wp_send_json() to return the time in JSON to complete the request like in the previous examples.

Running Some Benchmarks

With two potential solutions for a custom request endpoint in place, it’s time to run some benchmarks. To keep things somewhat consistent with the previous article, I’m using ApacheBench to send hundreds of HTTP requests to the server in a short time in order to get a feel for the average response time of the endpoint. I also have the same set of plugins activated:

  • ACF
  • Akismet
  • Black Studio TinyMCE Widget
  • WP Migrate DB
  • WP Super Cache
  • Yoast SEO

First let’s take another look at the REST API benchmark so we have a solid baseline:

REST API benchmark

After 100 requests, an average response time of 264ms becomes apparent. This is actually much faster than it was last year when I ran the same test on the same server (490ms), but for now let’s just focus on the 264ms average response time as a baseline with which we can judge the results of the custom endpoints.


Next let’s take a look at the custom endpoint that loads the bare minimum files by defining the SHORTINIT constant.

Standalone custom endpoint benchmark

With an average response time of just 29ms (an 89% decrease), it’s clear that this approach is night-and-day faster than using the REST API. This is because only the files needed to serve the request are loaded.

Must Use Plugin Benchmark

Finally let’s take a look at the custom endpoint that was created in the mu-plugin:

Must Use plugin custom endpoint benchmark

With an average response time of 144ms (a 45% decrease compared to the REST API), the performance of this endpoint is somewhere in between the standalone file and the REST API. This makes it a good solution for a custom request handler that can use WordPress core functions without knowing the location of the wp-load.php file.


Response times shouldn’t be your only consideration when developing an application that will send a large amount of requests. It would also be wise to consider the upfront development time as well as any time that you might spend supporting the endpoints.

For some use-cases where performance is critical, such as plugins that process a lot of data in batches or where the potential for plugin/theme conflicts is higher, it might make sense to use a custom endpoint. Most of the time, having a pre-made, officially supported solution will far outweigh the potential performance benefits of creating your own endpoints.

Are you using custom endpoints in your plugin or theme? If so, would you consider making the switch to the REST API? Why or why not? Let me know in the comments.

About the Author

Matt Shaw

Matt is a WordPress plugin developer located near Philadelphia, PA. He loves to create awesome new tools with PHP, JavaScript, and whatever else he happens to get his hands on.