register_rest_route()WP 4.4.0

Registers a REST API route and its endpoints. Simply put, registers the URL at which the specified PHP function will be triggered.

The function should be called on the action rest_api_init.

Since WP 5.5.0 the permission_callback parameter became mandatory! If you don't specify it, you will get the following notice in debug.log:

PHP Notice: register_rest_route was called incorrectly. The REST API route registration for /foo is missing the required argument permission_callback. For public REST API routes, use __return_true as the callback function.

Read more here: https://developer.wordpress.org/rest-api/extending-the-rest-api/routes-and-endpoints/#permissions-callback.

No Hooks.

Returns

true|false. True on success and false on failure.

Usage template

add_action( 'rest_api_init', function () {

	register_rest_route( 'myplugin/v1', '/my_slug/(?P<param_name>.+)', array(
		'methods'             => 'GET',            // request method: GET, POST ...
		'callback'            => 'function_name',  // request handler function. Must return the response to the request
		'permission_callback' => 'function_name',  // route access check function. Must return true/false
		// description of passed parameters
		'args' => array(
			'param_name' => array(
				'default'           => null,           // default value of the parameter
				'required'          => null,           // whether the parameter is required. Can only be true
				'validate_callback' => 'function_name', // parameter value validation function. Must return true/false
				'sanitize_callback' => 'function_name', // parameter value sanitization function. Must return the sanitized value
			),
			'param_name2' => array(
				...
			)
			...
		),
	) );

} );

Usage

register_rest_route( $namespace, $route, $args, $override );
$namespace(string) (required)
The first part of the route (URL) that comes after the REST prefix (/wp-json/). Must be unique, usually a unique name of your plugin or theme is used here, e.g. myplugin/v1. See more about namespaces in REST.
$route(string) (required)
The second part of the route (URL). Supports regular expressions, e.g. /author/(?P<id>\d+).
$args(array)

Array of parameters for the endpoint.
With these parameters you can finely tune the endpoint request handling.

If you need to create several endpoints, the array should contain nested arrays describing each endpoint.

The array for a single endpoint by default looks like this:

$args = [
	'methods'  => 'GET',
	'callback' => null,
	'permission_callback' => null,
	'args'     => array(),
];

Example of how to specify multiple endpoints (methods):

$args = [
	// common args
	'args' => [
		'id' => [
			'description' => __( 'Unique identifier for the term.' ),
			'type'        => 'integer',
		],
	],
	// GET
	[
		'methods'  => 'GET',
		'callback' => null,
		'permission_callback' => null,
		'args'     => [],
	],
	// POST
	[
		'methods'  => 'POST',
		'callback' => null,
		'permission_callback' => null,
		'args'     => [],
	],
]

Description of all possible arguments see below.

The array created here is inserted as-is into the property WP_REST_Server::$endpoints.

$override(boolean)

Determines whether to overwrite data if such a route already exists:

  • true - override
  • false - merge using array_merge().

Default: false

Arguments of the $args parameter

methods(string/array)

Defines by which request method the endpoint will be available: GET (default), POST, PUT, PATCH, DELETE. Multiple methods can be specified as a comma-separated string or an array.

You can use predefined constants of the WP_REST_Server class:

WP_REST_Server::READABLE    // GET
WP_REST_Server::CREATABLE   // POST
WP_REST_Server::EDITABLE    // POST, PUT, PATCH
WP_REST_Server::DELETABLE   // DELETE
WP_REST_Server::ALLMETHODS  // GET, POST, PUT, PATCH, DELETE
callback(callback function)

Specifies the name of the function that will be triggered when accessing (requesting) the URL (rest route). In the function you should build and return the data. The returned data will be converted to JSON and output.

In this parameter you can use your own function or any other function that returns a result, for example get_posts().

The function specified in this parameter receives a parameter of the WP_REST_Request class, which contains the request parameters and allows forming an accurate response. For example, calling get_posts() directly will return results according to its default settings, but if you wrap it in your function and pass the received author ID parameter to get_posts(), you can filter posts by author (see the first example).

Since the function receives an object of class WP_REST_Request, we have methods available to get various information:

// Registers a route
add_action( 'rest_api_init', function () {
	register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', array(
		'methods'  => 'GET',
		'callback' => 'my_rest_api_func',
	) );
} );

// Access the URL http://wp-test.ru/wp-json/myplugin/v1/author/1

function my_rest_api_func( WP_REST_Request $request ) {
	// You can access parameters via direct array access on the object:
	$param = $request['id']; // 1

	// Or using the method:
	$param = $request->get_param( 'id' ); // 1

	// Array of all parameters
	$parameters = $request->get_params(); // Array([id] => 1)

	// If necessary, separate parameter groups are also available:
	$parameters = $request->get_url_params(); // Array([id] => 1)

	$parameters = $request->get_query_params(); // Array()
	// If you request http://wp-test.ru/wp-json/myplugin/v1/author/1?post=1
	$parameters = $request->get_query_params(); // Array( [post] => 1 )

	$parameters = $request->get_body_params(); // Array()
	$parameters = $request->get_json_params(); // null - there was no request with header Content-type: application/json
	$parameters = $request->get_default_params(); // Array()

	// Upload data are not merged, but may be available separately:
	$parameters = $request->get_file_params();

	// ways to return data

	return new WP_REST_Response( true, 200 );

	return new WP_Error( 'no_author', 'Invalid author', array( 'status' => 404 ) );

	return 'строка';

	return array( 'foo'=>'bar' );

	// get WP_REST_Response object
	$response = rest_ensure_response( array( 'foo'=>'bar' ) );
	$response->set_status( 401 );
	$response->set_headers( [
		'X_REAL_IP'      => '54.15.124.126',
	] );
	return $response;
}
permission_callback(callback function) (required)

Allows checking the rights of the user requesting the route. Access will be granted if true is returned, and denied if false.

Since WordPress 5.5 this function must be specified!

Example of allowing access to all users:

register_rest_route( 'myplugin/v1', '/awesome-route', array(
	'methods'             => WP_REST_Server::READABLE,
	'callback'            => 'my_awesome_func',
	'permission_callback' => '__return_true'
) );

Example of checking user capabilities:

register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', array(
	'methods'             => WP_REST_Server::READABLE,
	'callback'            => 'my_awesome_func',
	'permission_callback' => 'my_awesome_permission_callback'
) );

function my_awesome_permission_callback ( WP_REST_Request $request ) {
	return current_user_can( 'edit_others_posts' );
}

Let the function my_awesome_func() return posts of the specified user. If the user has the capability to edit others' posts (for example, an editor), the required data will be returned. If the current user does not have such rights, an error will be returned:

{
	"code": "rest_forbidden",
	"message": "Sorry, you are not allowed to perform this action.",
	"data": {
		"status": 401
	}
}

The same error will be returned if you don't pass a nonce; read more in the Authentication in WP REST API section.

show_in_index(true/false)
Whether to show the route in the index of all routes.
args(array)

Possible endpoint parameters. Each array element describes one parameter. The element key is the name of the request parameter, and the value is a nested array describing that parameter.

Some parameter data is automatically added to the route schema (for example description, type, required ...). See rest_get_allowed_schema_keywords().

Possible arguments of the array:

description
default
required
sanitize_callback
validate_callback

// validation and sanitization
type
	string
		minLength / maxLength
		pattern
		format
	null
	boolean
	number / integer
		minimum / maximum
		exclusiveMinimum / exclusiveMaximum
		multipleOf
	array
		items
		minItems / maxItems
		uniqueItems
	object
		properties
		additionalProperties
		patternProperties
		minProperties / maxProperties
enum
oneOf / anyOf

REST data types. Validation and sanitization — see the full list of possible parameters and their descriptions there.

  • description(string) (appears in schema)
    Description of the parameter.

  • default(mixed) (appears in schema)
    Default value for the argument if it is not provided.

    register_rest_route( 'myplugin/v1', '/author/(?P<key>\d+)', array(
    	'methods'  => WP_REST_Server::READABLE,
    	'callback' => 'my_awesome_func',
    	'args'     => array(
    		'key' => array(
    			'default' => 1
    		),
    	),
    ) );
  • required(true/false) (appears in schema)
    If set to true and the specified parameter is not defined (not included in the request), an error will be returned.

    The parameter must not be present at all in the request for this setting to take effect, i.e. if it is present but its value is empty, it is considered present with an empty value.

    required makes no sense if default is set, because the argument will always have a value.

    register_rest_route( 'myplugin/v1', '/author/(?P<key>\d+)', array(
    	'methods'  => WP_REST_Server::READABLE,
    	'callback' => 'my_awesome_func',
    	'args'     => array(
    		'key' => array(
    			'required' => false // or true - required parameter
    		),
    	),
    ) );
  • validate_callback(string/callback)
    Data validation function. The function must return true if validation passed, and false otherwise.

    The function may also return a WP_Error object, in which case the message will be added to the response object. For example, if you return such an object:

    new WP_Error( 'err', 'Must match `^[a-zA-Z0-9:_-]+$` regex.' )

    The response will be:

    {
    	"code": "rest_invalid_param",
    	"message": "Invalid parameter: name",
    	"data": {
    		"status": 400,
    		"params": {
    			"name": "Must match `^[a-zA-Z0-9:_-]+$` regex."
    		},
    		"details": {
    			"name": {
    				"code": "err",
    				"message": "Must match `^[a-zA-Z0-9:_-]+$` regex.",
    				"data": null
    			}
    		}
    	}
    }

    Let the function my_awesome_func() return a list of posts of the specified author, then:

    register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', [
    	'methods'  => 'GET',
    	'callback' => 'my_awesome_func',
    	'args'     => [
    		'id' => [
    			'validate_callback' => function( $param, $request, $key ) {
    				return is_numeric( $param );
    			},
    		],
    	],
    ] );

    Request posts of author with ID = 1 at the address (assuming posts exist):

    http://wp-test.ru/wp-json/myplugin/v1/author/1

    An array of posts will be returned. But if you access:

    http://wp-test.ru/wp-json/myplugin/v1/author/vasya

    An error will be returned:

    {
    	"code": "rest_no_route",
    	"message": "No route was found matching the URL and request method",
    		"data": {
    		"status": 404
    	}
    }

    Because the id parameter did not pass the is_numeric() check, since the value vasya is not a number.

    You might wonder why not just specify the function is_numeric() like this:

    register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', array(
    	'methods'  => WP_REST_Server::READABLE,
    	'callback' => 'my_awesome_func',
    	'args'     => array(
    		'id' => array(
    			'validate_callback' => 'is_numeric'
    		),
    	),
    ) );
    

    But, as you can see from the example above, three parameters $param, $request, $key will be passed to the specified function and if all of them are passed to is_numeric() — a warning-level error will occur.

    [29-May-2018 12:07:54 UTC] PHP Warning:  is_numeric() expects exactly 1 parameter,
    3 given in \sites\example.com\wp-includes\rest-api\class-wp-rest-request.php on line 858

    There is a ticket on this subject.

  • sanitize_callback(string/callback)
    Data sanitization function. Similar to validate_callback, but instead of true/false you should return the sanitized incoming value. Or you can return a WP_Error object.

    register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', array(
    	'methods'  => WP_REST_Server::READABLE,
    	'callback' => 'my_awesome_func',
    	'args'     => array(
    		'id' => array(
    			'sanitize_callback' => function ( $param, $request, $key ) {
    				return (int) $param;
    			}
    		),
    	),
    ) );
  • type(string) (appears in schema)
    The type of the parameter value. Possible values: array, object, string, number, integer, boolean, null. If another type is specified, a 400 error will be returned. More about this parameter.

  • minimum / maximum(integer) (used in schema)
    Minimum or maximum value of the parameter. Only for type = integer.

  • format(string) (used in schema)
    Additional string format. Values will be validated against the specified format. More.

  • enum(array) (used in schema)
    List of possible parameter values (when the parameter has a limited set of possible values).

Examples

1

#1 Registers an API route with multiple parameters.

//The Following registers an api route with multiple parameters. 
add_action( 'rest_api_init', 'add_custom_users_api');

function add_custom_users_api(){

	register_rest_route( 'mmw/v1', '/users/market=(?P<market>[a-zA-Z0-9-]+)/lat=(?P<lat>[a-z0-9 .\-]+)/long=(?P<long>[a-z0-9 .\-]+)', [
		'methods' => 'GET',
		'callback' => 'get_custom_users_data',
		'permission_callback' => 'permission_check',
	] );
}

Make sure that your regex expressions are fine. If the data does not match then the URL will return a 404.

Some examples are:

(?P[a-zA-Z0-9-]+) for slug (you can change slug for your custom name).
(?P\d+) for id.
(?P[a-z0-9 .\-]+) for longitude or latitude.

// Customize the callback to your liking
function get_custom_users_data( $data ){

	// get users by market
	$users = mmw_get_custom_users();

	foreach ( $users as $key => $user ) {
		$market = $user['Market'];
		$long = $user['long'];
		$lat = $user['lat'];

		if( intval($market) === intval( trim($data['market']) ) ){

			$result[] = array(
				'user_login' => $user->user_login,
				'avatar_url' => get_avatar_url($user->ID),
				'lat' => $lat,
				'long' => $long
			);
		}
	}

	return $result;
}
0

#2 Getting the posts of the specified author

WordPress has default methods for this purpose, but let's create our own simple version as an example:

// registers the route
add_action( 'rest_api_init', function () {

	register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', array(
		'methods'  => 'GET',
		'callback' => 'my_awesome_func',
		// Here we register our permissions callback. 
		// The callback is fired before the main callback to check if
		// the current user can access the endpoint.
		'permission_callback' => 'prefix_get_private_data_permissions_check',
	) );
} );

// Processes the request
function my_awesome_func( WP_REST_Request $request ) {

	$posts = get_posts( array(
		'author' => (int) $request['id'],
	) );

	if ( empty( $posts ) )
		return new WP_Error('no_author_posts', 'No posts found', array( 'status' => 404 );

	return $posts;
}

Now let's get the author's posts with ID = 1 using a GET request to the address:

http://wp-test.ru/wp-json/myplugin/v1/author/1

We'll get an answer in json format if the posts are found:

[
	{
		"ID": 280,
		"post_author": "1",
		"post_date": "2018-04-10 21:06:29",
		"post_date_gmt": "2018-04-10 18:06:29",
		{ "post_content": "Hi! This article should be an exception.\r\n\r\n[smr_slider id='1']"
		"post_title": "more article",
		"post_excerpt": "",
		"post_status": "publish",
		"comment_status": "open",
		"ping_status": "open",
		"post_password": "",
		"post_name": "eshhyo-statya",
		"to_ping": "",
		"pinged": "",
		"post_modified": "2018-05-25 11:34:03",
		"post_modified_gmt": "2018-05-25 08:34:03",
		"post_content_filtered": "",
		"post_parent": 0,
		"guid": "http://wp-test.ru/?p=280",
		"menu_order": 0,
		"post_type": "post",
		"post_mime_type": "",
		"comment_count": "0",
		"filter": "raw"
	},
	{
		"ID": 273,
		...
	}
	...
]

We'll get the answer in json format if the author has no posts:

{
	"code": "no_author_posts",
	{ "message": "No posts found",
	"data": {
		"status": 404
	}
}

You can refine the function to get only the values you want:

// Process the request
function my_awesome_func( WP_REST_Request $request ) {

	// Get the posts of the specified author
	$posts = get_posts( array(
		'author' => (int) $request['id'],
	) );

	if( empty( $posts ) )
		return new WP_Error('no_author_posts', 'No posts found', array( 'status' => 404 );

	// Create your data to return
	$posts = array_map( function ( $post ) {
		$post_data['title']    = esc_html( $post->post_title );
		$post_data['url']      = esc_url( get_the_permalink( $post ) );
		$post_data['comments'] = (int) $post->comment_count;
		$post_data['likes']    = (int) get_post_meta( $post->ID, 'likes', true );

		return $post_data;
	}, $posts );

	return $posts;
}

If there are posts, get an answer:

[
	{
		{ "title": "more article",
		"url": "http://wp-test.ru/eshhyo-statya",
		"comments": 3,
		"likes": 11
	},
	{
		{ "title": "my article",
		...
	},
	...
]
0

#3 More examples

See REST API Tutorial under Creating Routes. There you will find, for example, an example of creating routes based on the Controller Class (OOP).

0

#4 Args is a named array that usually includes the keys ‘methods’ and ‘callback’.

method defines which HTTP methods are to be processed by the function defined by ‘callback’.

method can be a string of comma-separated HTTP methods or an array of strings of HTTP methods.

A common practice is to use the WP_REST_Server constants to set the 'method'.

WP_REST_Server::READABLE = 'GET'

WP_REST_Server::EDITABLE = 'POST, PUT, PATCH'

WP_REST_Server::DELETABLE = 'DELETE'

WP_REST_Server::ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE'

Example:

// Get a list of locations.
// Method GET via wp-json/store-locator-plus//locations/
//
// Calls the get_locations method of the current class when the route is matched.
//
register_rest_route( 'my-plugin-slug/v2' , '/locations/', [
	'methods' => 'GET',
	'callback' => [ this, 'get_locations' ],
	'permission_callback' => 'permission_check',
] );
0

#5 The second args array - parameters of parameter

args can also contain an optional args array of single parameter parameters.

The second args array contains the default arguments, required arguments, validation callback, and sanitization callback that are in place for each argument passed in with the REST request.

The key name of this array is the name of the parameter being passed.

Example

add_action( 'rest_api_init', function() {

	register_rest_route( 'myplugin/v1', '/author/(?P\d+)', [
		'methods'  => 'GET',
		'callback' => 'my_awesome_func',
		'args'     => [
			'id' => [
				'validate_callback' => function( $param, $request, $key ) {
					return is_numeric( $param );
				},
			],
		],
	] );
} );

Changelog

Since 4.4.0 Introduced.
Since 5.1.0 Added a _doing_it_wrong() notice when not called on or after the rest_api_init
Since 5.5.0 Added a _doing_it_wrong() notice when the required permission_callback argument is not set.

register_rest_route() code WP 6.9.1

function register_rest_route( $route_namespace, $route, $args = array(), $override = false ) {
	if ( empty( $route_namespace ) ) {
		/*
		 * Non-namespaced routes are not allowed, with the exception of the main
		 * and namespace indexes. If you really need to register a
		 * non-namespaced route, call `WP_REST_Server::register_route` directly.
		 */
		_doing_it_wrong(
			__FUNCTION__,
			sprintf(
				/* translators: 1: string value of the namespace, 2: string value of the route. */
				__( 'Routes must be namespaced with plugin or theme name and version. Instead there seems to be an empty namespace \'%1$s\' for route \'%2$s\'.' ),
				'<code>' . $route_namespace . '</code>',
				'<code>' . $route . '</code>'
			),
			'4.4.0'
		);
		return false;
	} elseif ( empty( $route ) ) {
		_doing_it_wrong(
			__FUNCTION__,
			sprintf(
				/* translators: 1: string value of the namespace, 2: string value of the route. */
				__( 'Route must be specified. Instead within the namespace \'%1$s\', there seems to be an empty route \'%2$s\'.' ),
				'<code>' . $route_namespace . '</code>',
				'<code>' . $route . '</code>'
			),
			'4.4.0'
		);
		return false;
	}

	$clean_namespace = trim( $route_namespace, '/' );

	if ( $clean_namespace !== $route_namespace ) {
		_doing_it_wrong(
			__FUNCTION__,
			sprintf(
				/* translators: 1: string value of the namespace, 2: string value of the route. */
				__( 'Namespace must not start or end with a slash. Instead namespace \'%1$s\' for route \'%2$s\' seems to contain a slash.' ),
				'<code>' . $route_namespace . '</code>',
				'<code>' . $route . '</code>'
			),
			'5.4.2'
		);
	}

	if ( ! did_action( 'rest_api_init' ) ) {
		_doing_it_wrong(
			__FUNCTION__,
			sprintf(
				/* translators: 1: rest_api_init, 2: string value of the route, 3: string value of the namespace. */
				__( 'REST API routes must be registered on the %1$s action. Instead route \'%2$s\' with namespace \'%3$s\' was not registered on this action.' ),
				'<code>rest_api_init</code>',
				'<code>' . $route . '</code>',
				'<code>' . $route_namespace . '</code>'
			),
			'5.1.0'
		);
	}

	if ( isset( $args['args'] ) ) {
		$common_args = $args['args'];
		unset( $args['args'] );
	} else {
		$common_args = array();
	}

	if ( isset( $args['callback'] ) ) {
		// Upgrade a single set to multiple.
		$args = array( $args );
	}

	$defaults = array(
		'methods'  => 'GET',
		'callback' => null,
		'args'     => array(),
	);

	foreach ( $args as $key => &$arg_group ) {
		if ( ! is_numeric( $key ) ) {
			// Route option, skip here.
			continue;
		}

		$arg_group         = array_merge( $defaults, $arg_group );
		$arg_group['args'] = array_merge( $common_args, $arg_group['args'] );

		if ( ! isset( $arg_group['permission_callback'] ) ) {
			_doing_it_wrong(
				__FUNCTION__,
				sprintf(
					/* translators: 1: The REST API route being registered, 2: The argument name, 3: The suggested function name. */
					__( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ),
					'<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>',
					'<code>permission_callback</code>',
					'<code>__return_true</code>'
				),
				'5.5.0'
			);
		}

		foreach ( $arg_group['args'] as $arg ) {
			if ( ! is_array( $arg ) ) {
				_doing_it_wrong(
					__FUNCTION__,
					sprintf(
						/* translators: 1: $args, 2: The REST API route being registered. */
						__( 'REST API %1$s should be an array of arrays. Non-array value detected for %2$s.' ),
						'<code>$args</code>',
						'<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>'
					),
					'6.1.0'
				);
				break; // Leave the foreach loop once a non-array argument was found.
			}
		}
	}

	$full_route = '/' . $clean_namespace . '/' . trim( $route, '/' );
	rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override );
	return true;
}