WordPress Performance: Advanced customisation with hooks

WordPress hooks beyond add_action: how priorities affect performance, removing expensive callbacks safely, building your own hook contracts and profiling them.

Updated 6 min read

Hooks let code change WordPress’s behaviour without editing core. Actions run code at a point in the request, and filters change a value as it passes through. They’re also where most of a slow page’s extra work happens, and where you can remove it.

If you’ve ever looked at a slow page and suspected a plugin, its hooks are usually the place to look.

What this post covers

  • the practical difference between actions and filters
  • priorities, accepted arguments, and how they change execution order
  • removing expensive behaviour safely
  • designing your own hooks so your theme stays modular
  • debugging and profiling hook execution

1. Actions and filters

Actions run code at a point in time. They do not return a value.

add_action( 'init', 'register_custom_post_type' );

Filters take a value, modify it, and return it.

add_filter( 'the_title', 'prefix_title' );

This matters because filters often run a lot. A heavy filter on every post title in a loop slows the whole page.


2. Priorities and accepted arguments

Every hook can take:

  • a priority (default 10)
  • the number of accepted arguments (default 1)
add_filter( 'the_content', 'add_cta_block', 20, 1 );

How priority works

  • lower number runs earlier
  • higher number runs later
  • if two callbacks have the same priority, they run in the order they were added

For example, you might strip out shortcodes early, then add your own markup late, after other plugins have finished.

Accepted arguments

If a filter passes more than one parameter, declare how many you want. the_title passes the title and the post ID, but your callback only receives the second if you ask for it:

add_filter( 'the_title', 'prefix_title', 10, 2 );

function prefix_title( string $title, int $post_id ): string {
    return get_post_type( $post_id ) === 'product' ? 'Product: ' . $title : $title;
}

Always match the hook’s signature. Ask for more arguments than the hook passes and PHP throws an error.


3. Anonymous functions

Inline callbacks are concise:

add_action( 'wp_footer', function () {
    echo '<!-- tracking -->';
} );

The catch is that you can’t easily remove them later, because nothing holds a reference to them. In production code, use a named function or a class method, so you can:

  • remove it conditionally
  • unit test it
  • find it when you need to change it

4. Removing hooks to cut work

This is one of the fastest ways to reduce the work in each request, especially when plugins add behaviour you don’t need.

Removing core output

remove_action( 'wp_head', 'wp_generator' );

Removing plugin behaviour conditionally

If a plugin adds an expensive callback that runs on all pages, remove it and only re-add it where you need it.

add_action( 'wp', function () {
    if ( ! is_singular( 'product' ) ) {
        remove_action( 'wp_enqueue_scripts', 'plugin_enqueue_assets' );
    }
}, 20 );

The wp action is a good place for this because WordPress has resolved the main query, so conditionals like is_singular() are reliable.

remove_action() only works if you pass the same priority the plugin used when it added the callback. If the plugin hooked in at priority 20, remove it at 20.

The rule: remove first, then add behaviour back only where it’s needed.


5. Do less work early in the request

WordPress has early hooks (plugins_loaded, init) and later hooks (wp, template_redirect, wp_enqueue_scripts, wp_footer).

If you do heavy work early, it runs even on requests that will be cached, redirected, or short-circuited. Instead:

  • defer heavy logic until you know you need it
  • avoid global work on every request
  • return early on pages the code doesn’t apply to

Example: a complicated navigation build does not need to run on every endpoint, especially not admin-ajax or REST requests.


6. Custom hooks in your own theme

In a theme that will keep changing, custom hooks keep features out of the template files.

Define hooks around template parts

do_action( 'psy_before_header' );
get_template_part( 'partials/header' );
do_action( 'psy_after_header' );

Then you can add features, swap markup, or integrate plugins without editing template files.

Document your hooks

If you create custom hooks, document:

  • when they fire (which template, which lifecycle stage)
  • what arguments are passed
  • whether callbacks should echo or return data

Whoever works on the theme next can then use the hooks without reading every template.


7. Debugging hook order and execution

Quick introspection

if ( did_action( 'init' ) ) {
    error_log( 'init has already run' );
}

error_log( current_filter() );

Tools

  • Query Monitor can show hooks, timing, and related templates.
  • Debug Bar is useful on some setups.
  • For deeper work, an APM such as New Relic shows the time spent inside each callback.

If you suspect a plugin, locate its callbacks by searching for add_action and add_filter in the plugin codebase, then confirm with Query Monitor.


8. Practical patterns I use a lot

A “guard clause” wrapper

function psy_only_on_frontend( callable $callback ): void {
    if ( is_admin() ) {
        return;
    }

    $callback();
}

Then use it to wrap frontend-only work.

Namespacing and prefixes

Prefix custom hook names (psy_*) to avoid collisions, especially on multisite and large plugin stacks.

Avoid heavy filters in loops

Filters like the_title, the_content, and the_excerpt can run many times per request. Keep callbacks tiny, and cache computed results if needed.


FAQ

Is it safe to remove hooks?

Yes, if you know what you’re removing and you test it. Remove hooks only where they aren’t needed, and check that page output, SEO metadata and tracking still work.

Where should I put remove_action calls?

Usually inside a callback on wp, or sometimes init, so WordPress conditionals are available. For admin-only removals, use admin_init.

Can hooks improve performance?

Not on their own. The gain comes from using them to skip work a page doesn’t need, stop loading unused files and control when expensive code runs.


I use custom hooks like these to keep a theme and its plugins apart in the WordPress sites I build, so either can change without breaking the other.

Published , updated .

Part 3 of WordPress Performance.

Related posts