WordPress customization is built almost entirely on hooks. A hook is a place where you can slot your own code into the middle of WordPress’s processing. The add_action(...) and add_filter(...) calls you write in your theme’s functions.php are exactly that.
There are only two kinds to learn.
- Actions (
add_action)—run something at that moment (no return value needed) - Filters (
add_filter)—modify and return the value you’re handed (returnis required)
Actions: slot processing into a moment
function my_enqueue_styles() {
wp_enqueue_style('main', get_theme_file_uri('style.css'), [], '1.0');
}
add_action('wp_enqueue_scripts', 'my_enqueue_styles');This says “when the moment for loading CSS (wp_enqueue_scripts) arrives, run this function.” You never call it yourself—WordPress calls it at the right time.
Filters: rewrite a value
function my_excerpt_more($more) {
return '…'; // ← always return
}
add_filter('excerpt_more', 'my_excerpt_more');This says “when you build the characters at the end of an excerpt (excerpt_more), run them through this function first.” The promise is that you modify the value you received and return it.
The shape of it
add_action('hook_name', 'function_name', priority, number_of_arguments);
add_filter('hook_name', 'function_name', priority, number_of_arguments);- Priority (default
10)—smaller runs earlier, larger runs later - Number of arguments (default
1)—specify this when you want to receive the second argument onward
// Make sure our excerpt length overrides anything set elsewhere
add_filter('excerpt_length', fn() => 80, 999);
// An example that receives two arguments
function my_title($title, $post_id) {
return $post_id === 1 ? 'Front page notice' : $title;
}
add_filter('the_title', 'my_title', 10, 2);Commonly used hooks
| Hook name | Kind | When it fires / what it’s for |
|---|---|---|
init | action | After initialization. Registering post types and taxonomies |
after_setup_theme | action | Enabling theme features (add_theme_support) |
wp_enqueue_scripts | action | Loading CSS and JS on the front end |
admin_enqueue_scripts | action | Loading CSS and JS in the admin screen |
wp_head / wp_footer | action | Adding output at the end of head / the end of body |
pre_get_posts | action | Changing the conditions of the main query |
save_post | action | Doing something when a post is saved |
the_content | filter | Rewriting post content |
the_title | filter | Rewriting the title |
excerpt_length / excerpt_more | filter | Excerpt length and its trailing characters |
body_class | filter | Adding classes to body |
upload_mimes | filter | Allowing more file types to be uploaded |
Example 1: append shared text to the end of post content
function my_after_content($content) {
if (is_single() && is_main_query()) {
$content .= '<p class="cta">We send the latest updates by email newsletter.</p>';
}
return $content;
}
add_filter('the_content', 'my_after_content');Narrowing it down with conditions like is_single() matters. Without conditions, it gets appended to excerpts in archives and other places you didn’t intend.
Example 2: add a class to body
function my_body_class($classes) {
if (is_front_page()) {
$classes[] = 'is-front';
}
return $classes;
}
add_filter('body_class', 'my_body_class');This is a filter that receives an array and returns an array. You can use it for tweaks like “change the spacing on the front page only” in CSS.
Example 3: do something when a post is saved
function my_on_save($post_id, $post, $update) {
if (wp_is_post_revision($post_id) || $post->post_type !== 'shohin') {
return;
}
// do something
}
add_action('save_post', 'my_on_save', 10, 3);save_post also runs for autosaves and revisions, so putting a guard at the top is the standard move.
Writing it shorter with anonymous functions
You can register a PHP anonymous function (closure) too. For short pieces of work it reads better.
add_action('after_setup_theme', function () {
add_theme_support('post-thumbnails');
add_theme_support('title-tag');
});Note, though, that anonymous functions can’t be removed with remove_action() (they have no name). Anything you might need to unhook later should be a named function.
Removing someone else’s hook
To stop something a theme or plugin registered, unhook it by naming the same hook, function name and priority.
add_action('init', function () {
remove_action('wp_head', 'wp_generator'); // hide the WordPress version
remove_filter('the_content', 'wpautop'); // stop automatic formatting (big impact—be careful)
});A different priority means it won’t be removed, so check the registering code and use the same value.
Avoiding function name collisions
functions.php is loaded from both the theme and the child theme, and it shares a namespace with plugins. Always give function names a prefix.
// ❌ obvious names collide
function setup() { … }
// ✅ use a prefix specific to the site
function shimaenaga_setup() { … }TipsHow to build a child theme (minimal setup)How to build a child theme (and why functions.php isn’t overridden)Summary
- A hook is a place to slot your own code into WordPress’s processing—the foundation of customization
- Action = run at a moment (no return); filter = modify and return a value (return required)
- When it doesn’t fire, suspect priority (the third argument) and when you registered it. To receive more arguments, use the fourth
- Give function names a prefix. Work that should survive a theme change belongs in a plugin
Once the idea of hooks clicks, requests like “I’d like to change this plugin’s behavior a little” come within reach.