This post will show how to create custom WordPress REST endpoints and different methods for performing requests to them. There will be examples in both PHP, jQuery and vanilla Javascript.

I assume you are already familiar with what WP REST API is, but here is a short summary. WordPress REST API is a JSON interface to send and receive data from your WordPress site. You can access the endpoints (specific paths/URLs) both externally and internally. WordPress already has a bunch of endpoints available, e.g. for fetching posts, categories, searching the site and more. See an overview of default WordPress endpoints here. But developers are fully free to create their own custom endpoints using this API, for either performing actions or fetching data.

We’ll start with the first step; which is creating custom endpoints. If you are only interesting in how to make requests, skip ahead to the second part.

Create custom endpoints

Registering custom endpoints is done in PHP. You can add the code to your theme’s functions.php or an active plugin’s code file. Make a function hooked to rest_api_init and use the function register_rest_route() for each endpoint you’d like to add.

As the first parameter to register_rest_route() you need to provide an unique namespace to make sure your endpoints don’t conflict with any other. Use your theme’s or plugin’s slug. It’s common practice to then include a / followed by a version number for your code. As an example I will use the namespace awhitepixel/v1. Second parameter is the path (which follows the namespace). Finally you can optionally provide an array as third parameter with options. In this array you can for example define the request method (GET, POST, or any other), define parameters, and most importantly define the function to run when that endpoint is requested.

As minimum you should provide the arguments ‘method’ and ‘callback’ (which is the function to handle the endpoint data) as third parameter. For ‘method’ you can set them as ‘GET', 'POST', 'PUT', or any other valid request method (or an array of multiple), but I recommend using WordPress’ defaults for this. They are as follows:

  • WP_REST_Server::READABLE = method ‘GET
  • WP_REST_Server::EDITABLE = methods ‘POST‘, ‘PUT‘, and ‘PATCH
  • WP_REST_Server::DELETABLE = method ‘DELETE
  • WP_REST_Server::ALLMETHODS = all of the above methods

Let’s create a basic custom endpoint that can be reached using GET requests:

add_action( 'rest_api_init', function() {
	register_rest_route( 'awhitepixel/v1', '/getsomedata', [
		'method'   => WP_REST_Server::READABLE,
		'callback' => 'awhitepixel_rest_route_getsomedata',
	] );
} );

At line #2 we have defined our custom endpoint as ‘awhitepixel/v1/getsomedata‘. The full URL would be WordPress’ REST API root URL prefixed, which is <yourdomain>/wp-json. So the full URL to the above endpoint would be ‘<yourdomain>/wp-json/awhitepixel/v1/getsomedata‘. At line #4 we’ve provided a function name as callback, which we’ll add shortly.

When registering (or changing) REST API routes using register_rest_route(), you will need to flush your permalinks in order for it to work. You can do this by visiting Settings > Permalinks in admin and simply click Save.

We have not yet defined the callback function – which is the function for the code that handles the reaction of using this endpoint. It must return a REST valid response (in JSON), so you’ll need to return something even though the endpoint is not supposed to return data. You can use the function rest_ensure_response() function or create an instance of WP_REST_Response object as the callback’s return. As parameter to the callback function we get a WP_REST_Request object which contains all information about the request – including any parameters. Let’s create a simple callback function that simply sends some text as response:

function awhitepixel_rest_route_getsomedata( $request ) {
	$response = 'Hello there!';
	return rest_ensure_response( $response );
}

This is the most basic way of writing a callback. The function rest_ensure_response() makes sure that any data we provide (the string) is converted into a REST valid response. Obviously you will want to add more code in here, to either do something in WordPress or fetch and send back data.

With the above code (registering the endpoint and the callback function) you can try to go to the URL in your browser and see what you get. (Remember to flush your permalinks). Going to <domain>/wp-json/awhitepixel/v1/getsomedata in the browser will show the “Hello there!” string.

Accepting parameters

It’s very common and useful to allow endpoints to accept parameters. For example if your site has e.g. product data, you’d want an endpoint where you can provide a product ID in order to get that product’s data. There are two ways to go about it. The most common way is using GET query string (which are appended after the URL after a ?, separated by & in key=value format. For example ‘<endpoint>/product/?product_id=14‘). Alternatively you can define a “prettier” URL pattern, where parameters are a part of the path. For example ‘<endpoint>/product/14/‘. This last method requires some knowledge to regexes though. Which method you’ll go for is up to you, I’ll demonstrate both below.

GET parameters

Defining possible GET parameters to your endpoint is using the ‘args‘ key in register_rest_route()‘s third parameter. For each parameter you want to allow, define the key value (in the above example ‘product_id‘) and an array of options for that parameter. As options you can define the parameter’s format (if it expects for example a number or a string) and also decide if that parameter is required or not.

As an example we want to allow our endpoint to accept ‘product_id‘ as a non-required number.

add_action( 'rest_api_init', function() {
	register_rest_route( 'awhitepixel/v1', '/getsomedata', [
		'method'   => WP_REST_Server::READABLE,
		'callback' => 'awhitepixel_rest_route_getsomedata',
		'args'     => [
			'product_id' => [
				'required' => false,
				'type'     => 'number',
			],
		],
	] );
} );

function awhitepixel_rest_route_getsomedata( $request ) {
	$product_id = $request->get_param( 'product_id' );
	if ( ! empty( $product_id ) ) {
		$response = 'Return product data for ID ' . $product_id;
	} else {
		$response = 'Hello there!';
	}
	return rest_ensure_response( $response );
}

If you define a parameter as true in required, WordPress will handle passing back a 400 error reply. Likewise if you pass an invalid format, for example “hello” as value to a parameter that expects a number.

At line #15 in the callback function you see how to get ahold of the parameter value from the request; using the method get_param() in the passed WP_REST_Request object. As an example I’ll show different responses depending on whether or not product_id was provided or not (remember that we’ve defined it as optional). The logic of modifying your code according to the provided parameters are completely up to you and your project. You can have fewer endpoints that accepts a lot of parameters, or many more separate endpoints for each specific case.

With the above code, I will get “Hello there!” when I visit <yourdomain>/awhitepixel/v1/getsomedata and “Return product data for ID 14” when I go to <yourdomain>/awhitepixel/v1/getsomedata/?product_id=14.

Parameters as part of path

If you want to allow parameters to be a part of the path rather than GET query string, you can do so. You will then provide a regex pattern in the path – the second parameter to register_rest_route().

Creating regex patterns can look quite cryptic, but since it’s a whole topic you’ll easily find examples for specific cases if you google it. I’ll show an example of defining a regex that accepts a number of any length;

add_action( 'rest_api_init', function() {
	register_rest_route( 'awhitepixel/v1', '/getsomedata/(?P<product_id>[\d]+)', [
		'method'   => WP_REST_Server::READABLE,
		'callback' => 'awhitepixel_rest_route_getsomedata',
	] );
} );

function awhitepixel_rest_route_getsomedata( $request ) {
	$product_id = $request->get_param( 'product_id' );
	$response   = 'Return product data for ID ' . $product_id;
	return rest_ensure_response( $response );
}

As you can see in line #2 I’ve added the regex pattern (?P<product_id>[\d]+) at the end. This patterns means that we are to collect a number (\d) of any length (+) and assign the collected value into the parameter key product_id. And in our callback function we use the exact same method as we did when setting up GET parameters; get_param() in the WP_REST_Request object provided to the function.

With the above code (after flushing permalinks) we can visit the URL <yourdomain>/wp-json/awhitepixel/v1/getsomedata/14 to get our response. This method certainly results in “prettier” URLs, but the code can easily get more difficult to read and bugfix. Whichever method you choose is up to you.

Returning proper response

Earlier I briefly mentioned how the callback function needs to return a proper REST response. So far we’ve used the simpler rest_ensure_response(). But sometimes you might want more control of your endpoint’s return. You might for example want to control the HTTP response status code. Say you’re making an endpoint where you can provide a product ID and get data for that product. But if that product ID didn’t exist, or any other parameters cause confusion, you might want to return a status code of for example 400 (Bad request), or 404 (Not found). Or perhaps 500 (Server error). Always passing 200 (Success) even though the request was bad can cause problems in the sender’s end.

I would then recommend your callback funtion returning a WP_REST_Response object. WIth this object you can control several things, including the status code. It’s easier than you think! You would in the simplest form create a new instance of WP_REST_Response, provide an array of the data to return as first parameter, and the status code as second parameter. As an example:

function awhitepixel_rest_route_getsomedata( $request ) {
	$product_id = $request->get_param( 'product_id' );
	// Do some external function call that returns product data or empty array on error
	$product_data = awhitepixel_get_product( $product_id );
	if ( empty( $product_data ) ) {
		return new WP_REST_Response( [
			'message' => 'Product was not found',
		], 400 );
	}
	return new WP_REST_Response( $product_data, 200 );
}

The above example we store the return of the awhitepixel_get_product() function in a variable. This function doesn’t exist, and you should replace it with the function that should fetch (or do) the actions you want in your project. But say the function returns an empty array and that means something went wrong (for example product did not exist). We could then return a WP_REST_Response object with status code 400, and optionally a message as data explaining why it failed (line #5-9). Otherwise we return the data with status code 200 Success (line #10).

Sending requests to (custom) endpoints

Let’s move on to the other side, the sending part: How to send requests to our custom endpoints. Normally you would send WP REST API requests using Javascript and here you’ll find examples of using jQuery, WordPress library and vanilla Javascript. It is uncommon, but possible, to perform a REST request with PHP as well – so I’ve included an example of that as well.

First thing to be aware of is that you obviously need to know the full URL in order to send a request. I don’t recommend hardcoding the domain (before the endpoint) as there are multiple ways of getting this if your Javascript is operating within WordPress. In older versions of WordPress you would need to use wp_localize_script() in PHP and pass on the core REST URL as a global Javascript variable. But the examples below show a newer and better way of getting the WordPress site’s REST root URL.

Another thing to note is that for your project you would probably wrap the requests as a result of some action. To keep things simple I am making all requests at DOM ready, so you should make sure to wrap the request code into e.g. as a result of a visitor clicking a button.

Using jQuery

If you have, and want to use, the jQuery library, you can use its $.ajax() function.

But first a note about your Javascript file’s dependencies. Obviously your script would need 'jquery' as dependency in enqueuing it. But in order to easily access your WordPress’ REST root URL, add another dependency; ‘wp-api-request’. This ensures that the Javascript variable wpApiSettings.root is available and contains the REST API root URL. Here’s an example at how you would enqueue your script to illustrate this dependency;

add_action( 'wp_enqueue_scripts', function() {
	wp_enqueue_script(
		'awp-javascript-wp-rest', 
		get_stylesheet_directory_uri() . '/assets/js/javascript_wp_rest.js', 
		[ 'jquery', 'wp-api-request' ], 
		null, 
		true
	);
} );

Line #5 is the interesting one; where we define both jQuery and wp-api-request as dependency. Then in our Javascript file we can perform a WP REST API request like so:

javascript_wp_rest.js
( function( $ ) {
	// Send request
	$.ajax( {
		url: wpApiSettings.root + 'awhitepixel/v1/getsomedata',
		method: 'GET',
		data: {
			product_id: 14
		},
		success: function( data ) {
			console.log( data );
		}
	} );
} )( jQuery );

This is as basic as it gets. We use $.ajax() to send a GET request to the defined url. As URL we use wpApiSettings.root to get the REST API root URL and then we append the desired endpoint after it; in our case the custom endpoint we created earlier. Optionally we can pass parameters in ‘data’. The example above passes product_id with the value 14 to the endpoint. Finally in the success function we receive the (successful) request as parameter and we are free to do what we want with it. In the above example we simple prints it to the console.

Using WordPress library (non-jQuery)

If your WordPress site does not have, or can, use the jQuery library you can use WordPress’ Javascript library to easily perform a REST API request. The function is apiFetch which is available in WordPress’ global wp namespace. wp.apiFetch() is a wrapper method for the browser standard fetch() function (which is demonstrated next).

Our Javascript will need the dependency ‘wp-api’ in order to use wp.apiFetch(). For example we could enqueue the script like this:

add_action( 'wp_enqueue_scripts', function() {
	wp_enqueue_script(
		'javascript-wp-rest', 
		get_stylesheet_directory_uri() . '/assets/js/javascript_wp_rest.js', 
		[ 'wp-api' ], 
		null, 
		true
	);
} );

You’ll find the critical dependency at line #5. With this we’ve ensured that our Javascript file has wp.apiFetch() available. Here’s a basic example in using it:

javascript_wp_rest.js
function awpSendRequest() {
	wp.apiFetch( {
		path: 'awhitepixel/v1/getsomedata?product_id=14',
	} ).then( data => {
		console.log( data );
	} );
}

if ( document.readyState != 'loading' ) {
	awpSendRequest();
} else {
	document.addEventListener( 'DOMContentLoaded', awpSendRequest );
}

Keep in mind that line #9-13 are just logic to run the function after DOM is ready.

One benefit of using wp.apiFetch() over the normal fetch() is that we can use ‘path’ which requires only the endpoint, instead of ‘url’ which requires the complete URL. Another benefit is that the first “chain” of .then() returns the data already JSON formatted. When you use the original .fetch() you need more “.then() chains”. Take a look at the next example (“Using vanilla Javascript”) to see what I mean.

Keep in mind that fetch() (and as a consequence wp.apiFetch()) does not provide a “clean” way to pass parameters. We will need to manually construct the GET query string in ‘path’, as I’ve done above: '../getsomedata?product_id=14'.

If you are interested in how to incorporate wp.apiFetch and custom endpoints in a Gutenberg block, I’ve written a separate tutorial on this:

Using vanilla Javascript

As a final example of Javascript methods to send request to WP REST API there is a pure vanilla, non-WordPress way, using fetch(). Please note that I do use WordPress global variable in order to get the REST root URL. If you are adding this script outside WordPress, you would probably need to hardcode the complete URL.

Since I still want access to the global variable for WP REST root URL, I add 'wp-api-request' dependency to my Javascript enqueuing function, like so:

add_action( 'wp_enqueue_scripts', function() {
	wp_enqueue_script(
		'awp-javascript-wp-rest', 
		get_stylesheet_directory_uri() . '/assets/js/javascript_wp_rest.js', 
		[ 'wp-api-request' ], 
		null, 
		true
	);
} );

And then in our Javascript file a most basic example would be:

function awpSendRequest() {
	fetch( wpApiSettings.root + 'awhitepixel/v1/getsomedata?product_id=14', {
		method: 'GET',
	} )
	.then( data => data.json() )
	.then( data => {
		console.log( data );
	} );
}

if ( document.readyState != 'loading' ) {
	awpSendRequest();
} else {
	document.addEventListener( 'DOMContentLoaded', awpSendRequest );
}

As mentioned above (“Using WordPress library”) .fetch() does not support a nice, clean way of providing parameters. So you’ll need to manually build the query string (“?product_id=14”) into the URL.

Keep in mind that fetch request doesn’t directly return with the data – it returns a promise. That’s why we need to chain “.then()” before we can handle the data. If this sounds unfamiliar to you I recommend looking up how to work with fetch() – there are plenty of explanations and examples on google that can explain it better than me.

If you want to check the REST response status code to your request, you could do it like so;

fetch( wpApiSettings.root + 'awhitepixel/v1/getsomedata?product_id=14', {
	method: 'GET',
} )
.then( data => {
	if ( data.status != 200 ) {
		console.log( data.status + ' Error: ' + data.statusText );
		return;
	}
	data.json().then( data => {
		console.log( data );
	} );
} );

In the example above in registering custom endpoints, I mentioned how you could return different HTTP status codes. The above code shows an example in how to check the response’s status code, which is available in the returned object’s .status property. In the example above I check if the status code is anything different then 200 (Success) at line #5. Only if the status code was 200 I convert the promise’s data return into JSON (line #9).

Using PHP

It is less common, but still possible, to perform REST request internally in WordPress using PHP. Here’s how.

In order to send a WP REST API request in PHP we create a WP_REST_Request object (exactly like what is passed to our callback function earlier in this post). In this object instance we define method (e.g. GET) and endpoint path. Optionally we can also add parameters. Then we use WordPress’ function rest_do_request() with this request instance. Finally we get ahold of the response with response_to_data() function available in WP_REST_Server's class.

function awp_do_php_rest_request( $product_id ) {
	$request = new WP_REST_Request( 'GET', '/awhitepixel/v1/getsomedata' );
	$request->set_query_params( [
		'product_id' => $product_id
	] );
	$response = rest_do_request( $request );
	return rest_get_server()->response_to_data( $response, false );
}

The function call set_query_params() (line #3-5) is optional and only necessary if you want to pass parameters. Following the red thread in this post I’ll included an example of passing the function’s parameter to the REST parameter key product_id.

Line #6 is where we send the request. And at line #7 we return the response’s data. So if we were to call this PHP function, for example awp_do_php_rest_request( 14 ) we would then get the response we defined in that endpoint (an array with a string if you still use the same example as the start of this post).