Skip to main content

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_system
  • rwp_bootstrap
  • rwp_route_payload
  • rwp_wp_head
  • rwp_critical_fonts
  • rwp_critical_medias
  • rwp_no_critical_medias
  • rwp_allowed_rest_routes
  • rwp_authenticated_cross_origin_rest_routes
  • rwp_headless_allowed_origins
  • rwp_headless_public_rate_limit
  • rwp_headless_public_rate_window
  • rwp_headless_client_ip
  • rwp_headless_require_json_auth
  • rwp_headless_allow_preview_query_token
  • rwp_headless_public_settings
  • rwp_headless_user_capabilities
  • rwp_headless_current_user_payload
  • rwp_headless_sitemap_post_types
  • rwp_headless_sitemap_limit
  • rwp_headless_sitemap_items
  • rwp_headless_allow_insecure_auth
  • rwp_preview_token_authorized
  • rwp_preview_token_max_ttl
  • rwp_public_author
  • rwp_options_page_capability
  • rwp_svg_upload_capability
  • rwp_svg_max_bytes
  • rwp_permissions_policy
  • rwp_content_security_policy
  • rwp_hsts_header
  • rwp_allow_xmlrpc
  • rwp_render_templates
  • rwp_render_config
  • rwp_render_mode
  • rwp_prerender_skip_loader
  • rwp_initial_render_enabled
  • rwp_static_render_manifest_paths
  • rwp_static_render_max_html_bytes
  • rwp_static_regeneration_batch_size
  • rwp_ssr_endpoint
  • rwp_ssr_timeout
  • rwp_ssr_circuit_seconds
  • rwp_ssr_max_html_bytes
  • rwp_ssr_allow_remote_endpoint
  • rwp_ssr_allow_insecure_loopback
  • rwp_ssr_payload
  • rwp_ssr_cache_identity
  • rwp_ssr_cache_query_keys
  • rwp_ssr_cache_max_query_bytes
  • rwp_current_user_payload

ReactWP also fires:

  • rwp_client_cache_busted
  • rwp_render_cache_invalidated
  • rwp_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_Post
  • WP_Term
  • WP_User
  • null for 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, or audio
  • src: the main source URL
  • target: a selector, a DOM node, or an array of selectors/nodes
  • sources: optional alternative sources with their own attributes such as media, type, or sizes
  • any DOM props that make sense for the rendered element, such as alt, className, poster, muted, loop, controls, autoplay, or playsInline

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:

FilterDefaultPurpose
rwp_headless_public_rate_limit240maximum public requests per address in one window; 0 disables this application limit
rwp_headless_public_rate_window60 secondspublic rate-limit window, clamped between 10 and 3600 seconds
rwp_headless_client_ipREMOTE_ADDRvalidated address used for public rate-limit buckets
rwp_headless_require_json_authtruerequire application/json for login and other authenticated write requests
rwp_headless_allow_preview_query_tokenfalsepermit 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

FilterDefaultPurpose
rwp_preview_token_authorizedpost exists, is not trashed, and the user can edit_postfinal authorization before issuing a signed preview token
rwp_preview_token_max_ttl3600 secondsmaximum accepted token lifetime, clamped between 60 and 86400 seconds
rwp_public_authorauthor has at least one public, published, non-password-protected postdecide 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

FilterDefaultPurpose
rwp_options_page_capabilitymanage_optionscapability required by Site settings and Theme settings
rwp_svg_upload_capabilitymanage_optionscapability required to upload SVG files
rwp_svg_max_bytes2097152 bytesmaximum SVG size before and during sanitation
rwp_permissions_policycamera=(), geolocation=(), microphone=()frontend Permissions-Policy header value
rwp_content_security_policybase-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'baseline frontend CSP value
rwp_hsts_headermax-age=31536000HSTS value sent only on production HTTPS responses
rwp_allow_xmlrpcfalserestore 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.

HookReceivesDefault or role
rwp_render_templatestemplate configuration mapPHP overrides for generated registry defaults
rwp_render_configconfig, route, queried objectfinal project-level configuration override
rwp_render_modemode, route, queried objectfinal mode-only override
rwp_prerender_skip_loaderskip flag, route, initial rendertrue; bypass the initial loader for valid pre-rendered HTML
rwp_initial_render_enabledenabled flagtrue; disable all initial static/SSR HTML when false
rwp_static_render_manifest_pathsmanifest pathsruntime uploads first, then the built theme manifest
rwp_static_render_max_html_bytesbyte limit5242880 (5 MiB)
rwp_static_regeneration_batch_sizeroute count10 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

HookDefaultPurpose
rwp_ssr_endpointRWP_SSR_ENDPOINTrender service URL
rwp_ssr_timeout2.5 secondsWordPress HTTP timeout
rwp_ssr_circuit_seconds20 secondsfallback window after a renderer failure
rwp_ssr_max_html_bytes5242880 byteslargest accepted HTML response
rwp_ssr_allow_remote_endpointfalseopt in to a non-loopback HTTPS renderer
rwp_ssr_allow_insecure_loopbackfalseallow a missing or shorter loopback renderer secret in a local/development WordPress environment
rwp_ssr_payloadcurrent payloadadd server-only request context
rwp_ssr_cache_identityauthenticated WordPress user IDpartition private cache entries
rwp_ssr_cache_query_keys[]exact query keys permitted in cached SSR route identities
rwp_ssr_cache_max_query_bytes2048 bytesmaximum 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.