Headless API
Role
ReactWP can be consumed by an external frontend without replacing the integrated WordPress + React mode.
The headless API is a public contract layered over the same runtime that powers the theme shell:
- WordPress still resolves content, menus, settings, SEO, and ACF data
- external frontends receive normalized JSON
- authenticated requests still run inside WordPress, so callbacks can use
wp_get_current_user()
Endpoints
All endpoints use the reactwp/v1 REST namespace.
| Endpoint | Method | Purpose |
|---|---|---|
/wp-json/reactwp/v1/bootstrap | GET | Public first-load payload for external apps |
/wp-json/reactwp/v1/route?view=/about/ | GET | Public route payload. The view parameter is required. |
/wp-json/reactwp/v1/navigation | GET | All normalized menus |
/wp-json/reactwp/v1/navigation?location=primary | GET | One normalized menu location |
/wp-json/reactwp/v1/settings | GET | Public settings only |
/wp-json/reactwp/v1/sitemap | GET | Public route index |
/wp-json/reactwp/v1/preview?postId=42&token=... | GET | Signed preview payload |
/wp-json/reactwp/v1/auth/me | GET | Current authenticated user |
/wp-json/reactwp/v1/auth/login | POST | Cookie login for headless frontends |
/wp-json/reactwp/v1/auth/logout | POST | Cookie logout |
Public Contract
Headless responses include:
apiVersiongeneratedAt- endpoint-specific payload data
The public route shape includes:
idtypetemplatestatuslangtitlepageNamepathsearchqueryurlseomediaGroupsdataheadis404links
Use this page as the source of truth when wiring a Next, Astro, Remix, or Vite frontend.
CORS Allowlist
ReactWP does not use * for headless authentication.
Allowed origins come from:
- the WordPress site URL
- the WordPress home URL
- Site settings > Headless API > Allowed Headless Origins
- the
rwp_headless_allowed_originsfilter
Example:
add_filter('rwp_headless_allowed_origins', function($origins){
$origins[] = 'https://app.example.com';
$origins[] = 'http://localhost:3000';
return $origins;
});
For authenticated browser requests, the frontend origin must be allowlisted and should use HTTPS. Localhost origins are allowed for local development.
Login Flow
External browser frontends can create a WordPress session with:
const response = await fetch('https://cms.example.com/wp-json/reactwp/v1/auth/login', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'editor@example.com',
password: 'password',
remember: true
})
});
const { currentUser } = await response.json();
When login succeeds, ReactWP returns:
- the current user payload
- a
restNonce - WordPress auth cookies set by the CMS domain
Subsequent authenticated requests should include both the cookies and nonce:
await fetch('https://cms.example.com/wp-json/my-app/v1/private-data', {
credentials: 'include',
headers: {
'X-WP-Nonce': currentUser.restNonce
}
});
Inside the WordPress REST callback, normal WordPress user APIs work:
register_rest_route('my-app/v1', '/private-data', [
'methods' => 'GET',
'permission_callback' => function(){
return is_user_logged_in();
},
'callback' => function(){
$user = wp_get_current_user();
return [
'userId' => $user->ID,
'displayName' => $user->display_name,
];
},
]);
The route must also be allowed through ReactWP's REST allowlist if it is used outside the admin REST surface:
add_filter('rwp_allowed_rest_routes', function($routes){
$routes[] = '/my-app/v1/private-data';
return $routes;
});
Logout Flow
Logout requires the REST nonce when a user is logged in:
await fetch('https://cms.example.com/wp-json/reactwp/v1/auth/logout', {
method: 'POST',
credentials: 'include',
headers: {
'X-WP-Nonce': currentUser.restNonce
}
});
Security Notes
ReactWP's headless auth is designed for browser-to-CMS requests.
Important defaults:
- login responses never reveal whether the username or password was wrong
- repeated failed logins are rate-limited
- authenticated origins are allowlisted
- preview access uses signed, expiring tokens
- auth and preview responses send no-store cache headers
- public settings are opt-in through
rwp_headless_public_settings - user capabilities are opt-in through
rwp_headless_user_capabilities
For production cross-origin cookie auth, serve the CMS over HTTPS and make sure your cookie policy supports the deployment topology. Same-site subdomain setups are usually simpler than unrelated domains.
Preview Tokens
Generate preview tokens server-side:
$token = rwp::preview_token($post_id, 600);
Or use the runtime class directly:
$token = ReactWP\Runtime\PreviewToken::create($post_id, 600);
Then send the external frontend to a URL that can request:
/wp-json/reactwp/v1/preview?postId=42&token=...
Tokens are signed with WordPress salts, scoped to one post ID, and expire after the configured TTL.
Public Settings
The settings endpoint exposes nothing by default.
Initial response:
{
"apiVersion": "1.0",
"generatedAt": "...",
"settings": {}
}
Add project-specific public settings with:
add_filter('rwp_headless_public_settings', function($settings){
$settings['featureFlags'] = [
'accountArea' => true,
];
return $settings;
});
Do not expose secrets, private option values, API keys, or unfiltered admin configuration through this filter.
Current User Payload
By default, auth/me returns a minimal current-user payload.
To expose a small capability map:
add_filter('rwp_headless_user_capabilities', function($capabilities, $user){
return [
'readPrivateArea' => user_can($user, 'read'),
'editPosts' => user_can($user, 'edit_posts'),
];
}, 10, 2);
Keep this payload small and intentional. Do not send the full WordPress capabilities array to the browser unless the project truly requires it.