Menu Search
Jump to the content X X
Smashing Conf Barcelona

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Useful Tips To Get Started With WordPress Hooks

Even though hooks in WordPress are amazing and everyone uses them knowingly or unknowingly, I get the impression that some advanced users and especially front-end developers still seem to avoid them. If you feel like you’ve been holding back on hooks, too, then this article will get you started. I am also going to reveal some interesting details to anyone who thinks they are familiar enough with hooks.

You’ll want to read this article especially if you’d like to:

  • understand code snippets with hooks such as those found in forums;
  • extend WordPress, plugins and themes without breaking updates;
  • learn how to avoid common problems;
  • allow others to extend your code.

My examples are taken from WordPress’ core and from my work as a freelance developer and author of the Advanced Ads plugin.

Hooks in WordPress

Hooks in WordPress are useful and widely used, but not straightforward. But you can extend plugins and themes without breaking them and avoid common maintenance issues along the way.

What Are Hooks? Link

Whenever you want to do something or customize something in WordPress, then there is very likely a hook you can use.

The “do” hook is called an “action.” Wherever an action is defined, you can execute your own code. Here are some examples:

  • send an email to the author once a post is published;
  • load a custom script file in the footer of the page;
  • add instructions above the login form.

The most important thing about action hooks is the position. There are action hooks all over WordPress core that allow you to do your thing.

Further Reading on SmashingMag: Link1

The “customize” hook is called a “filter.” A filter allows you to change or customize a value and return it in a new form. Some examples are:

  • capitalizing a post’s title,
  • attaching links to related posts after the main content,
  • changing an option that is retrieved from the database.

Not only is the position important for a filter, but the given and returned values matter, too. As with actions, you can assume that WordPress has a filter for almost every value it processes.

Elements Of A Hook Routine Link

The hook alone is just a position. Nothing happens here by default. To make it work, you need at least three basic functions, which, combined, I’d like to call a “hook routine.”

Let’s use the wp_head hook as an example for actions and the_content for filters.

Hook (Noun) Link

The hook itself is when and where the magic happens. Imagine this as the kind of hook a climber would put into a mountain face. It has a specific position and if they or other climbers want to move along, they can use it.

Action hooks are placed with do_action(), as in the following:

do_action( 'wp_head' );

Filter hooks use apply_filters(), as follows:

$content = apply_filters( 'the_content', $content );

As you can see, we need to catch the filter’s output, too.

Action and Filter Link

The next element in a hook routine is the action or the filter. This is at least one function that you define to do or filter something. This would be the actual climber who is ready to use any hook to get a bit higher.

An action that is triggered in wp_head is noindex().

function noindex() {
  // If the blog is not public, tell robots to go away.
  if ( '0' == get_option('blog_public') )

This simply checks whether you have disabled the setting for search engine visibility. If so, wp_no_robots() adds the robots meta tag telling search engines not to index your website.

An example of a filter on the_content is wpautop(). It is responsible for wrapping your paragraphs in <p> tags and using <br /> for line breaks. The important part of the plugin is this:

function wpautop( $pee, $br = true ) {
  // …
  return $pee;

Other than an action, a filter function needs at least one argument. This argument is to be returned again later.

Hook (Verb) Link

While our real-life climber knows what they can do with a hook, in a WordPress hook routine we would need to tell them that this very specific hook is meant for them to use. This means, we have to hook (verb) our function into the hook (noun).

This is done using a function that is often also incorrectly referred to as a “hook” (noun). When I’m explaining a hook routine, this is expressed as a verb, but if I need to use a noun, then I would call it a “connection.”

For the wp_head hook and the noindex() action, the connection is made with this line:

add_action( 'wp_head', 'noindex', 1 );

The third parameter is a priority, which we will be covering below.

Hooking wpautop() into the_content is done with this line:

add_filter( 'the_content', 'wpautop' );

These are the basics of a hook routine. We will cover more of them in the following sections.

You’re Already Using These Hooks Link

Actions and filter are hidden deep in the core, but you are just interested in changing your theme, so why should you bother with them?

The truth is that, even if you “just” code themes or occasionally put something in your theme’s functions.php, you are probably already using actions and filters.

Let’s look at the head section of the Twenty Fifteen theme, which can be found in wp-content/themes/twentyfifteen/header.php.

<!DOCTYPE html>
<html <?php language_attributes(); ?> class="no-js">
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width">
<link rel="profile" href="">
<link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>">
<!--[if lt IE 9]>
<script src="<?php echo esc_url( get_template_directory_uri() ); ?>/js/html5.js"></script>
<?php wp_head(); ?>

There is not really a lot between the starting and closing <head> tags, so take a look at the source code in your browser. You will see all kinds of meta elements, including a <title> tag.

The elements you might find missing in the header.php file are added with the wp_head() function. If you have your IDE open (i.e. the software you write code with), and it allows you to jump to the definition of a function, then you should really use that now and check out the contents of wp_head().

* Fire the wp_head action
* @since 1.2.0
function wp_head() {
* Print scripts or data in the head tag on the front end.
* @since 1.5.0
do_action( 'wp_head' );

If you were to remove the comments, then you’d see that the only thing wp_head() is doing is placing the wp_head hook.

Practically, this means that your theme could also just use do_action( 'wp_head' );, instead of wp_head();.

This hook is used by the core already. Here are some connections that WordPress defines by default:

add_action( 'wp_head','_wp_render_title_tag', 1);
add_action( 'wp_head','wp_enqueue_scripts', 1);
add_action( 'wp_head','feed_links', 2);
add_action( 'wp_head','feed_links_extra', 3);
add_action( 'wp_head','rsd_link');
add_action( 'wp_head','wlwmanifest_link');
add_action( 'wp_head','adjacent_posts_rel_link_wp_head', 10, 0 );
add_action( 'wp_head','locale_stylesheet');
add_action( 'wp_head','noindex', 1);
add_action( 'wp_head','print_emoji_detection_script', 7);
add_action( 'wp_head','wp_print_styles', 8);
add_action( 'wp_head','wp_print_head_scripts', 9);
add_action( 'wp_head','wp_generator');
add_action( 'wp_head','rel_canonical');
add_action( 'wp_head','wp_shortlink_wp_head', 10, 0 );
add_action( 'wp_head','wp_site_icon', 99);

Among them is our noindex() action, as well as (since WordPress 4.1) the call to _wp_render_title_tag(), which generates the title tag.

the_content Link

An example of a filter hook that you’ve used multiple times without knowing it is the_content.

It is hidden behind the the_content() function, which is used to output the contents of a post or page. It also contains the call to the the_content hook.

function the_content( $more_link_text = null, $strip_teaser = false) {
$content = get_the_content( $more_link_text, $strip_teaser );

// …
$content = apply_filters( 'the_content', $content );
$content = str_replace( ']]>', ']]&gt;', $content );
echo $content;

When calling the_content() in a clean WordPress installation, a few filters are already hooked to the_content.

add_filter( 'the_content', 'wptexturize' );
add_filter( 'the_content', 'convert_smilies' );
add_filter( 'the_content', 'convert_chars' );
add_filter( 'the_content', 'wpautop' );
add_filter( 'the_content', 'shortcode_unautop' );
add_filter( 'the_content', 'prepend_attachment' );

The default filters take care of the format and some special signs in your text. It is also widely used by many plugins to attach elements such as social-sharing icons or related posts to the end of a post’s main content.

Lessons From Using the_content Link

Even if you do not knowingly use this hook, it is good to learn a bit about it in order to understand common issues. Here are two stories in which incorrect usage prevents features from working properly.

As you can see in the_content(), there is also a call to get_the_content(). I have seen the latter used instead of the former in a couple of themes. This, however, bypasses the hook, and many features, including the post’s default formatting, would not work.

If you hook into the_content, then also be aware that it is used not only on content pages, but also on list pages, such as the archive and home page. I still remember a client contacting me about his front page, which was slow due to a popular plugin that injected a social-sharing widget below each post’s teaser, each of which called back home simultaneously. There was no “off” switch for such behavior.

Parameters In A Hook Routine Link

The most common parameters you’ll need to know about when building or debugging a hook routine are the name of the hook, the name of the function, the priority and the arguments you give to the function.

Needless to say, the devil is in the details.

For your reference, this is what a basic call to add_filter() looks like, including all parameters:

add_filter( $tag, $function, $priority, $accepted_args );

Naming Hooks ($tag) Link

You cannot do anything about the name of a hook that is used in WordPress’ core or in plugins or themes, but keep in mind a few things when creating your own names.

A rule of thumb I follow is that actions should contain the moment of their call, and filters should contain the moment and value that is going to be changed.

wp_head and wp_footer are good examples of actions called according to a position. It is also common to use a prefix or suffix such as before, pre, begin and after and end.

The function wp_delete_post(), which is obviously responsible for deleting a post, includes the following hooks:

  • before_delete_post
  • delete_post
  • deleted_post
  • after_delete_post

When I write a filter, I try to include the filtered value, too. the_content serves as a good example of this.

Hook Name Prefix Link

Besides the position and the value, you should give your hook a prefix that identifies it as your own. Follow these examples:

  • wpseo_ from Yoast SEO,
  • genesis_ from the Genesis framework,
  • advanced_ads_ from my Advanced Ads plugin.

This serves two purposes.

First, it prevent conflicts with hooks in other plugins or themes.

Secondly, if someone uses your hook in a theme or plugin, then third parties will more easily understand where exactly the hook originates.

Dynamic Hook Names Link

Imagine you have a lot of options in your plugin and you want others to be able to use a filter on each of them. You could either call apply_filters() for every option or use the idea of a dynamic hook name, as WordPress core does in the get_options() function.

It contains the following hooks:

  • 'pre_option_' . $option
  • 'default_option_' . $option
  • 'option_' . $option

Knowing this, you could hook into option_blogname or option_blogdescription to change the name or description of the blog dynamically.

This might also be a reason why you can’t find a hook you’ve read about in the source code without shortening the dynamic part.

The Magical “all” Link

The special all tag can be used as a hook name when calling add_action() and add_filter(). It will cause the hooked function to be used for every hook (both actions and filters).

The only time I have ever needed this is when debugging a hook’s whole environment.

Function Names Link

When calling a plain function, add a prefix. This will prevent conflicts with other function helpers that identify it.

In forums, you will often find function names starting with my_. Change that to something that can later be identified as yours.

add_filter( 'the_content', 'bestpluginever_capitalize_all_words' );

Hooking a static function in a class is possible, too:

add_filter( 'the_content', array( 'bestpluginever', 'capitalize_all_words' ) );

Calling it in an instance of this class would be as follows:

add_filter( 'the_content', array( $bestpluginever, 'capitalize_all_words' ) );

You can also call a method in the instance of the same class, like so:

add_filter( 'the_content', array( $this, 'capitalize_all_words' ) );

Make sure that the functions are public as well.

Use Default PHP Functions, Too Link

Sometimes, creating your own function is not needed.

This hook routine capitalizes the first letter of each word in the post’s title:

function bestpluginever_capitalize_title( $title ){
  return ucwords( $title );
add_filter( 'the_title', 'bestpluginever_capitalize_title' );

Because ucwords() is a normal PHP function that receives and returns a value like any other filter function, you can also shorten the four lines to this:

add_filter( 'the_title', 'ucwords' );

Priority Link

The third parameter in add_action() and add_filter() is the priority. It sets the order in which multiple hooked functions are called.

This parameter is optional and will default to 10 if undefined. Multiple functions with the same priority will be called in the order in which they were registered to the hook.

The priority parameter has cost me a lot of nerves. Advanced Ads uses the_content when injecting ads before or after the content. As I mentioned, a lot of other plugins do the same to insert content such as recent or similar posts.

The parameter determines whether the ad or the other content is attached first. To cut down on support requests about the “wrong” order of elements, which has in fact a very specific definition, I ended up letting the user select the priority of the filter as an option.

Making Sure That An Action Has Already Happened Link

To check whether a specific action has already run through, you can use did_action().

You might remember that _wp_render_title_tag was hooked into wp_head. This is an example on how core makes sure that the title tag is really only created there.

function _wp_render_title_tag() {
  if ( ! current_theme_supports( 'title-tag' ) ) {

  // This can only work internally on wp_head.
  if ( ! did_action( 'wp_head' ) && ! doing_action( 'wp_head' ) ) {

  echo '<title>' . wp_title( '|', false, 'right' ) . "</title>\n";

did_action( $hook ) returns the number of times an action has fired, including the current call. It would return 1 if _wp_render_title_tag() is currently being triggered in wp_head. doing_action( 'wp_head' ) makes sure that we are in the wp_head action right now. Practically, the code above is a double-check, which might also be the reason why it will be removed in WordPress 4.4.0.

doing_action() only checks whether an action is currently happening, but the logic behind it is a topic for a whole other article.

Check Whether a Filter Was Already Used Link

There is no alias for did_action() and doing_action() for filters.

You’ll need to make use of has_filter( $hook, $function ) instead. If only $hook is specified, then it will return true if any function is registered for the hook, or false if not.

If $functionis given, it will return the priority of this function, or false if that function is not hooked into the hook.

When injecting ads after a particular paragraph, my Advanced Ads plugin needs to make sure that wpautop() is called before my own filter function is applied.

$wpautop_priority = has_filter( 'the_content', 'wpautop');
if ( $wpautop_priority && $advads_content_injection_priority() < $wpautop_priority ) {
  $content = wpautop( $content );

In this snippet, I check whether the priority of wpautop() is higher than the ad injection’s priority. This would mean that the paragraphs are not set yet, and it forces the plugin to create them first.

Arguments Link

While filter hooks need at least one argument (the value that is filtered), actions don’t need any. However, this doesn’t mean that both can’t have more.

Let’s look at the delete_term hook, which is fired after a term is deleted from the database.

do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term );

When using the add_action() or add_filter() function, the fourth parameter is the number of arguments that your action or filter function is expecting.

In our case, a hooked function can make use of these four arguments, but it doesn’t have to, as you can see from this connection made in core:

add_action( 'delete_term', '_wp_delete_tax_menu_item', 10, 3 );

The action is only interested in the first three arguments and not in $deleted_term.

function _wp_delete_tax_menu_item( $object_id = 0, $tt_id, $taxonomy ) {
  // …

The Number-of-Arguments Trap Link

The stated number of arguments has to match the arguments that the function uses, but it can be left out if it is empty or 1.

I constantly run into the same problem with this argument, because it is not completely optional. If your function needs more arguments, then you’ll have to set them, or else you will get an error message. If you do set them, then you also need to set the priority, which is, in many cases, actually optional.

Start With Return Link

Another tip I want to give you concerns filter functions. If you write complex filters with many conditions, then it might happen that you forget to return a value, which would practically remove it. It has happened more than once in development that my post’s content was empty, until I figured out the reason.

For a while now, I have been starting my filters with the return $value statement at the end, which has reduced problems like this to a minimum.

Removing Actions And Filters Link

Removing an action or a filter from a hook is not uncommon. A popular example was the removal of emojis from WordPress 4.2.

The whole Disable Emojis6 plugin can be boiled down to this code:

function disable_emojis() {
  remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
  remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
  remove_action( 'wp_print_styles', 'print_emoji_styles' );
  remove_action( 'admin_print_styles', 'print_emoji_styles' );  
  remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
  remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );  
  remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
add_action( 'init', 'disable_emojis' );

And there are also plugins that disable the wpautop() function, which could be just one line in your theme’s functions.php.

remove_filter( 'the_content', 'wpautop' );

As you might have guessed from these examples, remove_action() and remove_filter() accept three arguments:

  • hook,
  • function,
  • priority.

They all need to match the values from add_action() and add_filter(). The tricky part here is that the connection will only be removed if it has already been added.

How To Find Hooks? Link

It might be scary to think of the 1957(!) hooks that WordPress currently has, according to Adam Brown’s hook database7, but you don’t have to learn them. You only need to know that, whatever you’d like to change or add, there is probably a hook for it.

To get started, you could just explore the functions that you already use in your existing code, like I did above with wp_head() and the_content().

Another resource is the page for the plugin API8, which lists the important functions and their details.

Debugging Hooks Link

To extensively debug hooks, you could create a list of all hooked actions and filters, as shown by Jean-Baptiste Jung9.

Using error_log() Link

For debugging filters especially in live environments, I like to use error_log() in combination with DEBUG_LOG10 turned on. This will write my output to a debug.log file but not display it in the front end.

function example_content_filter( $content ){
  $option = get_option( 'add_haha' );

  error_log( $option );

  if( $option === true ){
    $content .= 'HAHA<br/>' . $content;
  return $content;
add_filter( 'the_content', 'example_content_filter' );

In the scenario above, I’ve prepended the string HAHA to the content, but only if my option returns true. If this doesn’t work for some reason, I can use error_log( $option ); to debug the option’s value, writing it to the log to check out later, without breaking the content that is visible to visitors in the front end.

Using the Hook API Link

Another way to debug hooks is by using one of the functions mentioned above, like doing_action() or did_action().

One is missing from that list: current_filter() (or current_action()). You can use it to display the name of the current hook, as in this example taken from the Codex:

function my_filter() {
  echo current_filter(); // 'the_content'
add_filter( 'the_content', 'my_filter' );

If you want to see somewhere that core uses this, then check out capital_P_dangit()11. I’ll bet that most of you didn’t know such a filter exists.

Using a Plugin Link

There are also countless plugins for debugging out there, allowing you to see a list of all hooks and hooked functions in the front end. I’ve personally settled on Debug Objects12.

Get Started Link

Learning hooks is like studying law: You don’t need to know all of the statements and paragraphs, just where to find them. You will also never need all of them, but I hope you now understand the solutions posted in forums a bit better. Or you can get started by taking a closer look at any function that you are already using.

Let me know in the comments about your “hook” experience.

(dp, jb, ml, al)

Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9 /2009/08/10-useful-wordpress-hook-hacks/#10-list-all-hooked-functions
  10. 10
  11. 11
  12. 12

↑ Back to top Tweet itShare on Facebook

Thomas fell in love with WordPress as a freelance developer in 2010 and now focuses on his ad management plugin Advanced Ads and website monetization for his client’s and his own project about word-games. Dare to challenge him in German Scrabble.

  1. 1

    Hi there, great article indeed! I remember when I was just a beginner and I just could not understand actions and filters, when to use actions and when to use filters. Today, they feel so natural that I am trying to implement them on other solutions:)

    One of my best implementation of a hook is on a recent project I have done. That was a totally custom eCommerce solution built upon WordPress. I had used actions to add notifications to a user who has bought a product or something happened to the product while the user has wishlisted it. Another action was then called upon that notification to send emails to the administrator and to the user. Filters were used to check if the notification can be sent so that it can be disabled even by the administration and not only by the user in its settings.

    Notifications were used for various things such as products, orders or account types. When something was sold, processed or the account has expired a notification was called and its format was done by calling a dynamic filter / action similar to
    apply_filters("send_notification_" . $item_type . "_" . $action, $item_id, $user_id);

    where $item_type is a product, order or account type and the $action is sold,wishlisted,expired and similar.

    Once you get to know how to use them properly, you can make any plugin/theme easily extendable.

    • 2

      I completely agree that once you got the hang of it, hooks are a great and flexible basis of building new things in WordPress.

  2. 3

    Hooks are one of the best features of WordPress and they can be absolutely powerful when used correctly. There are a couple of plugins that I’ve found helpful because they display the hooks currently used on the page ( There is also which is a database of hooks for WordPress and also for plugins too.

    • 4

      Thanks for the links, I didn’t know, but there are other sites as well. I am missing my own Advanced Ads plugin there, so it doesn’t seem to be updated that often.

    • 5

      Yes, using them wrongly is also known as the Genesis effect.

  3. 6

    Great article. Hooks are the most powerful development feature of WordPress and easily my favorite part of the platform. I wish they were used more often by plugin developers, so often can I find a plugin that handles 90% of the functionality I need, but I need it styled a different way, or I need an additional feature – and the only way to implement my requirement is to fork the plugin! But if more devs used hooks and filters appropriately, forking would almost never be needed.

    • 7

      I have plenty of hooks in my plugins and am always positive about adding new ones. But the reality is that no one ever asked. I don’t know why and if this is normal. So if you didn’t contact them yet, try how the developers responds to such a request.

  4. 8

    This is a really great article, it explains how hooks work and how to use them in a friendly way.

    I’ve just recently discovered the power of hooks in WordPress and I’m still pretty amazed by what they are able to do. They are really powerful!

    I would also like to Shout out to plugins developers after our friend Paul H. here: USE THE HOOKS! I’ve recently worked with one plugin called BuddyPress and unfortunately there aren’t as many hooks as needed. I ended up accessing BP global variables directly as the plugin didn’t allow me to customize as many things as I needed to customize.

    • 9

      Did you contact BuddyPress about it? I would like to know how they would react on requests for new hooks.

  5. 10

    Tabitha M. Waldron

    January 6, 2016 7:34 am


  6. 11


    January 6, 2016 9:15 am

    awesome ….

  7. 12

    This examples are really good, by this hooks we can easily handle the website according to requirements.

  8. 13

    There is no alias for did_action() and doing_action() for filters.

    There is! (almost) – doing_filter(). I think you should also clarify the difference between that and current_filter() – from the codex:

    The function current_filter() only returns the most recent filter or action being executed. did_action() returns true once the action is initially processed.

    This function allows detection for any filter currently being executed (despite not being the most recent filter to fire, in the case of hooks called from hook callbacks) to be verified.

  9. 16
  10. 17

    It’s worth mentioning that the second parameter of `add_action()` function could be an anonymous function: `add_action(‘action_name’, function() { // do sth })`

    The advantages of this solution:
    – the code looks cleaner
    – the code is more readable
    – we’re not polluting the global namespace

    There’s one (?) disadvantage:
    – It’s tricky to remove anonymous function with `remove_action()`

    • 18

      It is definitely worth mentioning here, thank you. I was probably reading the same thread you linked and then decided to leave it out. Maybe mostly because I am biased as a plugin developer who doesn’t want to add functions that can’t be removed. However, I can definitely see a use case for anonymous functions.

  11. 19

    For those interested, and are almost essential to WP development. Debug Objects looks like it shows a lot of information but Query Monitor shows it in an easy to digest UI.

  12. 21

    I hate to ask this question, may I know what theme you are using (is it sublime/atom)? thanks in advance!

  13. 22

    A very good article. Hooks are amazing, can’t even imagine that some front-end developers still seem to avoid them.

  14. 23

    This is such a great write up. Thanks so much—I didn’t know about a number of the debugging tricks. Definitely the first place I’ll refer folks coming to grips with WP Hooks.

    Since you specifically call out front end devs at the start, I thought I’d share the thing that made me (a front ender) personally ‘get’ WP hooks: equating them to javascript events. I get those! “add_action()” ≈ “addEventListener()” ≈ jQuery’s $.on().

    So, first the English translation that applies to both:

    when('this_happens', do_this);

    In WordPressian PHP:

    add_action( 'wp_head','wp_enqueue_scripts');

    In Javascript:

    object.addEventListener('wp_head', wp_enqueue_scripts);

    // More commonly seen as something like:
    object.addEventListener('click', my_callback_function);
    // or
    $('body').on('click', my_callback_function)

    // But both follow the same signature
    when('some_event', some_function);

    I can’t find the original light bulb article but that was the gist. It was perhaps a little clearer whenever I read it because it was still considered good practice to use jQuery’s bind. So add_action and remove_action were the equivalent of bind() and unbind().

    WP Filters are also similar to JS/jQuery’s filter and map: take something, process it, return the processed version.

    And understanding that multiple functions might filter the same original piece of content is the same as understanding jQuery’s chaining: the priority argument ≈ the postion().in().the().chain().

  15. 26

    Great Read!! Really helpful for the WordPress Beginners.


↑ Back to top