Advanced WordPress Hooks: Actions and Filters for Customization
WordPress hooks (actions and filters) are the foundation of WordPress extensibility. Instead of modifying core files (bad practice), hooks let you customize WordPress without touching the code.
Mastering hooks transforms you from a plugin-dependent user to an independent WordPress developer.
What Are Hooks?
Hooks are points in WordPress where you can inject custom code.
Two types:
Actions: Do something at a specific point
- Example: "When post is published, send notification email"
Filters: Modify data before it's used
- Example: "Before displaying post title, add emoji prefix"
Understanding Actions
Actions are like events. WordPress triggers actions at specific moments.
Common actions:
wp_head- In pagewp_footer- In page footerwp_enqueue_scripts- Load CSS/JavaScriptinit- WordPress initializationsave_post- Post saveduser_register- User registers
Using actions:
add_action('init', function() {
echo "WordPress has initialized!";
});
Real example:
// Send email when post published
add_action('publish_post', function($post_id) {
$post = get_post($post_id);
wp_mail(
get_option('admin_email'),
'New post published: ' . $post->post_title,
'Check it out: ' . get_permalink($post_id)
);
});
Understanding Filters
Filters modify data. WordPress passes data through filters before displaying.
Common filters:
the_content- Post content before displaythe_title- Post title before displaypost_type_archive_title- Archive page titlewp_mail- Email data before sendingoption_siteurl- Site URL before use
Using filters:
add_filter('the_title', function($title) {
return '✨ ' . $title . ' ✨';
});
This adds emoji before and after every post title.
Real example:
// Add affiliate link to post content
add_filter('the_content', function($content) {
if (is_single()) { // Only on single posts
$affiliate_link = '<p><a href="https://amazon.com/?tag=myaffiliate">View on Amazon</a></p>';
return $content . $affiliate_link;
}
return $content;
});
Actions vs Filters: Key Difference
Actions:
- Execute code at a point
- Don't modify data
- Used for side effects (sending email, logging, etc.)
- Syntax:
add_action()
Filters:
- Receive data, return modified data
- Must return something
- Used for transformations
- Syntax:
add_filter()
Example distinguishing:
// ACTION: Do something
add_action('wp_head', function() {
echo '<meta name="custom" content="value">';
// Doesn't return anything
});
// FILTER: Transform data
add_filter('the_title', function($title) {
return strtoupper($title); // MUST return value
});
Priority and Arguments
Hooks support priority (execution order) and arguments (data passed to function).
Priority:
- Default: 10
- Lower number = earlier execution
- Higher number = later execution
// These run in order: 5, 10, 15
add_filter('the_title', function($title) { return 'First: ' . $title; }, 5);
add_filter('the_title', function($title) { return 'Second: ' . $title; });
add_filter('the_title', function($title) { return 'Third: ' . $title; }, 15);
Arguments: Some hooks pass multiple parameters.
// save_post passes $post_id and $post object
add_action('save_post', function($post_id, $post) {
echo 'Post ID: ' . $post_id;
echo 'Post title: ' . $post->post_title;
}, 10, 2); // "2" = accept 2 arguments
Custom Hooks
Create your own hooks for plugin/theme extensibility.
Custom action:
// In your plugin/theme
do_action('my_custom_event', $data);
// In functions.php or another plugin
add_action('my_custom_event', function($data) {
echo 'Someone triggered my hook!';
});
Custom filter:
// In your plugin/theme
$content = apply_filters('my_custom_filter', $content);
// In functions.php
add_filter('my_custom_filter', function($content) {
return '✨ ' . $content . ' ✨';
});
Common Use Cases
Adding JavaScript
add_action('wp_enqueue_scripts', function() {
wp_enqueue_script(
'my-script',
get_template_directory_uri() . '/js/custom.js',
array('jquery')
);
});
Adding CSS
add_action('wp_enqueue_scripts', function() {
wp_enqueue_style(
'my-style',
get_template_directory_uri() . '/css/custom.css'
);
});
Modifying Post Content
add_filter('the_content', function($content) {
if (is_single()) {
$content .= '<div class="disclaimer">This is a disclaimer</div>';
}
return $content;
});
Adding Admin Notices
add_action('admin_notices', function() {
echo '<div class="notice notice-warning"><p>Custom warning message</p></div>';
});
Scheduled Tasks
// Register hook
add_action('my_hourly_event', 'do_something_hourly');
// Schedule it
if (!wp_next_scheduled('my_hourly_event')) {
wp_schedule_event(time(), 'hourly', 'my_hourly_event');
}
// Function to run
function do_something_hourly() {
// Your code runs hourly
}
Where to Add Hooks
Option 1: functions.php
// In your theme or child theme
// wp-content/themes/your-theme/functions.php
add_filter('the_title', function($title) {
return strtoupper($title);
});
Pros: Simple, no plugin needed Cons: Lost if theme changed, not best practice
Option 2: Must-Use Plugin
// Create file: wp-content/mu-plugins/custom.php
// mu-plugins always load first
add_filter('the_title', function($title) {
return strtoupper($title);
});
Pros: Always loads, can't be deactivated Cons: Limited visibility
Option 3: Custom Plugin
// Create file: wp-content/plugins/my-hooks/my-hooks.php
<?php
/**
* Plugin Name: My Hooks
* Description: Custom hooks
*/
add_filter('the_title', function($title) {
return strtoupper($title);
});
Pros: Professional, easily managed, can be deactivated Cons: Requires plugin structure
Best practice: Custom plugin for extensibility.
Removing Hooks
Sometimes you need to remove hooks added by plugins/themes.
// Remove action
remove_action('wp_head', 'wp_generator');
// Remove filter
remove_filter('the_content', 'wpautop');
// Remove with priority
remove_action('wp_footer', 'function_name', 15);
Debugging Hooks
See which hooks are running and in what order.
Method 1: Query Monitor (plugin)
- Install "Query Monitor"
- Hooks tab shows all running hooks
- See priority and execution order
Method 2: Manual debug
add_action('wp_head', function() {
global $wp_actions;
echo '<pre>';
print_r($wp_actions);
echo '</pre>';
});
Method 3: Log to file
add_action('init', function() {
error_log('WordPress initialized');
});
Check wp-content/debug.log.
Best Practices
1. Use unique hook names
// Good: Plugin-specific prefix
add_action('my_plugin_init', 'do_something');
// Bad: Too generic
add_action('init', 'do_something');
2. Document your hooks
/**
* Action: my_plugin_init
* Fires when my plugin initializes.
*
* @param string $plugin_name The plugin name
*/
do_action('my_plugin_init', 'My Plugin');
3. Set proper priority
// If you need this to run early, use low priority
add_action('wp_head', 'critical_function', 5);
// Non-critical can be default or later
add_action('wp_head', 'non_critical', 15);
4. Use anonymous functions for simplicity
// Simple hooks can use anonymous functions
add_filter('the_title', function($title) {
return strtoupper($title);
});
// Complex hooks better as named functions
add_action('wp_insert_post', 'my_plugin_handle_post_insert');
function my_plugin_handle_post_insert($post_id) {
// Complex logic here
}
Conclusion
Hooks are WordPress's superpower. They let you customize anything without modifying core files.
Master actions and filters, and you unlock unlimited WordPress customization possibilities.
What's your favorite WordPress hook? What customizations have you built? Share your examples in the comments.
