Tips・The foundations of WordPress customization

An introduction to WordPress hooks (add_action and add_filter)

The idea behind hooks, the foundation of WordPress customization. Covers the difference between add_action and add_filter, setting priority and argument counts, a list of commonly used hooks, and why hooks sometimes don't fire, with code.

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 (return is 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 nameKindWhen it fires / what it’s for
initactionAfter initialization. Registering post types and taxonomies
after_setup_themeactionEnabling theme features (add_theme_support)
wp_enqueue_scriptsactionLoading CSS and JS on the front end
admin_enqueue_scriptsactionLoading CSS and JS in the admin screen
wp_head / wp_footeractionAdding output at the end of head / the end of body
pre_get_postsactionChanging the conditions of the main query
save_postactionDoing something when a post is saved
the_contentfilterRewriting post content
the_titlefilterRewriting the title
excerpt_length / excerpt_morefilterExcerpt length and its trailing characters
body_classfilterAdding classes to body
upload_mimesfilterAllowing more file types to be uploaded
TipsThe right way to load CSS and JavaScript (wp_enqueue)How to write CSS and JS loading (wp_enqueue_scripts)TipsHow to fetch posts outside the loop (WP_Query)How to adjust the main archive with pre_get_posts

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

  1. A hook is a place to slot your own code into WordPress’s processing—the foundation of customization
  2. Action = run at a moment (no return); filter = modify and return a value (return required)
  3. When it doesn’t fire, suspect priority (the third argument) and when you registered it. To receive more arguments, use the fourth
  4. 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.

FAQ

What's the difference between add_action and add_filter?
add_action "adds something to run at that moment" (no return value needed), while add_filter "modifies the value it's handed and returns it" (you must return). Use an action when you want to output or run something, and a filter when you want to rewrite a value.
Where do I write hooks?
In your theme's functions.php, a child theme's functions.php, or your own plugin. Anything you want to survive a theme change belongs in a plugin rather than the theme.
Everything disappeared after I added an add_filter.
You most likely forgot the return in your filter function. A filter promises to return the value it received, so without a return you get nothing.
My hook doesn't fire.
Check whether you're registering it after that hook has already run, whether another piece of code is overriding it via priority, and whether the function name is spelled correctly. Priority is the third argument (larger runs later).