Cover image
Web Front-end
11 minute read

Harness the Power of WordPress Hooks: Actions and Filters Explained

WordPress developers might be tempted to customize this popular website builder directly, but that approach creates ongoing maintenance headaches. Fortunately, there’s a clean way to extend functionality: via WordPress hooks, actions, and filters.

Like any CMS, WordPress won’t always meet your every need right out of the box. Since it is open-source, you could hack it to make it conform to your business needs—but instead, you can use WordPress’ hooks to achieve your goals. Building with hooks is a winning strategy that frees WordPress developers to build just about any website feature imaginable.

WordPress Hooks: Actions and Filters

WordPress hooks are not just powerful customization tools, they are how WordPress components interact with one another. Hooked functions manage many of the routine tasks we consider to be part and parcel of WordPress, such as adding styles or scripts to a page, or surrounding footer text with HTML elements. A search of WordPress Core’s codebase reveals thousands of hooks in more than 700 locations. WordPress themes and plugins contain even more hooks.

Before we jump into hooks and explore the difference between action hooks and filter hooks, let’s understand where they fit within WordPress’ architecture.

WordPress Infrastructure

WordPress’ modular elements readily integrate with one another, so we can easily mix, match, and combine:

  1. WordPress Core: These are the files required for WordPress to work. WordPress Core provides generalized architecture, the WP Admin dashboard, database queries, security, and more. WordPress Core is written in PHP and uses a MySQL database.
  2. Theme (or Parent Theme): A theme defines the basic layout and design of a website. Powered by PHP, HTML, JavaScript, and CSS files, a theme functions by reading the WordPress MySQL database to generate the HTML code that renders in a browser. Hooks in a theme may add stylesheets, scripts, fonts, or custom post types, for example.
  3. Child Theme: We create child themes ourselves to fine-tune the basic layout and design that parent themes provide. Child themes can define stylesheets and scripts to modify inherited features or add or remove post types. Child theme instructions always supersede those of the parent theme.
  4. Plugin(s): To extend the back-end functionality of WordPress, we can choose from thousands of third-party plugins. Hooks in a plugin could, for example, notify us by email when a post is published or hide user-submitted comments that contain banned language.
  5. Custom Plugin(s): When a third-party plugin does not fully meet business needs, we can turbocharge it by writing a custom plugin in PHP. Or we can write a new plugin from scratch. In both cases, we would add hook(s) to extend existing functionality.

Pyramid showing, from base to top, five levels: (1) WordPress Core, (2) Theme, (3) Child Theme, (4) Plugins, (5) Custom Plugins.
WordPress Infrastructure Hierarchy

Given that we have access to the source of all five layers, why are hooks needed in WordPress?

Code Safety

To keep up with evolving technologies, contributors to WordPress Core, parent themes, and plugins frequently release updates to mitigate security vulnerabilities, fix bugs, resolve incompatibilities, or offer new features. As any consultant with emergency experience knows firsthand, failure to keep WordPress components up to date can compromise or even disable a site.

If we directly modify local copies of upstream WordPress components, we encounter a problem: Updates overwrite our customizations. How can we circumvent this when customizing WordPress? Via hooks, in the child theme and custom plugin(s).

Coding in Our Child Theme

A child theme is a safe space where we can customize the look and feel of our installed theme. Any code added here will override comparable code in the parent without the risk of being overwritten by an update.

When a child theme is activated, it links to a deactivated parent, inheriting and exhibiting the parent’s characteristics while remaining unimpacted by the parent’s updates. So as not to fall prey to temptation to modify a theme, best practices suggest that a child theme be activated as part of our setup.

Writing Custom Plugin(s)

When a plugin is activated, its functions.php file executes with each call on the server. WordPress, in turn, loads and sorts hooks from all active plugins according to their priority and executes these sequentially. To extend the functionality of a third-party plugin, we can write our own WordPress custom plugin.

Where to Place Our Hooks in WordPress

Goal Example Where?  
    Child Theme PHP Custom Plugin PHP
To modify the structure of a web page Adding a custom stylesheet to change the colors and fonts of website elements  
To modify the functionality of another plugin (i.e., create a plugin to enhance the functionality of a third-party plugin) Adding a subheading (e.g., “News”) to custom post types  
To add a new feature that goes beyond WordPress Core Modifying the workflow that takes place when a post is visited to include updating a counter in the database  

Pre-dive Prep: Definitions

To avoid conflating terms, we’ll stick to this terminology:

  • A hook is a sweet spot in WordPress where functions are registered to run. We may connect our functions to one of the many hooks in WordPress and its components or create our own.
    • An action hook runs actions.
    • A filter hook runs filters.
  • A hooked function is a custom PHP callback function that we’ve “hooked” into a WordPress hook location. Which type to use depends on whether the hook is meant to allow changes outside the function—e.g., adding directly to the webpage output, modifying a database, or sending an email. These are known as side effects.
    • A filter (or filter function) should avoid side effects by only working on, then returning a modified copy of, the data passed to it.
    • An action (or action function), in contrast, is intended to cause side effects. It has no return value.

Diagram showing functions paired with compatible hooks. Filter hooks have filter functions attached to them, and action hooks have action functions attached to them.
WordPress hooks can have multiple callback functions, but all callback functions have to match the type of hook they’re registered with.

With these distinctions in mind, we can begin our exploration of hooks.

Abstraction and Clean Code

When an action or filter is incorporated into a hook, as needed, we fulfill the objectives of writing just one function per task and of avoiding the duplication of code within a project. For example, say we want to add the same stylesheet to three page templates (archive, single page, and custom post) in our theme. Rather than overriding each template in the parent, then recreating each in our child theme, then adding stylesheets to individual head sections, we can write code in a single function and attach it with the wp_head hook.

Thoughtful Nomenclature

Proactively avoid conflicts by naming a child theme or custom plugin hooks uniquely. Having same-named hooks in a single site is a recipe for unintended code behavior. Best practices prescribe that we begin the name of our hook with a unique, short prefix (e.g., author’s, project’s, or company’s initials), followed by a descriptive hook name. For example, using the pattern “project initials plus hook name,” for the project Tahir’s Fabulous Plugin, we could name our hooks tfp-upload-document or tfp-create-post-news.

Concurrent Development and Debugging

A single hook may trigger more than just one action or filter. For example, we could write a web page that contains multiple scripts, all of which use the wp_head action hook to print HTML (e.g., a <style> or <script> section) within the <head> section on the page’s front end.

Thus, several plugin developers can advance multiple goals in parallel on a single plugin, or divide the plugin into multiple, simpler individual plugins. If a feature does not work properly, we can directly investigate and debug its hooked function without having to search the entire project.

Actions

An action runs code when an event occurs in WordPress. Actions can perform operations such as:

  • Creating data.
  • Reading data.
  • Modifying data.
  • Deleting data.
  • Recording the permissions of logged-in users.
  • Tracking locations and storing them in the database.

Examples of events where actions can be triggered include:

  • init, after WordPress loads but prior to its sending headers to the output stream.
  • save_post, when a post has been saved.
  • wp_create_nav_menu, just after a navigation menu is created successfully.

An action can interact with an API to transmit data (e.g., a link to a post on social media), but it will not return data to the calling hook.

Let’s say we would like to automate the sharing of all new posts on our site via social media. Begin by looking through WordPress documentation for a hook that can be triggered whenever a post is published.

There are no shortcuts to finding our hook: We would learn through experience or pore through the listed actions to find likely candidates. We might consider save_post a candidate, but quickly rule it out since it would trigger multiple times during a single editing session. A better choice is transition_post_status, which triggers only when a post status is changed (e.g., from draft to publish, from publish to trash).

We will go with transition_post_status but will also refine our action to run only when the status of our post transitions to publish. Further, by following the official documentation and APIs of the various social media platforms, we can integrate and publish our post’s content, along with a featured image:

<?php
function publish_post_on_social_media ( $new_status = NULL, $old_status = NULL, $post_ID = NULL ) {
  if ( 'publish' == $new_status && 'publish' != $old_status ) {
    // build the logic to share on social media
  }
}
add_action( 'transition_post_status', 'publish_post_on_social_media', 10, 3 );
?>

Now that we know how to use action hooks, there’s one that’s particularly helpful, especially when it comes to CSS.

Designating Priorities With wp_enqueue_scripts

Say we want to add our child theme’s stylesheet last, after all others have loaded, to ensure that any same-named classes originating elsewhere are overridden by our child theme’s classes.

WordPress loads stylesheets in a default order:

  1. Parent theme’s
  2. Child theme’s
  3. Any plugins

In this construct:

add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1)

…the priority value of the added action determines its order of execution:

  • The default priority value for wp_enqueue_scripts (or any action) is “10.”
  • A function runs earlier if we reset its priority to a lower number.
  • A function runs later if we reset its priority to a higher number.

To load our child theme’s stylesheet last, use wp_enqueue_scripts, an action that is commonly used by WordPress themes and plugins. We need only change the priority of our child theme’s action wp_enqueue_scripts to a number that is higher than the default of “10,” say “99”:

add_action( 'wp_enqueue_scripts', 'child_theme_styles', 99 );



In general, we use actions when we are not looking for return values. To return data to the calling hook, we need to look at filters.

Filters

A filter allows us to modify data before it is processed for display in a browser. To this end, a filter accepts variable(s), modifies the passed value(s), and returns data for further processing.

WordPress checks for and executes all registered filters before preparing content for browsers. This way, we can manipulate data before sending it to the browser or database, as appropriate.

One of my clients personalizes the products he sells by imprinting them with images that customers provide. This client uses the WooCommerce plugin to manage e-commerce. WooCommerce does not support this functionality out of the box. Therefore, I added two bits of code to my client’s functions.php:

  1. woocommerce_checkout_cart_item_quantity, listed in the WooCommerce documentation, is a filter hook that allows customers to add external elements to their carts, prior to checkout.
  2. my_customer_image_data_in_cart is a filter that we will write ourselves and use to trigger woocommerce_checkout_cart_item_quantity whenever WooCommerce prepares a cart for display.

Using the following template, we can add add our filter and modify the cart’s default behavior:

add_filter( 'woocommerce_checkout_cart_item_quantity', 'my_customer_image_data_in_cart', 1, 3 );

function my_customer_image_data_in_cart( $html, $cart_item, $cart_item_key ) {
  if ( !empty( $cart_item['images_data'] ) ) {
    // Store image
    // Get image URL
    // Modify $html
  }
  return $html;
}

We add filters the same way we add actions. Filters work similarly to actions, including how priorities are processed. The major difference between filters and actions is that an action will not return data to the calling hook but a filter will.

Customized Action Hooks and Filter Hooks

Writing custom action hooks does not extend Wordpress Core but merely creates new trigger points within our own code.

Creating Custom Action Hooks

Adding a custom hook in our theme or plugin allows other developers to extend functionality without modifying our code base. To add a custom hook, use the same technique the WordPress Core code base itself uses: At our desired trigger point, we simply call do_action with the name of our new hook, optionally adding as many arguments as our callbacks might find useful:

do_action( 'myorg_hello_action', $arg1, $arg2 );

This code simply runs any callback functions that have been hooked onto our custom hook. Note that the namespace is global so, as suggested previously, it would be a good idea to preface our custom hook names with a shortened form of the name of our organization (and possibly our project, as well), hence the myorg_ here.

Now that we’ve defined myorg_hello_action, it’s available for developers to hook into the exact same way we covered earlier for built-in hooks: Define a function, then call add_action().

Unless we want to use a new hook purely internally—it’s a helpful way to structure our code, after all—we’ll have to communicate its availability downstream, to other members of our team or to external users of our plugin, via clear documentation.

Creating Custom Filter Hooks

WordPress’ pattern for custom filter hooks is the same as that of action hooks, except that we call apply_filters() instead of do_action().

Let’s run through a more concrete example this time. Suppose our plugin creates a sidebar menu, which normally consists of four items. We’ll add a custom filter hook so that we (and downstream developers) can modify that list of items elsewhere:

// Text labels of sidebar menu
$sidebar_menu = array( "Page One", "Page Two", "Page Three", "Page Four" );
$sidebar_menu = apply_filters( 'myorg_sidebar_menu', $sidebar_menu );

That’s it—our custom filter hook myorg_sidebar_menu is now ready to use in a plugin that may be loaded later or elsewhere in this one. This allows anyone writing downstream code to customize our sidebar.

We or other developers will follow the same pattern when using a built-in WordPress hook. In other words, we’ll start by defining some callback functions that return a modified version of the data they’re passed:

function lowercase_sidebar_menu( $menu ) {
    $menu = array_map( 'strtolower', $menu );
    return $menu;
}

function add_donate_item( $menu ) {
    $menu = array_push( $menu, 'Donate' );
    return $menu;
}

As with our earlier examples, we’re now ready to hook our filter callback functions to our custom hook:

add_filter( 'myorg_sidebar_menu', 'add_donate_item', 100 );
add_filter( 'myorg_sidebar_menu', 'lowercase_sidebar_menu' );

With that, we’ve hooked our two example callback functions onto our custom filter hook. Both now modify the original content of $the_sidebar_menu. Because we gave a higher priority value to add_donate_item, it runs later, after lowercase_sidebar_menu executes.

Three panels depicting the results of the filter functions described in this section. Panel 1 shows the sidebar as it would be were no callback hooked into the filter. Panel 2 shows the sidebar as it would be were the lowercase_sidebar_menu callback hooked into the filter, with all four item names in lowercase. Panel 3 shows the sidebar as it would be were the donate_button callback also hooked into the filter---the same lowercase items as in Panel 2 plus a fifth item, "Donate," left in title case.

Downstream developers are always free to hook more callback functions to myorg_sidebar_menu. As they do, they can use the priority parameter to make their hooks run before, after, or between our two example callback functions.

The Sky’s the Limit With Actions and Filters

With actions, filters, and hooks WordPress functionality can grow by leaps and bounds. We can develop custom features for our site, leaving our own contributions just as extensible as WordPress. Hooks let us adhere to safety and best practices as we take our WordPress site to the next level.

The Toptal Engineering Blog extends its gratitude to Fahad Murtaza for his expertise, beta testing, and technical review of this article.

Understanding the basics

A hook is a point in WordPress Core, a theme, or a plugin where developers can register custom functions to run. WordPress includes many predefined hooks. Using an action or a filter, you may connect to one of them or create your own hooks.

An action is a function that attaches to a hook, extending the functionality of WordPress. An action runs when an event occurs in WordPress. Unlike a filter, an action can perform database operations, such as creating, reading, updating, or deleting data. An action’s callback function does not return anything to the calling action hook.

A filter is a function that attaches to a hook, extending the functionality of WordPress. A filter runs when an event occurs in WordPress. Filters allow us to modify data before it is processed for display in a browser. A filter accepts variables, modifies the passed value, and returns data for further processing.

Comments

MD Yusuf
Hi Tahir, I am really grateful to be able to benefit from your knowledge
comments powered by Disqus