Skip to content
ThemesIonic — home
Troubleshooting

Fixing rest_no_route in the WordPress REST API

The REST server ran and found no registered route that matches both the path and the HTTP method. Either the route was never registered for this request, the URL does not match its pattern, or the method is wrong — and the message is the same for all three.

5 min read advanced

The REST server is running, but no registered route matches both this path and this HTTP method — check the route is registered for this request, then the pattern, then the method. The response looks like this:

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

It comes from WP_REST_Server::match_request_to_handler(), which returns this error whenever its search for a handler comes back empty. Note the wording: URL and request method. A route that exists for GET produces exactly the same error when called with POST.

Step 1: is the route registered at all?

The REST index lists every route registered for the current request. Ask for your namespace:

curl -s https://example.com/wp-json/myplugin/v1 | python3 -m json.tool | head -40

If the namespace returns its own rest_no_route, nothing in it was registered, and the problem is in the registration code, not the URL. The usual reasons:

  1. The plugin is inactive on this site, or on this subsite in a multisite network.
  2. The registration runs too late or never. Routes must be added on the rest_api_init action. Calling register_rest_route() before that action has fired triggers a _doing_it_wrong() notice that names the route and namespace, visible with debug mode on.
  3. The file is loaded only in the dashboard. Code wrapped in if ( is_admin() ) never runs for /wp-json/ requests, because REST requests are not admin requests. A route that only exists while someone is in wp-admin is the typical result.
  4. A condition inside the callback skips registration — a missing option, a licence check, a capability check that runs before the user is authenticated.

The correct shape is short:

add_action( 'rest_api_init', function () {
	register_rest_route( 'myplugin/v1', '/items/(?P<id>\d+)', array(
		'methods'             => WP_REST_Server::READABLE,
		'callback'            => 'myplugin_get_item',
		'permission_callback' => '__return_true',
		'args'                => array(
			'id' => array(
				'validate_callback' => function ( $value ) {
					return is_numeric( $value );
				},
			),
		),
	) );
} );

permission_callback does not affect matching, but leaving it out produces another _doing_it_wrong() notice since WordPress 5.5; set it to a real check for anything that is not public.

Step 2: does the URL match the pattern?

The server compares the path with each route as a regular expression anchored at both ends, so the whole path must match. Common mismatches:

Requested Registered Problem
/myplugin/v1/items/abc /items/(?P<id>\d+) \d+ accepts digits only
/myplugin/v2/items/5 myplugin/v1 Namespace version differs
/myplugin/v1/item/5 /items/... Typo in the base
/wp-json/myplugin/v1/items/5 on a subdirectory install — Missing /blog or similar prefix

Matching is case-insensitive, and WordPress removes the trailing slash before comparing, so neither of those is the cause. When building URLs in PHP, use rest_url( 'myplugin/v1/items/5' ) instead of concatenating strings: it adds the correct prefix and subdirectory, and switches to the query-string form when needed.

Step 3: is the method allowed?

For each route whose pattern matches, the server checks whether the handler accepts the request's method, and skips it if not. A HEAD request falls back to GET when no HEAD handler is registered; nothing else falls back. So:

// Registered as READABLE (GET) — this POST gets rest_no_route.
fetch( '/wp-json/myplugin/v1/items/5', { method: 'POST' } );

The method is checked per handler, so one route can have a GET handler and a POST handler with different callbacks and permissions; a request with any other method still gets rest_no_route. Register the method you actually send, or several:

'methods' => array( 'GET', 'POST' ),

Some hosts and proxies block PUT, PATCH and DELETE. The workaround is the X-HTTP-Method-Override header or the _method query parameter on a POST request, which the REST server translates into the intended method.

With Settings › Permalinks on "Plain", the /wp-json/ rewrite rule does not exist, and requests to it reach the web server as an ordinary 404 page — HTML, not JSON. The REST API itself still works through the query-string form:

curl -s "https://example.com/?rest_route=/myplugin/v1/items/5"

rest_url() returns that form automatically on plain permalinks, which is another reason to use it. If pretty permalinks are on and /wp-json/ still returns an HTML 404, the rewrite rules are stale or the server is not passing requests to WordPress; the fixes are in permalinks not working.

When the editor shows it

The block editor talks to the REST API constantly. A rest_no_route while saving usually points at a plugin's custom endpoint or a custom post type registered with show_in_rest but loaded conditionally — the same "registered only sometimes" problem from step 1. The editor-side symptoms are covered in publishing failed and not a valid JSON response; the second appears when something returns HTML where JSON was expected. An overview of how the API is structured is in the WordPress API.

Common mistakes

  • Flushing permalinks to fix a route that was never registered. Flushing affects the /wp-json/ rewrite, not individual routes.
  • Registering routes on init. It may appear to work on some requests and not others; use rest_api_init.
  • Hard-coding /wp-json/ in JavaScript instead of passing rest_url() through wp_localize_script() or the wpApiSettings object.
  • Treating a 401 or 403 from a security plugin as the same problem. Read the code field first.

Verify

  • curl -s https://example.com/wp-json/myplugin/v1 lists your route with the methods you expect.
  • The exact URL and method your code sends returns your callback's response, not rest_no_route.
  • Test logged out, and on a subsite if the plugin runs on multisite.
  • wp-content/debug.log shows no register_rest_route notices.

Frequently asked

No. The JSON response comes from the REST server itself, so the API is running. A plugin that blocks the API returns its own error code, usually with status 401 or 403. rest_no_route only means nothing registered matches this path and method.
The browser address bar sends GET. If your fetch sends POST and the route only allows GET, or the other way round, the server skips the route and reports rest_no_route. Register the method you send, or use WP_REST_Server::CREATABLE or ALLMETHODS.
No. WordPress strips the trailing slash from the route before matching, so /wp-json/myplugin/v1/items and /wp-json/myplugin/v1/items/ resolve the same way. Route matching is also case-insensitive.

Related guides