Fun with WP Hooks

Deep understanding of WordPress.

What are WP hooks?

The idea with hooks is to provide entry points to modify behaviors, restrict some areas, and filter data on the fly without having to modify WordPress core files.

By using functions such as add_action() and add_filter() developers can access pretty much everything in WordPress, this allows for using WordPress for a very large range of projects.

add_action() / add_filter()

Technically, add_action() returns add_filter(). This is called an alias.

You can use those functions because core developers have added a lot of do_action() and apply_filters() in WordPress core files.

Actions are like JavaScript events. You can run/execute code for specific screens or at a very specific loading time.

Filters are meant to modify data on the fly, for example before inserting data into the database or just before display.


This pretty easy :

    function exclude_category( $query ) {
        if ( $query->is_home() && $query->is_main_query() ) {
            $query->set( 'cat', '-1' );
    add_action( 'pre_get_posts', 'exclude_category' )


This code excludes articles with no category (term id 1 is for the « uncategorized » category) from the posts page.

3 Questions – 3 answers

Which hook fires first?

    function _the_init_bis() {
        // exécuter du code ici
    add_action( 'init', '_the_init_bis' );
    function _the_init() {
        // exécuter du code ici
    add_action( 'init','_the_init' );

First hook or second hook?
The answer is _the_init_bis(). This is not due to WordPress, it comes from PHP. The hook is declared before so it’s executed before.

This is due to these 2 lines :

    add_action( 'init', '_the_init_bis');
    add_action( 'init','_the_init' );

However, WordPress has an internal mechanism that allows you to override the natural PHP execution priority :

    add_action( 'init', '_the_init_bis', 10 );
    add_action( 'init','_the_init',9 );

This time _the_init() will fire firsts. 10 is de the default value (you can even skip it in your code), from 0 to 10 you will have highest priority, from 10 to +∞ you will have lower priority.

If you choose 11, this is lower than the default priority 10.

That’s why sometimes you see WordPress developers doing the following in their plugins :

    function _my_callback() {}
    add_action( 'NOM_DU_HOOK', '_my_callback', 999 );

They add a very low priority to « make sure » their code runs after. I use quotes here cause 999 is not the lowest, that would be something like PHP_INT_MAX but that’s a bad practice cause you cannot get something bigger than PHP_INT_MAX so you cannot override it.

Can I use negative priorities?

So we saw that priorities lower than 10 are in fact the highest because of their implementation in the core. What if I use negative priorities?

So let’s say I want my hook to fire before the following hook :

    add_action( 'NOM_DU_HOOK', '_my_callback', 0 );

if I do this :

    add_action( 'NOM_DU_HOOK', '_my_callback', -1 );

Does it work?
The answer is YES! This is neither a flaw nor a bug, this works as intended. The core does not use negative priorities but I guess some developers have used it in their plugins…

What is the accepted args error and how to solve it

What are the accepted args? add_action() or add_filter() accepts 4 arguments at most. The first argument is the hook name, the second is your callback name, the third is the priority and the last argument is the $accepted_arg number :

    add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1)


This $accepted_args is meant to get what you need and nothing more because it’s the core developer’s philosophy. This pretty much the contrary of « the more the merrier ».

In the do_action() and apply_filters() functions it’s possible to pass extra data (additional args). This is particularly useful to allow for fine conditions.

    do_action( 'save_post', $post_ID, $post, $update );

The above code provides useful extra data you can use in your callback. So instead of doing the following :

    function wpdocs_my_save_post( $post_ID, $post, $update ) {
       // execute code everywhere, everytime
    add_action( 'save_post', 'wpdocs_my_save_post', 10, 3 );

You can do :

    function wpdocs_my_save_post( $post_ID, $post, $update ) {
       if ( $update ) {
            // execute code only if updating existing post
    add_action( 'save_post', 'wpdocs_my_save_post', 10, 3 );

But what if I don’t put « 3 » but « 2 » as $accepted_args?

The answer is :

Fatal error: Uncaught ArgumentCountError

Why? Because of the core file wp-includes/class-wp-hook.php line 288 (the current version of WordPress was 4.9.5 at this time).

    $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) );

WordPress slices the array and PHP 7.1 throw a fatal error. Before PHP 7.1 it just triggers a warning.

Wrap up

I hope you have now a deeper understanding of WP Hooks. This post was originally published on

See Also