The WordPress REST API is one of the most powerful interfaces in WordPress, and also one of the most frequently misunderstood from a security perspective.
Visit a WordPress site at:
https://example.com/wp-json/
and you may see a large JSON response describing available REST API routes.
That often causes an immediate reaction:
The API is visible.
Therefore the website is vulnerable.
That conclusion is usually wrong.
The WordPress REST API is intentionally discoverable and is used by WordPress itself, plugins, themes, the Block Editor, external applications and custom integrations.
The real security questions are not:
Can someone see /wp-json/?
Can someone discover a route?
They are:
What data can an unauthenticated visitor retrieve?
Which actions require authentication?
Which capabilities are required?
Do custom endpoints enforce permissions correctly?
Are credentials protected?
Does an endpoint expose more data than it needs to?
This guide covers the WordPress REST API security basics administrators and developers should understand, including authentication, authorization, capabilities, nonces, Application Passwords, custom endpoint permissions, public data exposure, validation, HTTPS and sensible REST API hardening.
What is the WordPress REST API?
The WordPress REST API provides an HTTP interface through which applications can interact with WordPress data using JSON.
A typical REST API URL looks like:
https://example.com/wp-json/wp/v2/posts
A request to that URL can return published WordPress posts as structured JSON.
The official WordPress REST API Handbook describes the REST API as an interface for applications to send and receive WordPress data using JSON.
The REST API is part of normal WordPress functionality
The REST API is not an optional developer experiment bolted onto the side of WordPress.
It supports important WordPress functionality, including the Block Editor and many plugin interfaces.
It is also commonly used for:
- headless WordPress sites;
- mobile applications;
- external publishing tools;
- JavaScript interfaces;
- automation;
- custom dashboards;
- content integrations;
- remote management tools.
Therefore, disabling the entire API without understanding dependencies can break legitimate functionality.
Public REST API data is not automatically a data leak
WordPress generally exposes public content through the REST API because that content is already public.
For example:
GET /wp-json/wp/v2/posts
can return published posts.
Those same posts are normally accessible through the public website.
Providing a machine-readable representation of public content is one of the purposes of the API.
Private data is different
Sensitive operations and restricted data should require appropriate authentication and permissions.
For example, an anonymous visitor should not be able to use:
POST /wp-json/wp/v2/posts/123
to edit a post simply because the REST route exists.
The existence of an endpoint and permission to perform an operation are separate concepts.
REST API security has several separate layers
A useful model is:
transport security
↓
authentication
↓
authorization
↓
input validation
↓
data exposure control
↓
application logic
Security problems often arise when developers treat one layer as though it replaces all the others.
For example:
valid nonce
≠
permission to edit anything
and:
authenticated user
≠
administrator
Authentication and authorization are not the same thing
This is one of the most important concepts in REST API security.
Authentication asks
Who are you?
Authorization asks
Are you allowed
to perform this action?
A valid authenticated Subscriber is still a valid authenticated user.
That does not mean the Subscriber should be allowed to:
- delete plugins;
- edit another user’s posts;
- change site settings;
- create administrators.
Authentication establishes identity.
WordPress capabilities should then determine what that identity can do.
See WordPress user roles and capabilities, explained for the underlying permission model.
WordPress REST API cookie authentication
When REST requests originate from within WordPress for a user who is already logged in, WordPress can use normal authentication cookies.
This is common for administrative JavaScript applications.
The browser already has the authenticated WordPress session, so requests can operate as the current user.
However, REST requests also need protection against cross-site request forgery.
This is where REST API nonces become important.
WordPress REST API nonces
For cookie-authenticated REST requests, WordPress commonly uses a nonce created for:
wp_rest
The nonce can be passed using:
X-WP-Nonce
or through the supported request parameter.
Example
fetch(
'/wp-json/example/v1/settings',
{
method: 'POST',
headers: {
'X-WP-Nonce': myData.nonce,
'Content-Type': 'application/json'
},
body: JSON.stringify(
{
enabled: true
}
)
}
);
The official REST API authentication documentation explains the use of the wp_rest nonce for cookie-authenticated requests.
A nonce is not authorization
This deserves its own section because incorrect nonce usage is a recurring WordPress security mistake.
A valid nonce helps protect against certain request-forgery scenarios.
It does not answer:
Should this user
be allowed to perform
this operation?
The official WordPress nonce security documentation explicitly warns that nonces should not be relied upon for authentication, authorization or access control.
Bad security model
nonce valid
↓
perform administrator action
Correct model
request authenticated
+
nonce valid where appropriate
+
user has required capability
↓
perform action
Use current_user_can() for authorization
WordPress provides:
current_user_can()
to test whether the current user has a specific capability.
For example:
current_user_can(
'manage_options'
)
checks whether the current user has the capability required for many administrative configuration operations.
For content operations, more specific capabilities should normally be used.
Custom REST endpoints need a permission_callback
When developers register custom REST API endpoints, one of the most important security controls is:
permission_callback
The permission callback runs after authentication and determines whether the current requester may use the endpoint.
The official WordPress custom REST endpoint documentation recommends using this callback to perform authorization checks.
Example of an administrator-only endpoint
add_action(
'rest_api_init',
function () {
register_rest_route(
'example/v1',
'/settings',
array(
'methods' => 'POST',
'callback' => 'example_update_settings',
'permission_callback' =>
function () {
return current_user_can(
'manage_options'
);
},
)
);
}
);
Here the endpoint does not grant access merely because somebody knows its URL.
The user must have:
manage_options
Do not check roles when a capability expresses the permission better
Developers sometimes write:
if (
in_array(
'administrator',
$user->roles,
true
)
) {
// Allow.
}
Capability checks are generally preferable:
current_user_can(
'manage_options'
)
Why?
WordPress authorization is built around capabilities.
Roles are collections of capabilities and can be customized.
Capability checks are therefore more flexible for:
- custom roles;
- multisite environments;
- delegated permissions;
- plugins that modify role capabilities.
Public custom endpoints still need an intentional permission callback
Sometimes an endpoint is genuinely intended to be public.
For example:
GET /example/v1/public-status
might intentionally expose non-sensitive information.
In that case a developer can explicitly use:
'permission_callback' => '__return_true'
But that should mean:
This data is intentionally public.
It should not mean:
I did not want to think
about permissions.
The HTTP method does not provide authorization
REST APIs commonly use HTTP methods such as:
GET
POST
PUT
PATCH
DELETE
A request being:
POST
does not make it secure.
A request being:
GET
does not mean the returned information is safe to disclose.
Every route must have a permission model appropriate to the information or operation it exposes.
Application Passwords for external REST API access
WordPress includes Application Passwords for programmatic authentication.
They were introduced in WordPress 5.6 and are designed for applications, scripts and integrations rather than interactive browser login.
An Application Password belongs to a particular WordPress user.
That means requests authenticated with it operate with the capabilities of that user.
The official Application Passwords documentation explains their intended use and management.
Do not give integrations your main WordPress password
If an external integration needs REST API access, sharing the user’s normal interactive account password creates unnecessary risk.
An Application Password is preferable because it can be:
- created for a specific application;
- revoked independently;
- replaced without changing the main login password.
For example:
WordPress account password
→ browser login
Application Password A
→ publishing integration
Application Password B
→ reporting script
If the reporting script is retired, Password B can be revoked without disrupting Password A or the user’s normal login.
Application Passwords do not bypass capabilities
An Application Password authenticates as a WordPress user.
It does not create unrestricted API access.
If a user cannot:
edit_others_posts
through the normal WordPress permission model, possessing that user’s Application Password does not magically make that user an administrator.
This is another reason to create integrations under appropriately restricted accounts.
Follow the principle of least privilege
Suppose an integration only needs to create and edit its own posts.
Using the main administrator account gives it permissions it does not require.
A safer architecture is:
dedicated integration account
+
minimum required role/capabilities
+
dedicated Application Password
If that credential is exposed, the potential impact is reduced.
For broader permission auditing, see How to audit user roles on a WordPress site.
Application Passwords require transport security
Credentials used for API authentication must be protected in transit.
Production REST API traffic carrying credentials should use:
HTTPS
rather than plain HTTP.
HTTPS protects the request from ordinary network interception between client and server.
Do not treat encrypted transport as optional simply because the password belongs to an application instead of a human.
Never expose Application Passwords in frontend JavaScript
Do not write:
const apiPassword =
'xxxx xxxx xxxx xxxx';
inside publicly delivered JavaScript.
Anything included in browser-delivered source should be considered visible to the visitor.
Long-lived credentials belong on trusted server-side systems or other properly protected clients.
Rotate and revoke unused Application Passwords
Application credentials should be reviewed periodically.
Remove credentials associated with:
- retired applications;
- former vendors;
- old automation;
- lost devices;
- unused integrations.
A credential that nobody remembers using is not improved by sitting around indefinitely.
WordPress REST API user exposure
The endpoint:
/wp-json/wp/v2/users
deserves special attention because public user information can contribute to username enumeration.
The REST user schema contains different fields depending on the request context.
For example, fields such as:
username
email
roles
capabilities
are associated with restricted editing contexts rather than the ordinary anonymous public representation.
Public representations can still expose information such as:
- display name;
- user ID;
- author link;
- author slug;
- avatar URLs;
- public description.
The official REST API Users endpoint reference documents the available fields and contexts.
A public author slug may expose a login-related identifier
Depending on how a site is configured, the public user slug may correspond closely to an identifier associated with the account.
This can make username enumeration easier.
Username knowledge alone is not account compromise, but reducing unnecessary identifier exposure can still form part of a layered security strategy.
See How attackers collect WordPress usernames and email addresses for the broader enumeration problem.
TheOneWP Hide Author Slug can reduce username exposure
TheOneWP Hide Author Slug replaces the real username-derived author slug with a random token across the relevant author URL workflow.
Its REST API handling also replaces the exposed user slug and author link representation rather than modifying the actual login name stored for the account.
This addresses one specific exposure path.
It does not replace:
- strong passwords;
- two-factor authentication;
- rate limiting;
- proper REST permissions.
Username hiding is not authentication security by itself
An attacker learning:
editor-john
does not mean the attacker can authenticate as John.
Likewise, hiding that identifier does not make a weak password strong.
A proper login-security strategy should consider:
- unique passwords;
- two-factor authentication;
- login attempt limiting;
- least privilege;
- account review.
See A WordPress login hardening checklist and How to limit login attempts in WordPress.
Do not expose sensitive custom fields accidentally
Custom post metadata deserves particular attention.
A developer might register information such as:
internal_customer_id
vendor_reference
private_notes
integration_token
and then expose the metadata through REST without considering whether anonymous clients should receive it.
REST visibility must be treated as an explicit design decision.
show_in_rest changes API exposure
WordPress APIs commonly use:
show_in_rest
to determine whether certain registered objects or fields participate in REST API responses.
This is useful for:
- custom post types;
- taxonomies;
- registered metadata;
- other modern WordPress APIs.
Before enabling REST exposure, ask:
Does a client actually need this data?
Expose the minimum information required by the application.
Validate REST API input
Authorization determines whether somebody may call an endpoint.
It does not guarantee that the submitted data is valid.
Suppose an endpoint expects:
status:
active | disabled
The server should not blindly accept:
status:
anything-the-client-sends
Define expected types, formats and allowed values.
Use REST argument validation
Custom routes can define argument requirements.
For example:
'args' => array(
'user_id' => array(
'required' => true,
'type' => 'integer',
),
)
More complex requirements can use:
validate_callback;sanitize_callback;- REST schemas.
Sanitization and validation solve different problems
Validation asks
Is this value acceptable?
Sanitization asks
How should this accepted
input be normalized or cleaned?
For example:
email address
should be validated as an email and sanitized using appropriate WordPress functionality.
Do not blindly sanitize arbitrary attacker-controlled data and assume that whatever remains is safe for every context.
Escape data when outputting it
REST input may eventually appear in:
- HTML;
- attributes;
- URLs;
- JavaScript;
- SQL-driven operations.
Output encoding should match the final context.
For example:
esc_html()
esc_attr()
esc_url()
solve different output problems.
Security should not stop at the moment the REST request reaches PHP.
Do not trust IDs simply because they are integers
Consider:
POST /example/v1/post/123/delete
The fact that:
123
is a valid integer does not mean the current user may delete post 123.
A secure permission check should evaluate the specific object.
For example:
current_user_can(
'delete_post',
$post_id
)
Object-level authorization is particularly important when different users own different content.
Avoid insecure direct object references
A common API authorization flaw looks like:
authenticated user
+
valid object ID
↓
object returned
without checking whether that authenticated user may access that particular object.
For example:
/example/v1/orders/5001
must not return another customer’s private order merely because the caller changed:
5000
to:
5001
Authentication does not remove the need for object-level authorization.
Return only the data the client needs
Suppose an endpoint needs to return:
customer name
order status
order date
Do not automatically return the complete internal database row containing:
- internal notes;
- security flags;
- payment metadata;
- integration credentials;
- private operational information.
Explicit response construction is safer than indiscriminate serialization of internal objects.
Be careful with REST API context
WordPress REST resources can expose different fields according to context.
Common contexts include:
view
embed
edit
The:
edit
context can expose information intended for authorized editing interfaces.
Do not make edit-context responses publicly available simply to make frontend development easier.
Do not put secrets into REST responses
Information that should never be casually returned includes:
- passwords;
- Application Password values;
- API secrets;
- private signing keys;
- database credentials;
- authentication tokens;
- payment secrets.
If a secret must be used by server-side code, keep it server-side.
Route discovery is not an authorization mechanism
The REST API root can expose route information.
This can reveal namespaces such as:
wp/v2
plugin-name/v1
custom-api/v1
Security must assume an attacker can learn endpoint URLs.
Trying to keep an endpoint secret is not a substitute for protecting it.
The appropriate principle is:
Known URL
+
unauthorized requester
=
request denied
Removing REST API discovery links is different from disabling the REST API
WordPress can advertise its REST API through discovery mechanisms in frontend responses.
TheOneWP Disable REST API Links removes REST API discovery signals such as the REST API link in the page head and corresponding discovery header.
It does not disable REST endpoints.
This distinction is important:
remove discovery links
≠
restrict REST API access
Hiding discovery is not a security boundary
An attacker can still know that standard WordPress installations commonly expose:
/wp-json/
Removing a discovery tag can reduce unnecessary advertising of the endpoint location, but it does not replace authorization.
Security through obscurity may slightly reduce casual discovery.
It must not be the primary defense.
Should you disable the WordPress REST API?
Usually, do not disable it reflexively.
The REST API may be required by:
- the Block Editor;
- WooCommerce;
- forms;
- SEO plugins;
- security tools;
- mobile applications;
- headless frontends;
- custom integrations.
Blanket restrictions should be applied only after identifying legitimate dependencies.
When restricting REST API access can make sense
Some sites have no requirement for anonymous REST access.
Others may want to allow:
authenticated users only
or:
administrators only
for specific environments.
A private internal WordPress installation may have very different requirements from a public headless publication.
Security configuration should reflect the site’s architecture rather than follow a universal toggle.
TheOneWP Disable REST API
TheOneWP Disable REST API provides configurable REST API access restriction rather than merely removing discovery links.
Its available access modes can restrict REST API requests for:
- unauthenticated visitors;
- non-administrator users;
- everyone.
This is fundamentally different from Disable REST API Links.
Disable REST API Links
removes discovery signals
Disable REST API
changes who may access
REST API functionality
Test integrations before restricting REST access
Before applying broad restrictions, test:
- post editing;
- Block Editor operations;
- media uploads;
- WooCommerce interfaces;
- forms;
- mobile apps;
- external automation;
- webhooks and integrations;
- custom frontend applications.
A security control that silently breaks publishing five minutes later is technically a control, but not a particularly impressive deployment plan.
Rate limiting can complement REST API security
Authentication endpoints and expensive API operations can benefit from rate limiting.
Rate limiting may be implemented through:
- a web application firewall;
- reverse proxy;
- hosting platform;
- security plugin;
- custom application logic.
Rate limiting does not replace authorization.
It limits request volume.
A request that should never be allowed must still be rejected even if the requester makes it only once.
Monitor unusual REST API activity
Depending on the site, logs can help identify:
- repeated authentication failures;
- unexpected calls to sensitive custom namespaces;
- high request volumes;
- requests from unusual sources;
- repeated enumeration behavior;
- unexpected write operations.
Useful sources can include:
- web server access logs;
- WAF logs;
- application logs;
- security-plugin logs.
Keep WordPress, plugins and themes updated
REST API security does not exist separately from the code implementing the endpoints.
A plugin can register its own REST namespace.
If that plugin contains a permission flaw, WordPress Core being fully updated does not fix the plugin’s application logic.
Keep:
- WordPress Core;
- plugins;
- themes;
- custom integrations
maintained and reviewed.
Audit custom REST namespaces
Visit:
/wp-json/
on a development or staging environment and identify the namespaces present.
For example:
wp/v2
wc/v3
my-plugin/v1
company/v1
Ask:
- Which plugin owns each namespace?
- Which routes are intentionally public?
- Which routes can modify data?
- Which capabilities protect those routes?
- Are any obsolete integrations still registered?
Test REST endpoints as an anonymous visitor
Do not test security only while logged in as Administrator.
An administrator session can hide permission problems because it has nearly every capability.
Test important endpoints as:
- logged out;
- Subscriber;
- Editor;
- the actual integration user;
- Administrator where required.
This reveals whether access boundaries behave as intended.
Test both reads and writes
An endpoint can be secure for:
GET
but insecure for:
POST
or:
DELETE
Review every supported HTTP method independently.
The permission required to:
view a resource
may be very different from the permission required to:
delete it
Do not return detailed internal errors unnecessarily
API errors should be useful enough for legitimate clients to understand what went wrong.
They should not casually expose:
- filesystem paths;
- database queries;
- stack traces;
- secret values;
- internal infrastructure details.
Production debugging output should be controlled appropriately.
Use appropriate HTTP status codes
REST clients depend on HTTP status codes.
Common examples include:
200
successful request
201
resource created
400
invalid request
401
authentication required or failed
403
authenticated but forbidden
404
resource not found
429
too many requests
500
server error
Exact behavior depends on the endpoint and authentication implementation, but meaningful status codes make security behavior easier to test and integrate.
Do not confuse 401 and 403 conceptually
A useful mental model is:
401
You need valid authentication.
403
We know who you are,
but you cannot do this.
Real implementations can contain nuances, but distinguishing authentication failure from authorization failure makes debugging far easier.
Use dedicated accounts for integrations
Instead of connecting every external application through:
site administrator
create an account whose permissions match the actual workflow.
This also makes auditing easier.
You can identify:
Content API Bot
instead of discovering that fifty REST requests were performed using the CEO’s administrator account.
Disable accounts and credentials that are no longer required
Old integrations are a common source of forgotten access.
Periodically review:
- REST integration users;
- Application Passwords;
- custom API credentials;
- service accounts;
- external vendors.
This principle overlaps with general access governance.
See Auditing dormant WordPress user accounts.
REST API security checklist
- Use HTTPS for production REST API authentication.
- Understand which REST endpoints are intentionally public.
- Do not treat route visibility as a vulnerability by itself.
- Separate authentication from authorization.
- Use WordPress capabilities for permission checks.
- Use object-specific capability checks where appropriate.
- Register a deliberate
permission_callbackfor custom endpoints. - Use
__return_trueonly for deliberately public endpoints. - Do not use nonces as authorization.
- Use the
wp_restnonce for appropriate cookie-authenticated requests. - Use Application Passwords rather than sharing primary account passwords with integrations.
- Give integrations the minimum capabilities required.
- Use dedicated integration accounts where practical.
- Revoke unused Application Passwords.
- Never embed private API credentials in public JavaScript.
- Validate REST request arguments.
- Sanitize input appropriately.
- Escape data for its eventual output context.
- Return only fields the client actually needs.
- Do not expose secrets through REST schemas or custom responses.
- Review metadata registered with
show_in_rest. - Review user information exposed through public REST responses.
- Audit custom REST namespaces registered by plugins and themes.
- Test endpoints while logged out.
- Test endpoints with low-privilege accounts.
- Test every supported HTTP method.
- Review object-level authorization.
- Apply rate limiting where appropriate.
- Monitor unusual REST activity.
- Keep Core, plugins and themes updated.
- Test integrations before globally restricting REST access.
- Do not confuse removing REST discovery links with disabling REST access.
Related WordPress security guides
Continue with these related guides and tools:
- How attackers collect WordPress usernames and email addresses
- A WordPress login hardening checklist
- WordPress user roles and capabilities, explained
- How to audit user roles on a WordPress site
- Auditing dormant WordPress user accounts
- How to limit login attempts in WordPress
- Disable REST API
- Disable REST API Links
- Hide Author Slug
- Access Manager
- Two-Factor Authentication
Final thoughts
WordPress REST API security is not about making /wp-json/ disappear.
A secure REST architecture assumes that routes can be discovered.
The important controls happen after discovery:
Who is making this request?
How were they authenticated?
What capabilities do they have?
Can they access this specific object?
Is the submitted data valid?
What information should the response expose?
The basic security model is:
HTTPS
+
appropriate authentication
+
capability-based authorization
+
secure permission callbacks
+
input validation
+
minimal data exposure
=
strong REST API foundations
Nonce validation adds important CSRF protection to cookie-authenticated WordPress requests, but it does not replace capability checks.
Application Passwords provide revocable credentials for external integrations, but those integrations should still run under accounts with the minimum required permissions.
Custom endpoints should always have an explicit access model, and sensitive data should never become public merely because returning an entire database object was easier than designing a proper response schema.
If a site genuinely does not need public REST API access, restrictions can be useful. TheOneWP Disable REST API can control REST access according to authentication level, while Disable REST API Links addresses discovery signals without pretending that hiding a link is authorization.
That distinction is the foundation of sensible WordPress REST API security: protect capabilities and data, not merely URLs.

