Hooks and Filters
Role
ReactWP exposes documented project-level filters that projects are expected to use instead of patching runtime internals directly.
These filters are some of the most important extension points in the starter.
Main Public Filters
rwp_systemrwp_bootstraprwp_route_payloadrwp_wp_headrwp_critical_fontsrwp_critical_mediasrwp_no_critical_mediasrwp_allowed_rest_routesrwp_authenticated_cross_origin_rest_routesrwp_headless_allowed_originsrwp_headless_public_rate_limitrwp_headless_public_rate_windowrwp_headless_client_iprwp_headless_require_json_authrwp_headless_allow_preview_query_tokenrwp_headless_public_settingsrwp_headless_user_capabilitiesrwp_headless_current_user_payloadrwp_headless_sitemap_post_typesrwp_headless_sitemap_limitrwp_headless_sitemap_itemsrwp_headless_allow_insecure_authrwp_preview_token_authorizedrwp_preview_token_max_ttlrwp_public_authorrwp_options_page_capabilityrwp_svg_upload_capabilityrwp_svg_max_bytesrwp_permissions_policyrwp_content_security_policyrwp_hsts_headerrwp_allow_xmlrpcrwp_render_templatesrwp_render_configrwp_render_moderwp_prerender_skip_loaderrwp_initial_render_enabledrwp_static_render_manifest_pathsrwp_static_render_max_html_bytesrwp_static_regeneration_batch_sizerwp_ssr_endpointrwp_ssr_timeoutrwp_ssr_circuit_secondsrwp_ssr_max_html_bytesrwp_ssr_allow_remote_endpointrwp_ssr_allow_insecure_loopbackrwp_ssr_payloadrwp_ssr_cache_identityrwp_ssr_cache_query_keysrwp_ssr_cache_max_query_bytesrwp_current_user_payload
ReactWP also fires:
rwp_client_cache_bustedrwp_render_cache_invalidatedrwp_ssr_error
rwp_system
Use this filter to extend or alter the system payload.
Signature:
add_filter('rwp_system', function($system){
// ...
return $system;
});
Use it for:
- extra URLs
- environment flags
- theme-level runtime settings
rwp_bootstrap
Use this filter to extend the full bootstrap payload before it is injected into the page.
Signature:
add_filter('rwp_bootstrap', function($payload, $route){
// ...
return $payload;
}, 10, 2);
Use it when you need to add project-wide frontend data.
rwp_route_payload
Use this filter to modify the normalized route payload before it is returned.
Signature:
add_filter('rwp_route_payload', function($payload, $object){
// ...
return $payload;
}, 10, 2);
Use it for:
- adding route-level data
- changing route-level flags
- augmenting
data,seo, or other payload fields
$object can be a:
WP_PostWP_TermWP_Usernullfor 404 payloads
Example: route a query-string variant to a dedicated template:
add_filter('rwp_route_payload', function($payload, $object){
$query = isset($payload['query']) && is_array($payload['query'])
? $payload['query']
: [];
if(!array_key_exists('s', $query)){
return $payload;
}
$search_term = trim((string)$query['s']);
$payload['type'] = 'search';
$payload['template'] = 'Search';
$payload['pageName'] = $search_term !== ''
? 'Search: ' . $search_term
: 'Search';
$payload['data']['searchTerm'] = $search_term;
$payload['data']['isSearchRoute'] = true;
return $payload;
}, 10, 2);
This works because ReactWP treats pathname + search as route identity.
The array_key_exists('s', $query) check is important if you want ?s= to use the same template as ?s=test.
rwp_wp_head
Use this filter to add or change head tags that must work both:
- on direct page load
- after React navigation
Signature:
add_filter('rwp_wp_head', function($wp_heads, $context = []){
// ...
return $wp_heads;
}, 10, 2);
See Head and SEO for the full behavior and the $context structure.
rwp_critical_fonts
Use this filter to define the critical font groups exposed to the loader runtime.
Signature:
add_filter('rwp_critical_fonts', function($fonts){
// ...
return $fonts;
});
Typical shape:
add_filter('rwp_critical_fonts', function($fonts){
$fonts['all'] = [
'400 1rem "Suisse Intl"',
'600 1rem "Suisse Intl"',
];
$fonts['home'] = [
'700 1rem "Suisse Intl"',
];
return $fonts;
});
rwp_critical_medias
Use this filter to register critical media groups for the loader runtime.
Signature:
add_filter('rwp_critical_medias', function($medias){
// ...
return $medias;
});
Each group entry can include:
type:image,video, oraudiosrc: the main source URLtarget: a selector, a DOM node, or an array of selectors/nodessources: optional alternative sources with their own attributes such asmedia,type, orsizes- any DOM props that make sense for the rendered element, such as
alt,className,poster,muted,loop,controls,autoplay, orplaysInline
Example:
add_filter('rwp_critical_medias', function($medias){
$medias['home'] = [
[
'type' => 'image',
'src' => '/wp-content/uploads/2026/04/hero-desktop.jpg',
'target' => [
'#hero .media-slot',
'#hero .media-slot--fallback'
],
'alt' => 'Hero image',
'className' => 'hero-image',
'sources' => [
[
'src' => '/wp-content/uploads/2026/04/hero-mobile.jpg',
'media' => '(max-width: 767px)'
],
[
'src' => '/wp-content/uploads/2026/04/hero-desktop.jpg',
'media' => '(min-width: 768px)'
]
]
]
];
return $medias;
});
During the critical display phase, ReactWP downloads the entry, resolves the target, renders the right media element, and only then lets the route finish its critical reveal.
rwp_no_critical_medias
Use this filter to register deferred media groups.
Signature:
add_filter('rwp_no_critical_medias', function($medias){
// ...
return $medias;
});
For both media filters, ReactWP expects grouped arrays keyed by media group name.
rwp_allowed_rest_routes
ReactWP restricts REST access by default and lets specific routes through.
By default:
- authenticated admins keep normal REST API access when the request includes valid WordPress REST authentication, such as the REST nonce
- non-admin users, guests, and unauthenticated external requests are blocked
- any route listed here stays accessible
Use this filter to allow additional public REST routes.
Signature:
add_filter('rwp_allowed_rest_routes', function($routes){
$routes[] = '/my-plugin/v1/public-endpoint';
return $routes;
});
Use it only when a route must remain accessible outside the admin-only REST surface.
rwp_authenticated_cross_origin_rest_routes
Authenticated cross-origin administrators do not automatically receive access to the complete WordPress REST API. This filter allows exact additional REST routes after the request origin has passed the ReactWP origin policy.
add_filter('rwp_authenticated_cross_origin_rest_routes', function($routes){
$routes[] = '/my-plugin/v1/account';
return $routes;
});
The default is an empty array. This is separate from rwp_allowed_rest_routes, which makes a route public. Keep both lists exact and as small as possible.
rwp_headless_allowed_origins
Use this filter to allow external frontend origins to make credentialed headless auth requests.
Signature:
add_filter('rwp_headless_allowed_origins', function($origins){
$origins[] = 'https://app.example.com';
return $origins;
});
ReactWP ignores wildcard origins for this allowlist. Use exact origins including scheme and port when needed.
rwp_headless_public_settings
Use this filter to expose project-specific public settings through:
/wp-json/reactwp/v1/settings
The endpoint returns an empty settings object until this filter adds values.
Signature:
add_filter('rwp_headless_public_settings', function($settings){
$settings['featureFlags'] = [
'accountArea' => true,
];
return $settings;
});
Only expose values that are safe for every visitor to see.
rwp_headless_user_capabilities
Use this filter to expose a small, intentional capability map for authenticated frontend users.
Signature:
add_filter('rwp_headless_user_capabilities', function($capabilities, $user){
return [
'editPosts' => user_can($user, 'edit_posts'),
];
}, 10, 2);
Do not expose the full WordPress capabilities array by default.
rwp_headless_current_user_payload
Use this filter when you need to customize the authenticated user payload returned by:
/wp-json/reactwp/v1/auth/me/wp-json/reactwp/v1/auth/login
Signature:
add_filter('rwp_headless_current_user_payload', function($payload, $user){
$payload['avatarUrl'] = get_avatar_url($user->ID);
return $payload;
}, 10, 2);
Sitemap Filters
Use rwp_headless_sitemap_post_types to control the public post types queried by the headless sitemap:
add_filter('rwp_headless_sitemap_post_types', function($post_types){
return ['page', 'project'];
});
Use rwp_headless_sitemap_limit to change the item limit. ReactWP clamps the result between 1 and 1000:
add_filter('rwp_headless_sitemap_limit', function(){
return 750;
});
Use rwp_headless_sitemap_items for final project-level additions or removals after the default items are built.
rwp_headless_allow_insecure_auth
This filter can permit authenticated headless requests from an origin that does not pass the normal secure-origin requirement.
add_filter('rwp_headless_allow_insecure_auth', function($allowed, $origin){
return $allowed;
}, 10, 2);
Leave it false in production. Localhost is already handled as a development case; real credential flows should use HTTPS.
Headless Request Boundaries
These filters expose the limits used by the public and authenticated headless endpoints:
| Filter | Default | Purpose |
|---|---|---|
rwp_headless_public_rate_limit | 240 | maximum public requests per address in one window; 0 disables this application limit |
rwp_headless_public_rate_window | 60 seconds | public rate-limit window, clamped between 10 and 3600 seconds |
rwp_headless_client_ip | REMOTE_ADDR | validated address used for public rate-limit buckets |
rwp_headless_require_json_auth | true | require application/json for login and other authenticated write requests |
rwp_headless_allow_preview_query_token | false | permit the legacy token query parameter for previews |
Administrators with a valid WordPress session bypass the public endpoint rate limit. rwp_headless_client_ip should trust forwarded proxy headers only when the request came through a proxy you control; returning an unvalidated value falls back to the shared unknown bucket.
Keep rwp_headless_require_json_auth enabled. Preview tokens belong in X-ReactWP-Preview-Token or a Bearer authorization header because URL query strings are commonly retained in logs and analytics.
Preview and Public-Identity Filters
| Filter | Default | Purpose |
|---|---|---|
rwp_preview_token_authorized | post exists, is not trashed, and the user can edit_post | final authorization before issuing a signed preview token |
rwp_preview_token_max_ttl | 3600 seconds | maximum accepted token lifetime, clamped between 60 and 86400 seconds |
rwp_public_author | author has at least one public, published, non-password-protected post | decide whether author data can enter a public payload |
The default lifetime requested by ReactWP is 600 seconds; rwp_preview_token_max_ttl sets the ceiling rather than extending every token automatically. Do not use rwp_preview_token_authorized to bypass normal editorial capabilities unless the project has an equivalent authorization rule.
Admin, SVG, and Transport Security Filters
| Filter | Default | Purpose |
|---|---|---|
rwp_options_page_capability | manage_options | capability required by Site settings and Theme settings |
rwp_svg_upload_capability | manage_options | capability required to upload SVG files |
rwp_svg_max_bytes | 2097152 bytes | maximum SVG size before and during sanitation |
rwp_permissions_policy | camera=(), geolocation=(), microphone=() | frontend Permissions-Policy header value |
rwp_content_security_policy | base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self' | baseline frontend CSP value |
rwp_hsts_header | max-age=31536000 | HSTS value sent only on production HTTPS responses |
rwp_allow_xmlrpc | false | restore XML-RPC methods and the pingback header when explicitly required |
Returning an empty CSP or HSTS string omits that header. Treat weaker header values, broader capabilities, larger SVG limits, and XML-RPC re-enablement as security decisions. The SVG sanitizer still runs when upload access is broadened.
rwp_client_cache_busted
ReactWP fires this action after the global ReactWP cache generation changes:
add_action('rwp_client_cache_busted', function($version){
// Trigger project-specific CDN invalidation here.
});
This is the bridge for infrastructure-specific purges that ReactWP cannot perform generically.
Rendering Filters
Rendering hooks run after WordPress resolves the route but before it chooses initial HTML.
| Hook | Receives | Default or role |
|---|---|---|
rwp_render_templates | template configuration map | PHP overrides for generated registry defaults |
rwp_render_config | config, route, queried object | final project-level configuration override |
rwp_render_mode | mode, route, queried object | final mode-only override |
rwp_prerender_skip_loader | skip flag, route, initial render | true; bypass the initial loader for valid pre-rendered HTML |
rwp_initial_render_enabled | enabled flag | true; disable all initial static/SSR HTML when false |
rwp_static_render_manifest_paths | manifest paths | runtime uploads first, then the built theme manifest |
rwp_static_render_max_html_bytes | byte limit | 5242880 (5 MiB) |
rwp_static_regeneration_batch_size | route count | 10 routes per WP-Cron event |
rwp_render_templates overrides generated registry defaults by template name:
add_filter('rwp_render_templates', function($templates){
$templates['Home'] = [
'mode' => 'static',
'cache' => [
'tags' => ['post-type:project'],
],
];
return $templates;
});
rwp_render_config receives the normalized config, route, and queried object. rwp_render_mode receives only the final mode plus the same route/object context. Return the value expected by each filter; malformed modes and cache values are normalized afterward.
rwp_prerender_skip_loader defaults to true for valid static/server HTML. Return false when a project requires the loader's imperative critical-media phase before reveal.
rwp_initial_render_enabled can disable every pre-rendered initial response without changing route configuration.
rwp_static_render_manifest_paths changes or extends the packaged/runtime manifest lookup. rwp_static_render_max_html_bytes prevents WordPress from reading an unexpectedly large fragment. rwp_static_regeneration_batch_size controls how many invalidated static routes one WP-Cron event regenerates.
SSR Filters
| Hook | Default | Purpose |
|---|---|---|
rwp_ssr_endpoint | RWP_SSR_ENDPOINT | render service URL |
rwp_ssr_timeout | 2.5 seconds | WordPress HTTP timeout |
rwp_ssr_circuit_seconds | 20 seconds | fallback window after a renderer failure |
rwp_ssr_max_html_bytes | 5242880 bytes | largest accepted HTML response |
rwp_ssr_allow_remote_endpoint | false | opt in to a non-loopback HTTPS renderer |
rwp_ssr_allow_insecure_loopback | false | allow a missing or shorter loopback renderer secret in a local/development WordPress environment |
rwp_ssr_payload | current payload | add server-only request context |
rwp_ssr_cache_identity | authenticated WordPress user ID | partition private cache entries |
rwp_ssr_cache_query_keys | [] | exact query keys permitted in cached SSR route identities |
rwp_ssr_cache_max_query_bytes | 2048 bytes | maximum normalized query-string length considered for SSR caching |
rwp_ssr_endpoint can provide the render service URL instead of RWP_SSR_ENDPOINT. rwp_ssr_timeout, rwp_ssr_circuit_seconds, and rwp_ssr_max_html_bytes control failure boundaries.
Remote renderer hosts are rejected unless rwp_ssr_allow_remote_endpoint returns true. Keep loopback as the default.
Loopback SSR with a missing or shorter secret requires both RWP_SSR_ALLOW_INSECURE_LOOPBACK=1 in the Node process and rwp_ssr_allow_insecure_loopback returning true in WordPress. PHP additionally requires the WordPress environment type to be local or development. Production should use the same secret of at least 32 characters on both sides.
SSR responses with query strings are not cached by default. Add only stable, non-sensitive keys through rwp_ssr_cache_query_keys; any unlisted key bypasses HTML caching for that request. rwp_ssr_cache_max_query_bytes limits the complete normalized search string before those keys are canonicalized.
rwp_ssr_payload can add request-specific project data before WordPress sends the internal payload to the renderer:
add_filter('rwp_ssr_payload', function($payload, $route, $config){
$payload['cart'] = my_project_cart_payload();
return $payload;
}, 10, 3);
Never place secrets in values that a React template will render or that the integrated browser bootstrap also exposes.
Private SSR caching is disabled for anonymous visitors by default because ReactWP cannot infer a cart or application session identity safely. rwp_ssr_cache_identity can return a project-controlled, non-secret cache identity when an anonymous session cache is intentional. Never return one shared value for unrelated private sessions.
rwp_ssr_error fires when the renderer fails or returns an invalid response:
add_action('rwp_ssr_error', function($error, $route){
error_log(sprintf(
'ReactWP SSR failed for %s: %s',
$route['path'] ?? '/',
is_wp_error($error) ? $error->get_error_message() : 'Unknown renderer error'
));
}, 10, 2);
The first argument is the WP_Error or HTTP-layer error value and the second is the normalized route. The browser still receives the client-rendered fallback.
rwp_render_cache_invalidated
ReactWP fires this action after it stores normalized dependency invalidation timestamps:
add_action('rwp_render_cache_invalidated', function($tags, $timestamp){
error_log(sprintf(
'ReactWP invalidated %s at %.6f',
implode(', ', $tags),
$timestamp
));
}, 10, 2);
$tags is the normalized tag array and $timestamp is a Unix timestamp with microsecond precision. Runtime static regeneration already listens to this action. Use it for observability or project-specific revalidation, but do not invalidate the same tags again from inside the callback.
See Cache Tags for automatic invalidation events and custom tag design.
Integrated Current User
Use rwp_current_user_payload to extend the currentUser prop for integrated templates:
add_filter('rwp_current_user_payload', function($payload, $user){
$payload['avatarUrl'] = get_avatar_url($user->ID);
return $payload;
}, 10, 2);
This is separate from rwp_headless_current_user_payload, which controls the external headless auth contract.
Default Theme Examples
The default theme already exposes starter-level examples in:
src/themes/reactwp/template/functions.php
That file is the recommended place for project-specific filter usage.
Recommendation
When possible:
- use these filters first
- keep runtime internals stable
- keep project customizations in the theme or project-level plugin layer
For sanitization, paths, cache helpers, and previews that are methods rather than hooks, see PHP Helpers.