Skip to content

WordPress REST API publicly lists the plugins it runs

Warning Whole domain Security

General instructions. Open this article from your PageCheck report to see it filled in with the plugins your site's index names and which of them your visitors' browsers use.

What it is

WordPress's REST API lets scripts read and change the site through addresses under /wp-json/: the block editor, apps, and many plugins use it. Its index lists every "namespace" the site registers — a group of addresses — and WordPress itself owns only a handful of them. Every other namespace belongs to a plugin.

What is wrong

The index answers anyone who asks for it, and its namespaces name the site's plugins. With that list an attacker does not guess: each plugin's version is read from its own files next, and matched against the public lists of known security holes. The page itself shows only the plugins that load files into it; the index also names the ones that do not — SEO, forms, backup, security and admin plugins.

How to fix

The API stays; only its map and the parts visitors never call are hidden from anyone who is not logged in as staff. Hidden addresses should answer "not found" (404), not "unauthorized" (401): a 401 still confirms the plugin is there.

1. Find out which namespaces your pages call. Open the site in a private browser window, open the browser's developer tools (F12) → Network, filter by wp-json, and use the site as a visitor: send a form, add something to the cart, open the checkout, search. Every request listed names a namespace to keep.

Common ones:

  • Contact Form 7 submits its forms through the REST API.
  • WooCommerce's block-based cart, checkout and mini cart read the Store API (wc/store).
  • View counters such as WP Statistics and WordPress Popular Posts record visits through it.
  • Community, donation and consent plugins (BuddyPress, GiveWP, Complianz) use it on the pages.

2. Hide the rest from visitors. Save this as wp-content/mu-plugins/hide-rest-map.php (the folder may need creating; files there are always active). Put the namespaces from step 1 in the list — each one is kept with everything under it.

wp-content/mu-plugins/hide-rest-map.php

<?php
/* Hide the REST API's map from visitors who are not logged in. */
add_filter( 'rest_endpoints', function ( $endpoints ) {
	if ( current_user_can( 'edit_posts' ) ) {
		return $endpoints; // staff keep everything
	}
	// Namespaces your pages call. Everything else answers 404 to visitors.
	$keep = array( 'wc/store', 'contact-form-7/v1' );
	foreach ( array_keys( $endpoints ) as $route ) {
		$kept = false;
		foreach ( $keep as $namespace ) {
			if ( 0 === strpos( $route, '/' . $namespace . '/' ) ) {
				$kept = true;
				break;
			}
		}
		if ( ! $kept ) {
			unset( $endpoints[ $route ] );
		}
	}
	return $endpoints;
}, 5 );

// No pointer to the API in the pages' head and headers.
remove_action( 'wp_head', 'rest_output_link_wp_head' );
remove_action( 'template_redirect', 'rest_output_link_header', 11 );

Anyone who can edit content keeps the whole API, so the block editor, WooCommerce's and other plugins' admin screens and Site Health work as before. Security plugins offer the same as a setting ("Disable REST API for visitors" or similar); make sure it takes a list of exceptions and answers 404, not 401.

Not recommended: the common snippet that refuses the whole API to visitors through rest_authentication_errors. It breaks forms and the block cart, and still answers 401 for every address, which confirms each plugin exists.

3. Check it. Open https://example.com/wp-json/ in a private window: it should answer "not found" (404, rest_no_route). Then send a form and use the cart or whatever else your pages do, and run this check again.

Hide my WordPress site's REST API map from visitors who are not logged in, without breaking the features that use the API. https://example.com/wp-json/ currently answers anyone and lists every namespace, i.e. every plugin.

Do this:
1. Find which namespaces the public pages call (forms, cart, checkout, view counters): grep the theme and plugins for wp-json / rest_url / apiFetch, and check the browser's network tab as a visitor.
2. Add a must-use plugin (wp-content/mu-plugins/hide-rest-map.php) with a rest_endpoints filter: when the user cannot edit_posts, unset every route except those under the kept namespaces (match '/<namespace>/' as a prefix of the route), so the index '/', every namespace index and the other routes answer 404 rest_no_route, never 401. Also remove_action wp_head rest_output_link_wp_head and template_redirect rest_output_link_header (priority 11).
3. Do not use rest_authentication_errors to refuse the whole API: it breaks visitor-facing features and answers 401, which confirms the plugins.
4. Verify: /wp-json/ and /wp-json/wp/v2 answer 404 for a visitor and still work for an editor; the kept features work in a private window; no <link rel="https://api.w.org/"> and no Link header on the pages.
Tell me exactly which file you added and which namespaces you kept.

Check your site to see whether it has this problem. Check a website