WordPress’s extensibility is almost entirely built on hooks. Understanding hooks allows you to change site behavior without modifying core code—and “not modifying the core” is the first principle of WordPress development, because modified core code will be overwritten during the next update.
Two Types of Hooks
| Type | Purpose | Registration Function | Trigger Function | Return Value Required? |
|---|---|---|---|---|
| Action | Do something at a specific moment | add_action |
do_action |
No |
| Filter | Modify a value and return it | add_filter |
apply_filters |
Yes, must return |
In one sentence: An Action is “execute when the time comes,” while a Filter is “modify as you pass by.”
Action Example
<?php
// Output content in the page header
add_action('wp_head', function () {
echo '<!-- Site has enabled custom code -->';
});
// Perform an action after saving a post
add_action('save_post', function ($post_id, $post, $update) {
if ($update) {
return; // Only handle on initial publish
}
// Example: Notify an external system upon publishing
}, 10, 3);
Filter Example
<?php
// Modify the post title (Note: must return)
add_filter('the_title', function ($title) {
return $title . ' · This Site';
});
// Modify excerpt length
add_filter('excerpt_length', function ($length) {
return 80;
});
Forgetting to return a value in a Filter is the most common error. The consequence is that content becomes blank—because the filtered value becomes null. Moreover, this error does not throw an exception; the content simply disappears, making the cause hard to spot at a glance.
Parameters and Priority
add_action( 'hook_name', 'callback_function', priority, accepted_args );
add_filter( 'hook_name', 'callback_function', priority, accepted_args );
// Priority: smaller numbers execute earlier, default is 10
// Accepted args: default is 1; you must explicitly declare if more are needed
<?php
// When using the 2nd and 3rd parameters, you must specify 2
add_filter('the_content', function ($content, $post_id) {
return $content;
}, 10, 2); // ← The '2' here is mandatory
Wanting to use more parameters but forgetting to declare the count is another high-frequency error: if your callback defines second or third parameters but you don’t declare the count during registration, those parameters will be empty.
Common Hooks
| Hook | Type | Trigger Moment |
|---|---|---|
init |
Action | After WordPress initialization completes |
wp_head |
Action | Frontend page <head> |
wp_footer |
Action | Frontend page footer |
wp_enqueue_scripts |
Action | The correct time to load CSS/JS |
save_post |
Action | Post save/update |
the_content |
Filter | Before outputting body content |
the_title |
Filter | Before outputting the title |
excerpt_length |
Filter | Excerpt length |
Where to Place Code
| Location | Suitable For | Drawbacks |
|---|---|---|
Theme’s functions.php |
Quick testing | Becomes invalid when switching themes; may be overwritten on theme updates |
Child Theme‘s functions.php |
Changes related to the theme | Recommended approach |
| Custom Plugin | Functionality independent of the theme | Recommended; unaffected by theme changes |
<?php
/**
* Plugin Name: Site Custom Functions
* Description: Stores custom hooks for this site
*/
// Write add_action / add_filter here
Save it as wp-content/plugins/site-custom/site-custom.php and activate it in the admin dashboard. A custom plugin is the most comfortable location for long-term maintenance—it does not change with the theme nor is it affected by theme updates.
Verify if the Hook is Working
# View which callbacks are registered on a specific hook
wp hook list 2>/dev/null || echo "Requires wp-cli hook package installed"
# More universal: log directly inside the callback
wp config set WP_DEBUG true --raw
wp config set WP_DEBUG_LOG true --raw
tail -f /var/www/html/wp-content/debug.log
<?php
// Temporarily write to log during debugging
add_action('init', function () {
error_log('Custom hook executed');
});
Decision Tree
Need to modify something via hooks
├─ Is it "doing something at a moment" or "modifying a value"
│ ├─ Doing something → Action (add_action)
│ └─ Modifying value → Filter (add_filter, must return)
├─ If results are incorrect, check first
│ ├─ Did the Filter return a value? ← Most frequent error
│ ├─ Are parameter counts declared (4th arg)?
│ ├─ Is priority overridden by other callbacks?
│ └─ Did the code actually execute? (Add error_log to verify)
└─ Where to place it
├─ Temporary trial → Theme functions.php
└─ Long-term use → Child theme or custom plugin
Scenario Walkthrough: Modifying Plugin Source Code, All Changes Lost After One Update
Phenomenon: Business logic required an extra step after a successful order (writing an internal record).
The most direct approach at the time was to find the plugin file, append your code to the end of the function, and test it successfully.
Later, the plugin updated, and these changes disappeared, causing silent failure.
- First, confirm if the changes were overwritten → Check the modification time of files in the plugin directory,
confirming that the entire directory was replaced during the update. Just like themes, plugin updates overwrite everything. - Realize that “directly modifying others’ files” is problematic →
Updates to third-party code are uncontrollable; any changes written inside will inevitably be removed by the next update. - Confirm if there is an extension point at this location → Most mature plugins provide mount points after completing certain actions,
allowing external logic insertion without modifying source code. - Distinguish which type of mount point to use →
Use Action types for “I need to do something after an event occurs”;
Use Filter types for “I need to modify a value before outputting”. This case falls under the former. - Move the changes to your own location → Place them in an independent functional file or child theme,
connecting via the mount point, and stop touching third-party files. - Verify again after updating → The plugin updates normally, your logic still works, completing the loop.
# Confirm if plugin file changes were overwritten by updates
ls -lt wp-content/plugins/target-plugin/ | head -5
# Mount points usually look like this; find the hook name "after the action occurs"
grep -rIn 'do_action|apply_filters' wp-content/plugins/target-plugin/ | head -20
# Your implementation (place in child theme functions.php or standalone mini-plugin):
# add_action( 'some_action_name', function( $arg ) {
# // Perform your processing, do not touch third-party files
# }, 10, 1 );
Root Cause: Business logic was written directly into third-party code.
Such code is replaced entirely during the next update, so “working at the time” and “still working later” are two different things.
Two key points to remember:
- For all third-party code, connect via mount points; never modify files directly.
The standard for judging correctness is straightforward: Do the changes persist after an update? - First distinguish between “doing something” and “modifying a value.”
The former corresponds to Action-type mount points, the latter to Filter types. Choosing incorrectly won’t throw an error, but nothing will happen.
8 Common Pitfalls
- Forgetting to return in Filter: Content becomes blank, no error thrown.
- Using multiple parameters without declaring the count: Parameters remain empty.
- Directly modifying core files: Overwritten during updates.
- Writing code in Parent Theme: Lost on theme updates.
- Setting priority incorrectly: Overridden by other callbacks.
- echoing debug info in callbacks: Pollutes page output (use error_log instead).
- Calling functionality before init: Some functions are unavailable at this stage.
- Failing to verify execution: Add error_log or check debug.log to confirm.
Real Customer Feedback (From Similar Service Reviews and Consultations on Cloud Markets)
The following excerpts are taken from real buyer reviews and consultations for similar operations services on Alibaba Cloud / Tencent Cloud / Huawei Cloud markets (service providers include our store and peers, provided solely for reference of real feedback, not fictional tickets from our site):
10***09 2024-03-18 5.0 Convenient and stable | Clean interface | Comprehensive after-sales service (User did not fill in comment content)