GeneratorsCoreREST Route

REST Route Generator

Namespaced endpoints with a real args schema, sanitise and validate callbacks, and a permission callback that is never just __return_true by accident.

items-route.php
Saved just now
The endpoint
/wp-json/myplugin/v1/items
Methods
Each method gets its own callback.
Who can call it
permission_callback is required — omitting it throws a notice.
Readable by anyone and cached by proxies. Never expose private data or write operations here.
Arguments
Becomes the args schema — WordPress validates before your callback runs.
per_pageinteger
absint() → rest_validate_request_arg() · min/max
searchstring
sanitize_text_field() → rest_validate_request_arg()
Extras
Also register a REST field
Adds a register_rest_field() example that extends the core post response.
Send cache headers
Adds a rest_post_dispatch filter with Cache-Control. Public GET routes only.
Paste into a plugin, an mu-plugin, or a functionality plugin.
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Register the /items endpoint.
 */
function myplugin_register_items_route() {
	register_rest_route(
		'myplugin/v1',
		'/items',
		array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => 'myplugin_items_get',
				'permission_callback' => 'myplugin_items_permission',
				'args'                => array(
					'per_page' => array(
						'description'       => __( 'How many items to return.', 'textdomain' ),
						'type'              => 'integer',
						'default'           => 10,
						'minimum'           => 1,
						'maximum'           => 50,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'search' => array(
						'description'       => __( 'Optional search term.', 'textdomain' ),
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			),
		)
	);
}
add_action( 'rest_api_init', 'myplugin_register_items_route' );

/**
 * Permission check for /items.
 *
 * @return true|WP_Error
 */
function myplugin_items_permission() {
	return true;
}

/**
 * GET /items
 *
 * @param WP_REST_Request $request Request object.
 * @return WP_REST_Response|WP_Error
 */
function myplugin_items_get( $request ) {
	$per_page = $request->get_param( 'per_page' );
	$search = $request->get_param( 'search' );

	$query = new WP_Query(
		array(
			'post_type'      => 'post',
			'post_status'    => 'publish',
			'posts_per_page' => $per_page,
			'no_found_rows'  => true,
		)
	);

	$items = array_map(
		static function ( $post ) {
			return array(
				'id'    => $post->ID,
				'title' => get_the_title( $post ),
				'link'  => get_permalink( $post ),
			);
		},
		$query->posts
	);

	return rest_ensure_response( $items );
}

About this generator

REST Route Generator Online

A register_rest_route generator that produces the whole endpoint: the registration on rest_api_init, a named permission callback, a typed args schema with sanitize_callback and validate_callback on every parameter, and a handler that returns through rest_ensure_response(). You choose the namespace, the route and the methods; it writes the rest.

The Calling it tab shows the finished endpoint URL with your default query arguments applied, plus ready examples for apiFetch, plain fetch with an X-WP-Nonce header, and curl with an application password. Free to use, no account, and the code is built in your browser.

Reviewed Output tested on WordPress 6.8 · PHP 8.3

Why the register_rest_route generator beats a copied endpoint

Custom endpoints go wrong in two directions. Either they are too open — a public write route that anyone on the internet can call — or they are unvalidated, taking whatever $request->get_param() returns straight into a query. The args schema and the permission callback are the two things this generator refuses to leave blank.

A permission callback, always

WordPress 5.5 made permission_callback mandatory and triggers a _doing_it_wrong() notice without it. Every route here gets a real named function: return true for a genuinely public read, is_user_logged_in(), or a current_user_can() check that returns a WP_Error with rest_authorization_required_code() when it fails.

Public writes flagged as an error

A public permission callback on a POST, PUT, PATCH or DELETE route is an open door, so it is reported as an error with a one-click switch to requiring edit_posts. Mixing a public GET with writes on the same route raises a warning of its own.

A schema WordPress validates before your callback runs

Each argument emits type, plus required, default, enum or minimum/maximum where you set them, a sanitize_callback matched to the type (absint, floatval, rest_sanitize_boolean, sanitize_email, esc_url_raw or sanitize_text_field) and validate_callback set to rest_validate_request_arg.

Route parameters read properly

Named capture groups such as (?P<id>[\d]+) are detected and pulled into the handler with $request->get_param(). Writing /items/{id} instead is an error — WordPress routes use PCRE named groups, not braces — and a capture with no matching argument entry is flagged as neither sanitised nor documented.

Namespace conventions checked

The vendor/v1 shape is what makes it possible to change the response later without breaking clients, so a namespace that does not match is flagged. Registering into the wp/ namespace is an error: that one belongs to core.

Handlers you can run immediately

Start from a WP_Query list that maps posts to id, title and link, an option read/write pair with a WP_Error branch when the save fails, or an empty stub. Optional extras add a register_rest_field() example and Cache-Control headers — with a warning if you try to cache an authenticated route.

How does the REST Route Generator work?

Four steps, and the finished URL is shown as you type so you can see exactly what you are building.

  1. 01

    Define the endpoint

    Set the namespace, the route and a function prefix, then choose a handler style. The full path — /wp-json/myplugin/v1/items — updates live underneath.

  2. 02

    Choose methods and permissions

    Pick the HTTP methods the route answers, then the permission level: public, any logged-in user, a named capability, editors or administrators. Each choice explains what it really allows.

  3. 03

    Declare the arguments

    Add each parameter with a type, an optional format (email, URI, date-time, enum, min/max range), a default and whether it is required. Required arguments with a default are flagged, since the default can never apply.

  4. 04

    Test the call, then export

    Use the Calling it tab to copy an apiFetch, fetch or curl example, clear the Checks tab, then copy or download the endpoint as a snippet, a functions.php block or a plugin file.

Worked example — a public GET route with a validated per_page argument

The registration half of a myplugin/v1 endpoint. The handler and the permission function are generated below it in the same file.

/**
 * Register the /items endpoint.
 */
function myplugin_register_items_route() {
	register_rest_route(
		'myplugin/v1',
		'/items',
		array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => 'myplugin_items_get',
				'permission_callback' => 'myplugin_items_permission',
				'args'                => array(
					'per_page' => array(
						'type'              => 'integer',
						'default'           => 10,
						'minimum'           => 1,
						'maximum'           => 50,
						'sanitize_callback' => 'absint',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			),
		)
	);
}
add_action( 'rest_api_init', 'myplugin_register_items_route' );

Because minimum and maximum are declared, a request for per_page=500 is rejected by WordPress with a 400 before myplugin_items_get() ever runs. The permission callback is written as its own named function rather than __return_true, so making the route public later is a decision you can find in the code.

WordPress REST API routes — frequently asked questions

The questions developers hit when adding their first custom endpoint.

What does "missing the required permission_callback argument" mean?

Since WordPress 5.5 every registered route must declare permission_callback, and omitting it triggers a _doing_it_wrong() notice. Add a callback that returns true, false or a WP_Error. For a route that really is meant to be public, core's own guidance is to use __return_true — the requirement exists so that being public is an explicit decision rather than an oversight.

Is __return_true safe as a permission callback?

For a read-only endpoint that exposes nothing private, yes — it is the documented way to declare a public route. It is not safe on anything that writes, deletes or reads private data, because a public route is reachable by anyone on the internet without a cookie or a key. Use current_user_can() with the capability that matches the operation for those.

How do I add a URL parameter such as /items/123 to a REST route?

Put a named PCRE capture group in the route: /items/(?P<id>[\d]+). WordPress puts the capture into the request, so the handler reads it with $request->get_param( 'id' ). Declare it in the args array as well so it is sanitised and validated, and note that {id} braces are not WordPress syntax — that route simply never matches.

Why do I get rest_no_route or a 404 from my custom endpoint?

Usually one of four things: the registration is not on rest_api_init, so it runs too late or not at all; the namespace or route in the URL does not match what was registered exactly, including the leading slash; the code lives in a plugin that is not active or a theme that is not the current one; or the HTTP method you are sending is not in the methods list, which returns 404 rather than 405 in some setups. Requesting /wp-json/myplugin/v1 lists everything actually registered in that namespace.

How do I authenticate a request to my custom endpoint?

From JavaScript inside WordPress, the logged-in cookie is used automatically as long as you send the REST nonce in an X-WP-Nonce header — apiFetch does this for you. From outside WordPress, use an application password over HTTPS with basic auth, which is core functionality since 5.6. There is no way to authenticate with a plain cookie alone; without the nonce the request is treated as logged out.

What namespace should I use for my endpoints?

A vendor prefix and a version: myplugin/v1. The vendor part keeps your routes from colliding with another plugin's, and the version is what lets you ship a v2 with a different response shape while old clients keep working. Never register into wp/v2 — that namespace is core's, and anything you add there can be overwritten by a core release.

Where do I paste the code from the REST Route generator?

You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.

Why isn't my new rest route showing up, or why do I get a 404?

Almost always stale rewrite rules. WordPress caches the URL routing table, so anything that adds URLs is invisible until that cache is rebuilt. Go to Settings → Permalinks and press Save Changes — you do not need to alter anything, just saving the page flushes the rules. Never call flush_rewrite_rules() on every page load; it is an expensive write and belongs in an activation hook.

Can I use the generated code in client work or a commercial plugin?

Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.

The other Core generators in the library, plus the WordPress tools most often used alongside this one.