Registering hooks before the WordPress hooks system starts
Usually hooks in WordPress can be used only after the core initialization, when the global object $wp_filter is already created. However sometimes there is a need to create a hook for an action earlier — even before the hooks system starts working. For this, WordPress allows you to define the global variable $wp_filter in advance.
How it works
In WP there is the function WP_Hook::build_preinitialized_hooks(), which normalizes the array $wp_filter predefined in advance, and turns it into fully-fledged WP_Hook objects.
Thus, you can prepare in advance $GLOBALS['wp_filter'] as if the hooks were already registered. And WP will then in its own time turn your array into a WP_Hook object and when the hook is executed, your callback will run.
- Hooks are usually registered after WordPress starts, but you can do this earlier by manually defining
$GLOBALS['wp_filter']. - For this, the method WP_Hook::build_preinitialized_hooks() is used.
- Such code works only in
wp-config.phpor earlier. - Suitable for temporary solutions and low-level fixes.
Format of the $wp_filter array
In the array key there should be the name of the hook, and in the value you need to specify an array of callbacks grouped by priorities:
$filters = [
'wp_fatal_error_handler_enabled' => [
10 => [
[
'accepted_args' => 0,
'function' => function() {
return false;
},
],
],
],
];
The hook wp_fatal_error_handler_enabled is called even before the hooks system starts. Because of this, it can be overridden already in wp-config.php.
Example: redirect from the home page to a subdomain
Consider a real case. There is a multisite: / (English version) and /ru/ (Russian version). During development of the English version, it was temporarily required to redirect the home page / → /ru/.
To make such non-standard behavior obvious, and the code easy to find and remove, it was moved to wp-config.php. But since the hooks system is not yet working there, the hook was registered via $GLOBALS['wp_filter'].
/// Temp fix for prod site while EN version is under development.
defined( 'WP_CLI' ) || defined( 'DOING_CRON' ) || defined( 'DOING_AJAX' ) || temp_fix_for_multisite_migration_for_prod();
function temp_fix_for_multisite_migration_for_prod() {
$host = 'example.com';
if( $host !== $_SERVER['HTTP_HOST'] ){
return;
}
// ! before REQUEST_URI change
if( preg_match( '~^/ru/?$~', $_SERVER['REQUEST_URI'] ) ){
header( "Location: https://$host", true, 307 );
exit;
}
// request substitution
if( '/' === $_SERVER['REQUEST_URI'] ){
$_SERVER['REQUEST_URI'] = '/ru/';
}
$temp_ru_home_url_fix = static function( $url, $path, $orig_scheme, $blog_id ){
if( ! $path || '/' === $path ){
$url = preg_replace( '~/ru/?~', '/', $url );
}
return $url;
};
// add a hook for the wp action
$GLOBALS['wp_filter'] = [
'wp' => [
0 => [
[
'function' => static fn() => add_filter( 'home_url', $temp_ru_home_url_fix, 99, 4 ),
'accepted_args' => 0,
],
],
],
];
}
Example: disabling the recovery screen
$GLOBALS['wp_filter'] = [ 'wp_fatal_error_handler_enabled' => [ 10 => [[ 'function' => static fn() => false, 'accepted_args' => 0, ]], ], ];