Problem Background
In WordPress development, the init hook is a core action hook used to execute code after the WordPress environment is initialized. However, if callback functions from plugins or themes are incorrectly attached to the init hook multiple times, or if the function itself is called repeatedly, it can lead to serious performance issues such as duplicate database queries, redundant resource loading, and even logical errors.
Solution: Using Static Variables or Flags
The most direct and effective method is to use a static variable or class property inside the callback function to track whether it has already executed, thereby preventing duplicate runs.
Method 1: Using a Static Variable in a Regular Function
function my_custom_init_action() {
static $has_run = false;
if ( $has_run ) {
return;
}
$has_run = true;
// Your actual initialization code goes here
// e.g., register custom post types, add shortcodes
// This code will only run once
register_post_type( 'my_product', array(
'public' => true,
'label' => 'Products'
) );
}
add_action( 'init', 'my_custom_init_action' );
How it works: The static variable $has_run is initialized to false on the first function call and set to true after the core code executes. If WordPress's init hook triggers this function multiple times unexpectedly, subsequent calls will return early due to the condition check, protecting the core logic.
Method 2: Using a Property in a Class Method
If your code is organized in an object-oriented (OOP) style, you can use a private class property to achieve the same goal.
class MyPluginInit {
private $has_run = false;
public function setup() {
add_action( 'init', array( $this, 'init_action' ) );
}
public function init_action() {
if ( $this->has_run ) {
return;
}
$this->has_run = true;
// Your actual initialization code
// This code will only run once
flush_rewrite_rules(); // Example: flush rewrite rules
}
}
$my_plugin = new MyPluginInit();
$my_plugin->setup();
Checking Hook Attachment Count
Sometimes the issue isn't inside the function but because the add_action statement is executed multiple times, attaching the same callback to the hook repeatedly. You can debug this with:
// Check how many times a callback is attached to the 'init' hook
$hook_count = has_action( 'init', 'my_custom_init_action' );
if ( $hook_count ) {
error_log( 'my_custom_init_action attached to init hook count: ' . $hook_count );
}
If the count is greater than 1, your add_action call might be in a file that loads multiple times (e.g., inside a template loop) or the plugin is initialized repeatedly. Ensure initialization code (like add_action) runs only once, typically in the main plugin file or theme's functions.php.
Advanced Pattern: Using WordPress's did_action Function
WordPress provides did_action( $hook_name ), which returns how many times a given hook has been fired. While init usually fires once per request, it might fire multiple times in special cases (e.g., AJAX, REST API requests). You can use it for finer control.
function my_conditional_init_action() {
// Ensure execution only on the main request's init, avoid duplicates in AJAX, etc.
if ( did_action( 'init' ) > 1 ) {
return;
}
// Your initialization code
// ...
}
add_action( 'init', 'my_conditional_init_action' );
Note: This method suits scenarios needing strict separation between 'first main request initialization' and 'subsequent sub-requests'. For preventing duplicate execution within the same page request, the static variable method is preferred.
Best Practices & Summary
- Single Responsibility: Ensure functions attached to
initonly handle initialization; keep logic simple. - Defensive Programming: Always include 'run once' protection in critical one-time operation functions.
- Code Placement: Place
add_action( 'init', ... )calls in the root scope of your main plugin file or theme'sfunctions.phpto prevent duplicate execution from conditionals or loops. - Performance Monitoring: Use debugging plugins like Query Monitor to check the number and execution time of callbacks on the
inithook.
By implementing these strategies, you can effectively avoid performance loss and potential errors caused by multiple executions of init hook callbacks, making your WordPress plugin or theme run more efficiently and stably.