A practical guide to actions and filters for anyone moving from “I install plugins” to “I extend and fix plugins.” This guide is useful for WordPress developers, support engineers, and product teams who need to understand how plugins can be customized safely.
Why Hooks Matter
Picture this customer request: “I want an email field to reject anyone using a @gmail.com address.”
Without hooks, your options are risky:
- Edit the plugin file directly. This works until the next plugin update removes your change.
- Fork the plugin. Now you must maintain your own version forever.
- Use fragile runtime hacks. These are hard to maintain and may not work on many hosting setups.
With a hook, you can add the change in a small custom plugin or snippet. The change can survive plugin updates because you are not editing the original plugin files.
That is the real value of hooks. They let you change behavior safely without fighting WordPress or the plugin.
The Two Main Tools: add_action() and add_filter()
The difference is simple.
Filter means WordPress or a plugin asks, “What should this value be?” You receive a value, change it if needed, and return it.
Action means WordPress or a plugin announces, “Something happened.” You can run your own code at that moment. You do not return anything.
The function signatures look similar:
add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 );
add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 );
Two parameters are easy to misunderstand:
$prioritycontrols the order. Lower numbers run first. The default is10.$accepted_argstells WordPress how many arguments your callback can receive. The default is1.
For filters, always return the value. If you forget to return it, the value may become empty or broken.
A Real Filter Example
Let’s say a plugin provides a validation filter before accepting a field value. You want to block Gmail addresses from an email field.
add_filter(
'my_plugin_field_validation',
'block_gmail_addresses',
10,
4
);
function block_gmail_addresses( $result, $value, $form, $field ) {
// Only run this check on email fields.
if ( $field->type !== 'email' ) {
return $result;
}
// If the value ends with @gmail.com, fail validation.
if ( str_ends_with( strtolower( $value ), '@gmail.com' ) ) {
$result['is_valid'] = false;
$result['message'] = 'Please use a non-Gmail email address.';
}
return $result;
}
Important details:
- We used
4as the accepted arguments because the hook passes four values. - We check the field type first, so the code only runs for email fields.
- We return
$resultevery time because this is a filter.
This changes the behavior without editing the plugin file. That makes it safer during updates.
How to Override Someone Else’s Hook
Sometimes another plugin, theme, or custom code already added a callback to a hook. You may want to remove it or replace it.
remove_filter( 'my_plugin_before_render', 'some_addon_callback', 10 );
To replace it, remove the old callback and add your own callback:
remove_filter( 'my_plugin_before_render', 'some_addon_callback', 10 );
add_filter( 'my_plugin_before_render', 'my_replacement_callback', 10, 4 );
To remove a filter, WordPress needs the same hook name, the same callback, and the same priority.
Trap 1: The Priority Must Match
If the original code used priority 20, this will not remove it:
remove_filter( 'my_plugin_before_render', 'some_addon_callback' );
Why? Because remove_filter() uses priority 10 by default.
The correct version must use the original priority:
remove_filter( 'my_plugin_before_render', 'some_addon_callback', 20 );
This is why developers often search the plugin source for the matching add_filter() or add_action() call.
Trap 2: Class Methods Need the Same Object
A class method callback may look like this:
add_filter( 'my_plugin_before_render', array( $this, 'before_render' ), 10, 4 );
To remove it, you need the same object instance that added it. This can be hard if the plugin does not expose that object.
Sometimes the plugin has a singleton method or public instance you can use. If not, removing that callback becomes more difficult and needs a case-by-case approach.
What If There Is No Hook?
Start with the safest options first.
1. Search More Carefully
Search the plugin code for:
apply_filters
do_action
Many plugins have useful hooks that are not obvious from the settings screen.
2. Check for Pluggable Functions
Some WordPress functions are declared only if they do not already exist:
if ( ! function_exists( 'wp_mail' ) ) {
function wp_mail( /* ... */ ) {
// Default behavior.
}
}
Because of function_exists(), you can define your own version earlier, and WordPress will use yours. This is an older pattern and is not as flexible as hooks, but it still exists in some places.
3. Accept That There May Be No Extension Point
If a plugin does not include a hook where you need one, you cannot add that hook from the outside. A hook is a real line of code inside the plugin.
Your better options are:
- Use a nearby hook and check the condition you care about.
- Use a public function or class from the plugin if it exposes one safely.
- Ask the plugin author to add a hook.
The safest rule is simple: extend through hooks when possible. If no hook exists, avoid hacking plugin files unless there is no other choice.
Creating Your Own Hooks
If you are building a plugin, you can create hooks so other developers can customize your work.
A filter lets someone change a value:
$greeting = apply_filters( 'my_plugin_greeting', 'Hello' );
echo $greeting;
An action lets someone react to an event:
do_action( 'my_plugin_user_signed_up', $user_id, $signup_meta );
Good hook design rules:
- Prefix hook names. Use something like
my_plugin_to avoid conflicts. - Pass useful context. If someone needs the user ID and metadata, pass both.
- Document the hook. A hook that nobody can find is almost the same as no hook.
- Use filters for values and actions for events. Do not mix the pattern.
- Do not break the hook signature after release. Add a new hook if needed.
Lab Findings
1. The Filter Chain Works Like an Assembly Line
Each callback receives the value returned by the previous callback.
$title → callback at priority 10 → callback at priority 11 → final $title
Lower priority runs first. Higher priority runs later. The later callback gets the final chance to change the value.
Example:
add_filter( 'the_title', function( $title ) {
return strtoupper( $title );
}, 10, 1 );
add_filter( 'the_title', function( $title ) {
return strtolower( $title );
}, 11, 1 );
The priority 10 callback changes the title to uppercase first. The priority 11 callback runs after that and changes it to lowercase. So the final title becomes lowercase.
2. Anonymous Functions Are Hard to Remove
This callback cannot be removed easily later:
add_filter( 'the_title', function( $title ) {
return strtoupper( $title );
}, 10, 1 );
The reason is simple: remove_filter() needs to identify the same callback. An anonymous function has no name you can pass later.
Use a named function if you may need to remove it:
function make_title_uppercase( $title ) {
return strtoupper( $title );
}
add_filter( 'the_title', 'make_title_uppercase', 10, 1 );
remove_filter( 'the_title', 'make_title_uppercase', 10 );
Or store the anonymous function in a variable:
$uppercase_title_filter = function( $title ) {
return strtoupper( $title );
};
add_filter( 'the_title', $uppercase_title_filter, 10, 1 );
remove_filter( 'the_title', $uppercase_title_filter, 10 );
3. Class Methods Need the Same Instance
A method callback can look like this:
class TitleFormatter {
public function make_uppercase( $title ) {
return strtoupper( $title );
}
}
$formatter = new TitleFormatter();
add_filter( 'the_title', array( $formatter, 'make_uppercase' ), 10, 1 );
To remove it, use the same object instance:
remove_filter( 'the_title', array( $formatter, 'make_uppercase' ), 10 );
If you create a new object, WordPress will not treat it as the same callback.
Function Callback vs Method Callback
A function is standalone:
function make_title_uppercase( $title ) {
return strtoupper( $title );
}
add_filter( 'the_title', 'make_title_uppercase', 10, 1 );
A method belongs to a class:
class TitleFormatter {
public function make_uppercase( $title ) {
return strtoupper( $title );
}
}
$formatter = new TitleFormatter();
add_filter( 'the_title', array( $formatter, 'make_uppercase' ), 10, 1 );
Simple rule:
- Function = standalone callback.
- Method = callback inside a class.
Cheat Sheet
| Task | Code |
|---|---|
| Hook into an existing event | add_action( 'wp_footer', 'my_callback' ); |
| Modify a value | add_filter( 'the_title', 'my_callback' ); |
| Return from a filter | return $title; |
| Same-priority order | The callback added first runs first. |
| Remove someone else’s filter | remove_filter( 'hook', 'their_callback', $their_priority ); |
| Create your own filter | $value = apply_filters( 'my_plugin_value', $default ); |
| Create your own action | do_action( 'my_plugin_event', $context ); |
| Accept more hook arguments | Use the 4th argument in add_action() or add_filter(). |
| Find available hooks | Search plugin files for apply_filters and do_action. |
Closing Thought
Hooks are not just a WordPress feature. They are a safe agreement between the original developer and anyone who wants to extend the code later.
When you know how to find hooks, use hooks, remove callbacks, and design your own hooks, you stop treating WordPress as a closed tool. You start working with it like a developer.
Leave a Reply