Skip to content
wp-skills
Register
tools/rest-route-generator

WordPress REST API Route Generator

Register a custom REST API endpoint with register_rest_route(): the rest_api_init hook, a permission_callback you have to choose, an argument schema WordPress validates for you, and a handler that returns WP_Error or a response.

Vendor and version, with no slashes at either end — register_rest_route() joins this to the route, so a leading slash produces a URL nobody requests.

A regex fragment. A named capture like (?P<id>\d+) is what puts a value into $request['id'], and the name has to match an argument below for the schema to apply.

Methods

Runs on rest_api_init. Registering on init instead is the usual mistake — it runs too early on a REST request and the route never appears.

Required since WordPress 5.5. Choosing 'Public' writes __return_true, which is a real answer and not a placeholder: it passes for every request on the internet.

Arguments1
  • #1

    Match a named capture in the route to give that capture a schema.

    WordPress validates and casts against this on its own — since 5.5 no validate_callback is needed to make it bite.

    A missing required argument is answered with 400 before the handler runs.

    Comma separated. Anything outside the list is a 400. Leave blank to accept any value of the declared type.

snippet.php
/**
 * Registers the my-plugin/v1/items/(?P<id>\d+) route.
 *
 * The hook is 'rest_api_init' rather than 'init'. Registering on 'init' runs
 * far too early on a REST request and the route never appears.
 */
function wps_register_items_route() {
	register_rest_route(
		'my-plugin/v1',
		'/items/(?P<id>\\d+)',
		array(
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => 'wps_items_handler',
			'permission_callback' => 'wps_items_permission',
			'args'                => array(
				'id' => array(
					'type'        => 'integer',
					'required'    => true,
					'description' => 'The item to return.',
				),
			),
		)
	);
}
add_action( 'rest_api_init', 'wps_register_items_route' );

/**
 * Answers the request.
 *
 * Read parameters off $request with array access — it merges the URL captures,
 * the query string and the JSON body, so the handler does not care where a
 * value arrived from.
 *
 * Return anything JSON-serialisable and WordPress wraps it in a 200. Return a
 * WP_Error to choose a different status.
 */
function wps_items_handler( WP_REST_Request $request ) {
	$id = $request['id'];

	return rest_ensure_response(
		array(
			'success' => true,
		)
	);
}

/**
 * Who may call this route.
 *
 * Returning false sends 401 to a logged-out caller and 403 to a logged-in one,
 * which WordPress works out on its own. Returning a WP_Error instead lets you
 * choose the status and the message.
 */
function wps_items_permission() {
	return current_user_can( 'edit_posts' );
}