The WordPress REST API is still very new. As with many early early technologies, documentation seems to be the last thing to get updated. This is why it can be fun/educational to read through the source code in open source projects. You’ll find things the documentation didn’t teach you! (Or at least not in any documentation I could easily find.)
The custom endpoint I was creating required quite a few arguments when called. I wanted to include sanitization and/or validation of these arguments. Setting up a schema for the endpoint made sense. I knew I could perform validation by specifying a ‘validate_callback’ function in the argument definition.
However browsing the source code in WordPress’ core revealed a few bonuses built right into the core, in a function called rest_validate_value_from_schema
.
The ‘type’ attribute
By including in a ‘type
‘ attribute when defining an argument, the REST API’s validation will automatically check if the value passed into this argument is of the appropriate data type.
register_rest_route( 'route/v1', '/endpoint/', array(
'methods' => 'POST',
'callback' => array( $this, 'endpoint_post_handler' ),
'permissions_callback' => 'is_user_logged_in',
'args' => array(
'first_name' => array(
'required' => true,
'type' => 'string',
'description' => 'The client\'s first name',
),
)
) );
In this example above, the “first_name” argument is required, and must be a string. ‘type
‘ values that are supported in WordPress 4.7.2 are:
array
boolean
integer
number
string
NOTE: I could not get the ‘array’ type working properly in my tests. Once I figure out how to get this validation working properly, I’ll update this post.
If the value being passed to the REST endpoint for that argument does not match the data type specified by the type
attribute, an HTTP 400 (Bad Request) error will be returned. Data about the invalid argument is returned in the body as part of the JSON response. The screenshot below shows an example of what happens if you pass a string into an argument with type
set to boolean
:
The ‘enum’ attribute
The ‘enum
‘ attribute allows you to specify a list of valid values for this argument as an array. This is called an enumeration or an enumerated type.
register_rest_route( 'route/v1', '/endpoint/', array(
'methods' => 'POST',
'callback' => array( $this, 'endpoint_post_handler' ),
'permissions_callback' => 'is_user_logged_in',
'args' => array(
'language_preference' => array(
'required' => true,
'type' => 'string',
'description' => 'The locale in which this user would like to receive communications',
'enum' => array(
'en_CA',
'en_US',
'fr_CA',
),
),
)
) );
In the example above, my language_preference
endpoint is a string that only allows a predefined set of locale codes. If the value passed into the argument is not one of these items, an HTTP 400 error is returned. Data about the invalid argument is returned in the body as part of the JSON response.
The values are case-sensitive.
The ‘enum
‘ attribute works in conjunction with the ‘type
‘ attribute.
The ‘format’ attribute
The ‘format
‘ attribute allows you to enforce the format of a string. Accepted values are:
date-time
email
ip
uri
For example, the following endpoint must contain an email address:
register_rest_route( 'route/v1', '/endpoint/', array(
'methods' => 'POST',
'callback' => array( $this, 'endpoint_post_handler' ),
'permissions_callback' => 'is_user_logged_in',
'args' => array(
'email' => array(
'required' => true,
'type' => 'string',
'description' => 'The user\'s email address',
'format' => 'email'
),
)
) );
If this were not an email address, an HTTP 400 error is returned. Data about the invalid argument is returned in the body as part of the JSON response.
The date
value ensures that the argument contains an RFC3339 timestamp.
The ip
value accepts both IPv4 and IPv6 addresses.
The uri
value is a sanitization function. It will run the value through the esc_url_raw function to format it as a URI.
Numeric Ranges
There are a few attributes you can assign your endpoints arguments that will be used to check the value of both integer and numeric values. ( Setting the 'type'
attribute to integer
or number
is required). These attributes are:
- minimum
- maximum
- exclusiveMinimum
- exclusiveMaximum
The minimum
and maximum
attributes contain the minimum and maximum values that will be accepted for this argument. The exclusiveMinimum
and exclusiveMaximum
attributes, when present, specify that the value must be greater less or less the specified number, but can’t be that number.
For example, the following endpoint accepts a ‘price’ argument with an integer value between 0 and 1000 (inclusive). Any value between 0 and 1000, including 0 or 1000, will be accepted.
register_rest_route( 'route/v1', '/endpoint/', array(
'methods' => 'POST',
'callback' => array( $this, 'endpoint_post_handler' ),
'permissions_callback' => 'is_user_logged_in',
'args' => array(
'price' => array(
'required' => true,
'type' => 'integer',
'description' => 'Product value',
'minimum' => 0,
'maximum => 1000,
),
)
) );
The following endpoint accepts a ‘zero_one_two’ argument with an integer value between 0 and 3 (exclusive). This means that values 0, 1 and 2 will be accepted, but 3 will not.
register_rest_route( 'route/v1', '/endpoint/', array(
'methods' => 'POST',
'callback' => array( $this, 'endpoint_post_handler' ),
'permissions_callback' => 'is_user_logged_in',
'args' => array(
'zero_one_two' => array(
'required' => true,
'type' => 'integer',
'description' => 'Product value',
'minimum' => 0,
'maximum' => 3
'maximumExclusive => true,
),
)
) );
This would be the same as using a minimum of 0 and a maximum of 2. The exclusiveMinimum
and exclusiveMaximum
attributes offer flexibility in how you define these ranges, and the wording of the returned error when a value falls outside these ranges.
Have fun!
These additional options should help you write better REST endpoints with less code than custom validation and sanitization functions.
Have fun!
Thanks for this post! It seems that the type value is case-sensitiv and ‘Array’ does the trick.
Thank you, very helpful! I still can’t see any of this documented in the official handbook so this is a really helpful resource. Thanks for putting it together.
You’re welcome Andy. Glad you found it useful!
I got here because I was having trouble with the array type too. Looks like it is still buggy.
After posting my last comment, I had a face-palm moment and realized how the array type is supposed to work.
The reason it doesn’t work is because you have to tell it what kind of array you expect and define its schema. Array of strings? objects? numbers?
So when you declare an array type, just add another “items” key with:
“items” => [
“type” => “string”, // or put whatever type you need here
]
Thanks for this post. After a bit of trial and error I found you can use the array type like so:
‘colors’ => [
‘type’ => ‘array’,
‘items’ => [
‘type’ => ‘string’,
‘enum’ => [
‘Red’,
‘Green’,
‘Blue’,
],
],
],
Thanks, I keep referring to this post every now and again. Just a note about arrays:
‘type’ => ‘array’ only seems to work for “regular” “non-associative” arrays if an ‘items’ is passed along with it.
For associative arrays, I’ve found ‘type’ => ‘object’ works great.
Let me know what you think!
Thank you so much for this! Exactly what I was looking for!
You can also nest arrays, validate items within associative arrays, and set a number of ‘maxItems’ for an array like so:
‘args’ => array(
‘validation’ => array(
‘type’ => ‘string’,
‘enum’ => array( ‘require-all-validate’, ‘normal’ ),
‘default’ => ‘normal’,
),
‘requests’ => array(
‘required’ => true,
‘type’ => ‘array’,
‘maxItems’ => $this->get_max_batch_size(),
‘items’ => array(
‘type’ => ‘object’,
‘properties’ => array(
‘method’ => array(
‘type’ => ‘string’,
‘enum’ => array( ‘POST’, ‘PUT’, ‘PATCH’, ‘DELETE’ ),
‘default’ => ‘POST’,
),
‘path’ => array(
‘type’ => ‘string’,
‘required’ => true,
),
‘body’ => array(
‘type’ => ‘object’,
‘properties’ => array(),
‘additionalProperties’ => true,
),
‘headers’ => array(
‘type’ => ‘object’,
‘properties’ => array(),
‘additionalProperties’ => array(
‘type’ => array( ‘string’, ‘array’ ),
‘items’ => array(
‘type’ => ‘string’,
),
),
),
),
),
),
)
I’m also curious about the ‘additionalProperties’ parameter but I haven’t used it yet. There’s still a lot left to explore here!
Also, just as a note, this code is pulled from the WP_Rest_Server class on line 109: https://developer.wordpress.org/reference/classes/wp_rest_server/
It’s also formatted there.
It looks like there may also be options for a “validate_callback” and a “sanitize_callback” on individual JSON parameters.
For example:
‘args’ => array(
‘key’ => array(
‘required’ => true,
‘type’ => ‘string’,
‘sanitize_callback’ => array( ‘Akismet_REST_API’, ‘sanitize_key’ ),
‘description’ => __( ‘A 12-character Akismet API key. Available at akismet.com/get/’, ‘akismet’ ),
),
),
See the code block here for an example of the validate callback: https://developer.wordpress.org/rest-api/requests/#internal-requests
And see this source code from Akismet for an example of the sanitize callback: https://plugins.trac.wordpress.org/browser/akismet/tags/4.1.10/class.akismet-rest-api.php#L26