WordPress 5.6 introduces a framework for making a series of REST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think “phone app” or “website”) can communicate with the data store (think “database” or “file system”) https://developer.wordpress.org/rest-api/. calls in one request to the server. At its simplest, this is a helpful performance optimization when a large number of write operations need to be made. It also optionally offers basic concurrency controls.
Registration
In order to be used in a batch request, routes must first declare support for the feature during their registration. For example:
register_rest_route( 'my-ns/v1', 'my-route', array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'my_callback',
'permission_callback' => 'my_permission_callback',
'allow_batch' => array( 'v1' => true ),
) );
If the REST API route was implemented using best practices, declaring support should be sufficient for the route to be writable via the batch endpoint. Specifically, these are the things to look out for:
- Routes must use the
WP_REST_Request
object to get all request data. In other words, it shouldn’t access the $_GET
, $_POST
or $_SERVER
variables to get parameters or headers. - Routes must return data. This could be a
WP_REST_Response
object, a WP_Error
object or any kind of JSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. serializable data. This means the route must not directly echo the response and die()
. For example by using wp_send_json()
or wp_die()
. - Routes must be re-entrant. Be prepared for the same route to be called multiple times in a batch.
Making a Request
To send a batch, make a POST
request to https://yoursite.test/wp-json/batch/v1
with an array of the desired requests. For example, the simplest batch request looks like this.
{
"requests": [
{
"path": "/my-ns/v1/route"
}
]
}
Request Format
Each request is an object that can accept the following properties.
{
"method": "PUT",
"path": "/my-ns/v1/route/1?query=param",
"headers": {
"My-Header": "my-value",
"Multi": [ "v1", "v2" ]
},
"body": {
"project": "Gutenberg"
}
}
method
is the HTTP HTTP is an acronym for Hyper Text Transfer Protocol. HTTP is the underlying protocol used by the World Wide Web and this protocol defines how messages are formatted and transmitted, and what actions Web servers and browsers should take in response to various commands. method to use for the request. If omitted, the POST
method is used.path
is the REST API route to call. Query parameters can be included. This property is required.headers
is an object of header The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. names to a header values. If the header has multiple values, it can be passed as an array.body
is the parameters to pass to the route. It is filled in the POST
parameter type.
Discovering Max Requests
By default, the REST API accepts up to 25 requests in a single batch. However, this value is filterable so it can be scaled up or down based on server resources.
function my_prefix_rest_get_max_batch_size() {
return 50;
}
add_filter( 'rest_get_max_batch_size', 'my_prefix_rest_get_max_batch_size' );
As such, clients are strongly encouraged to make a preflight request to discover the limit. For example, making an OPTIONS
request to batch/v1
will return the following response.
{
"namespace": "",
"methods": [ "POST" ],
"endpoints": [
{
"methods": [ "POST" ],
"args": {
"validation": {
"type": "string",
"enum": [ "require-all-validate", "normal" ],
"default": "normal",
"required": false
},
"requests": {
"type": "array",
"maxItems": 25,
"items": {
"type": "object",
"properties": {
"method": {
"type": "string",
"enum": [ "POST", "PUT", "PATCH", "DELETE" ],
"default": "POST"
},
"path": {
"type": "string",
"required": true
},
"body": {
"type": "object",
"properties": [],
"additionalProperties": true
},
"headers": {
"type": "object",
"properties": [],
"additionalProperties": {
"type": [ "string", "array" ],
"items": {
"type": "string"
}
}
}
}
},
"required": true
}
}
}
],
"_links": {
"self": [
{
"href": "http://trunk.test/wp-json/batch/v1"
}
]
}
}
The limit is specified in the endpoints[0].args.requests.maxItems
property.
Response Format
The batch endpoint will return a 207
status code and the responses of each request in the same order as they were requested. For example:
{
"responses": [
{
"body": {
"id": 1,
"_links": {
"self": [
{
"href": "http://trunk.test/wp-json/my-ns/v1/route/1"
}
]
}
},
"status": 201,
"headers": {
"Location": "http://trunk.test/wp-json/my-n1/v1/route/1",
"Allow": "GET, POST"
}
}
]
}
Internally, the REST API envelopes each response before including it in the responses
array.
Validation Modes
By default, each request is processed in isolation. This means that a batch response can contain some requests that were successful, and some that failed. Sometimes it’s desired to only process a batch if all the requests are valid. For instance, in Gutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses ‘blocks’ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/ we don’t want to save some menu items, ideally all would be saved or none would.
To accomplish this, the REST API allows for passing a validation
mode of require-all-validate
. When this is set, the REST API will first check that each request is valid according to WP_REST_Request::has_valid_params()
and WP_REST_Request::sanitize_params()
. If any request fails validation, then the entire batch is rejected.
In this example, a batch of two requests is made and the second one has failed validation. Since the order of responses
is still the same as the order of requests
, null
is used to indicate that the request didn’t fail validation.
{
"failed": "validation",
"responses": [
null,
{
"body": {
"code": "error_code",
"message": "Invalid request data",
"data": { "status": 400 }
},
"status": 400,
"headers": {}
}
]
}
Note: Using require-all-validate
is not a guarantee that all requests will be successful. A route callback may still return an error.
Validate Callback
Those WP_REST_Request
methods use the validate_callback
and sanitize_callback
specified for each parameter when the route is registered. In most cases, this will mean the schema based validation.
Any validation done inside the route, for instance in the prepare_item_for_database
method, will not cause the batch to be rejected. If this is a concern, it is recommended to move as much validation as possible into the validate_callback
for each individual parameter. This can be built on top of the existing schema based validation, for instance.
'post' => array(
'type' => 'integer',
'minimum' => 1,
'required' => true,
'arg_options' => array(
'validate_callback' => function ( $value, $request, $param ) {
$valid = rest_validate_request_arg( $value, $request, $param );
if ( is_wp_error( $valid ) ) {
return $valid;
}
if ( ! get_post( $value ) || ! current_user_can( 'read_post', $value ) ) {
return new WP_Error( 'invalid_post', __( 'That post does not exist.' ) );
}
return true;
}
)
)
Sometimes when performing validation, the full context of the request is needed. Typically, this validation would have been done in prepare_item_for_database
, but WordPress 5.6 introduces an alternative. When registering a route, a top-level validate_callback
can now be specified. It will receive the full WP_REST_Request
object and can return a WP_Error
instance or false
. The callback won’t be executed if parameter-level validation did not succeed.
register_rest_route( 'my-ns/v1', 'route', array(
'callback' => '__return_empty_array',
'permission_callback' => '__return_true',
'validate_callback' => function( $request ) {
if ( $request['pass1'] !== $request['pass2'] ) {
return new WP_Error(
'passwords_must_match',
__( 'Passwords must match.' ),
array( 'status' => 400 )
);
}
return true;
}
) );
Note: Request validation happens before permission checks take place. Keep this in mind when considering whether to moving logic to a validate_callback
.
Limitations
No built-in routes currently allow batching. This will be added in a future release, most likely starting immediately with WordPress 5.7.
GET
requests are not supported. Developers are instead encouraged to use linking and embedding or utilize parallel requests for the time being.
Further Reading
See #50244, [49252], [48947], [48945].
Props @kadamwhite, @m_butcher, @jeffmatson for reviewing.
#5-6, #dev-notes, #rest-api