67

In what order add_action hooks execute?

i.e.

init
wp_head
wp_footer
after_theme_setup 
etc...
???
???
???



EDIT:

I've also posted my solution.

1

6 Answers 6

123

"Data! Data! Data!" he cried impatiently. "I can't make bricks without clay."

Sherlock Holmes - The Adventure of the Copper Beeches

So let's gather some real data from a vanilla WordPress 5.7.2 install and the TwentyTwelve theme activated with only a single Text widget.

For the home page, the following do_action / do_action_ref_array calls are made in the following order (logged out):

[0] => mu_plugin_loaded
[1] => muplugins_loaded
[2] => registered_taxonomy
[3] => registered_taxonomy
[4] => registered_taxonomy
[5] => registered_taxonomy
[6] => registered_taxonomy
[7] => registered_post_type
[8] => registered_post_type
[9] => registered_post_type
[10] => registered_post_type
[11] => registered_post_type
[12] => registered_post_type
[13] => registered_post_type
[14] => registered_post_type
[15] => registered_post_type
[16] => registered_post_type
[17] => plugins_loaded
[18] => sanitize_comment_cookies
[19] => wp_roles_init
[20] => setup_theme
[21] => unload_textdomain
[22] => load_textdomain
[23] => after_setup_theme
[24] => load_textdomain
[25] => load_textdomain
[26] => auth_cookie_malformed
[27] => set_current_user
[28] => init
[29] => registered_post_type
[30] => registered_post_type
[31] => registered_post_type
[32] => registered_post_type
[33] => registered_post_type
[34] => registered_post_type
[35] => registered_post_type
[36] => registered_post_type
[37] => registered_post_type
[38] => registered_post_type
[39] => registered_taxonomy
[40] => registered_taxonomy
[41] => registered_taxonomy
[42] => registered_taxonomy
[43] => registered_taxonomy
[44] => widgets_init
[45] => register_sidebar
[46] => register_sidebar
[47] => register_sidebar
[48] => wp_register_sidebar_widget
[49] => wp_register_sidebar_widget
[50] => wp_register_sidebar_widget
[51] => wp_register_sidebar_widget
[52] => wp_register_sidebar_widget
[53] => wp_register_sidebar_widget
[54] => wp_register_sidebar_widget
[55] => wp_register_sidebar_widget
[56] => wp_register_sidebar_widget
[57] => wp_register_sidebar_widget
[58] => wp_register_sidebar_widget
[59] => wp_register_sidebar_widget
[60] => wp_register_sidebar_widget
[61] => wp_register_sidebar_widget
[62] => wp_register_sidebar_widget
[63] => wp_register_sidebar_widget
[64] => wp_default_scripts
[65] => wp_register_sidebar_widget
[66] => wp_register_sidebar_widget
[67] => wp_register_sidebar_widget
[68] => wp_register_sidebar_widget
[69] => wp_register_sidebar_widget
[70] => wp_register_sidebar_widget
[71] => wp_register_sidebar_widget
[72] => wp_register_sidebar_widget
[73] => wp_register_sidebar_widget
[74] => wp_register_sidebar_widget
[75] => wp_register_sidebar_widget
[76] => wp_register_sidebar_widget
[77] => wp_register_sidebar_widget
[78] => wp_register_sidebar_widget
[79] => wp_register_sidebar_widget
[80] => wp_register_sidebar_widget
[81] => wp_register_sidebar_widget
[82] => wp_register_sidebar_widget
[83] => wp_register_sidebar_widget
[84] => wp_register_sidebar_widget
[85] => wp_register_sidebar_widget
[86] => wp_register_sidebar_widget
[87] => wp_register_sidebar_widget
[88] => wp_register_sidebar_widget
[89] => wp_register_sidebar_widget
[90] => wp_register_sidebar_widget
[91] => wp_register_sidebar_widget
[92] => wp_register_sidebar_widget
[93] => wp_register_sidebar_widget
[94] => wp_sitemaps_init
[95] => wp_loaded
[96] => parse_request
[97] => send_headers
[98] => parse_tax_query
[99] => parse_query
[100] => pre_get_posts
[101] => posts_selection
[102] => wp
[103] => template_redirect
[104] => get_header
[105] => wp_head
[106] => wp_enqueue_scripts
[107] => wp_default_styles
[108] => enqueue_block_assets
[109] => wp_print_styles
[110] => wp_print_scripts
[111] => wp_body_open
[112] => parse_tax_query
[113] => parse_query
[114] => pre_get_posts
[115] => parse_tax_query
[116] => posts_selection
[117] => parse_tax_query
[118] => parse_query
[119] => pre_get_posts
[120] => parse_tax_query
[121] => posts_selection
[122] => parse_tax_query
[123] => parse_query
[124] => pre_get_posts
[125] => parse_tax_query
[126] => posts_selection
[127] => parse_tax_query
[128] => parse_query
[129] => pre_get_posts
[130] => parse_tax_query
[131] => posts_selection
[132] => parse_term_query
[133] => pre_get_terms
[134] => loop_start
[135] => the_post
[136] => get_template_part_content
[137] => get_template_part
[138] => parse_comment_query
[139] => pre_get_comments
[140] => parse_comment_query
[141] => pre_get_comments
[142] => comment_form_comments_closed
[143] => loop_end
[144] => get_sidebar
[145] => dynamic_sidebar_before
[146] => dynamic_sidebar
[147] => dynamic_sidebar_after
[148] => get_footer
[149] => twentytwelve_credits
[150] => wp_footer
[151] => wp_print_footer_scripts
[152] => shutdown

using the must-use plugin:

add_action( 'all', function ( $tag ) {
    static $hooks = array();
    // Only do_action / do_action_ref_array hooks.
    if ( did_action( $tag ) ) {
        $hooks[] = $tag;
    }
    if ( 'shutdown' === $tag ) {
        print_r( $hooks );
    }
} );

that prints the collected action hooks, for the current page, in the last available core action (shutdown).

If you want to check the order of actions and how many times each one is fired, then you can use for example:

add_action ( 'shutdown', function(){
    print_r ( $GLOBALS['wp_actions'] );         
} );

or without explicit globals:

add_action ( 'shutdown', function() use ( &$wp_actions ) {
    print_r ( $wp_actions );      
} );

that outputs the array:

[mu_plugin_loaded] => 1
[muplugins_loaded] => 1
[registered_taxonomy] => 10
[registered_post_type] => 20
[plugins_loaded] => 1
[sanitize_comment_cookies] => 1
[wp_roles_init] => 1
[setup_theme] => 1
[unload_textdomain] => 1
[load_textdomain] => 3
[after_setup_theme] => 1
[auth_cookie_malformed] => 1
[set_current_user] => 1
[init] => 1
[widgets_init] => 1
[register_sidebar] => 3
[wp_register_sidebar_widget] => 45
[wp_default_scripts] => 1
[wp_sitemaps_init] => 1
[wp_loaded] => 1
[parse_request] => 1
[send_headers] => 1
[parse_tax_query] => 9
[parse_query] => 5
[pre_get_posts] => 5
[posts_selection] => 5
[wp] => 1
[template_redirect] => 1
[get_header] => 1
[wp_head] => 1
[wp_enqueue_scripts] => 1
[wp_default_styles] => 1
[enqueue_block_assets] => 1
[wp_print_styles] => 1
[wp_print_scripts] => 1
[wp_body_open] => 1
[parse_term_query] => 1
[pre_get_terms] => 1
[loop_start] => 1
[the_post] => 1
[get_template_part_content] => 1
[get_template_part] => 1
[parse_comment_query] => 2
[pre_get_comments] => 2
[comment_form_comments_closed] => 1
[loop_end] => 1
[get_sidebar] => 1
[dynamic_sidebar_before] => 1
[dynamic_sidebar] => 1
[dynamic_sidebar_after] => 1
[get_footer] => 1
[twentytwelve_credits] => 1
[wp_footer] => 1
[wp_print_footer_scripts] => 1
[shutdown] => 1

where we can get the total count with echo array_sum( $GLOBALS['wp_actions'] );

Here's a prettified version:

add_action ( 'shutdown', function() {
    foreach ( $GLOBALS['wp_actions'] as $action => $count ) {
        printf( '%s (%d) <br/>' . PHP_EOL, $action, $count );
    }
} );

or without explicit globals:

add_action ( 'shutdown', function() use ( &$wp_actions ) {
    foreach ( $wp_actions as $action => $count ) {
        printf( '%s (%d) <br/>' . PHP_EOL, $action, $count );
    }
} );

to get the following list:

mu_plugin_loaded (1)
muplugins_loaded (1)
registered_taxonomy (10)
registered_post_type (20)
plugins_loaded (1)
sanitize_comment_cookies (1)
wp_roles_init (1)
setup_theme (1)
unload_textdomain (1)
load_textdomain (3)
after_setup_theme (1)
auth_cookie_malformed (1)
set_current_user (1)
init (1)
widgets_init (1)
register_sidebar (3)
wp_register_sidebar_widget (45)
wp_default_scripts (1)
wp_sitemaps_init (1)
wp_loaded (1)
update_option (1)
update_option__transient_doing_cron (1)
updated_option (1)
set_transient_doing_cron (1)
setted_transient (1)
requests-requests.before_request (1)
requests-curl.before_request (1)
http_api_curl (1)
requests-curl.before_send (1)
requests-curl.after_send (1)
requests-curl.after_request (1)
requests-requests.before_parse (1)
http_api_debug (1)
parse_request (1)
send_headers (1)
parse_tax_query (9)
parse_query (5)
pre_get_posts (5)
posts_selection (5)
wp (1)
template_redirect (1)
get_header (1)
wp_head (1)
wp_enqueue_scripts (1)
wp_default_styles (1)
enqueue_block_assets (1)
wp_print_styles (1)
wp_print_scripts (1)
wp_body_open (1)
parse_term_query (1)
pre_get_terms (1)
loop_start (1)
the_post (1)
get_template_part_content (1)
get_template_part (1)
parse_comment_query (2)
pre_get_comments (2)
comment_form_comments_closed (1)
loop_end (1)
get_sidebar (1)
dynamic_sidebar_before (1)
dynamic_sidebar (1)
dynamic_sidebar_after (1)
get_footer (1)
twentytwelve_credits (1)
wp_footer (1)
wp_print_footer_scripts (1)
shutdown (1)

PS: You should also check out the great Query Monitor plugin by John Blackbourn. (I'm not related to this plugin)

13
  • 1
    Very nice indeed!
    – jdm2112
    Commented Mar 19, 2015 at 19:30
  • 1
    Thank you for mentioning Query Monitor. Seems to be a useful plugin on this case.
    – D.A.H
    Commented Oct 16, 2015 at 9:53
  • 1
    I liked the quote and wanted to see more context. And as I already had the link for myself why not just update here as well. :)
    – kraftner
    Commented Mar 1, 2017 at 14:22
  • 1
    Over 4 yo post and still helpful. Thanks a lot! Commented Feb 5, 2019 at 10:15
  • 1
    Glad to hear it's still useful @Gas
    – birgire
    Commented Sep 29, 2022 at 11:00
28

Here is the WordPress load chart

WordPress Load Chart

Source by @Rarst

3
  • 10
    Add at least the source, or even better: find a duplicate for this question.
    – fuxia
    Commented Sep 29, 2014 at 8:36
  • 3
    Actually I did not know where I got it from. I had this image saved on my PC. Otherwise I would have done that.
    – Robert hue
    Commented Sep 29, 2014 at 8:43
  • It's also published on Tom Mc Farlin homepage: The WordPress Page Lifecycle -> tommcfarlin.com/wordpress-page-lifecycle
    – D.A.H
    Commented Oct 16, 2015 at 10:05
3

Found Solution!

Thanks @birgire for nice answer. I will add to that, muplugins_loaded sometimes is not fired, so I will use plugins_loaded as the most first hook (but at that time, user-authorization is not done yet. If you want to check user's authorization, then init is the earliest for that)...

p.s. there exist excellent plugins:

1) Query Monitor - You can see everything what happens on the page-load, i.e. duration of each executed function and much more(view all screenshots on plugin page):

enter image description here

2) WP-DEBUG-BAR + WP-DEBUG-SLOW-ACTIONS:
a) debug hooks(actions) run list on your site.
b) See duration of each action (not function): enter image description here

2

The basic sequence can also be found on the official docs:

https://codex.wordpress.org/Plugin_API/Action_Reference

2

This is similar to the @birgire's answer, but it provides a nice report that can be displayed for any URL in a WordPress site. You'll need to be logged-in as an Admin level user, then add ?wp-hooks onto the end of a URL you want to test.

/**
* WordPress Hooks Reference
*
* Dump all action and filter hooks at the bottom of any page
* by adding ?wp-hooks onto the end of the URL while logged-in
* as an Administrator level user.
*/
function kevinlearynet_hooks_reference() {

    // Only shown for Administrator level users when ?list-wp-hooks is added to the URL
    $trigger = isset( $_GET['wp-hooks'] ) && current_user_can( 'manage_options' );
    if ( ! $trigger ) return;

    // Capture and sort filters and hooks
    $filters = array_keys( $GLOBALS['wp_filter'] );
    sort( $filters );
    $actions = array_keys( $GLOBALS['wp_actions'] );

    // Output rough template
    ob_start();
    ?>
    <section class="wp-hooks">
    <h1 class="wp-hooks__h1">WordPress Hooks Reference</h1>
    <div class="wp-hooks__lists">
    <div class="wp-hooks__col">
    <h2 class="wp-hooks__h2">Actions</h2>
    <?php foreach ( $actions as $hook ) : ?>
    <p class="wp-hooks__hook"><?php echo $hook; ?></p>
    <?php endforeach; ?>
    </div>
    <div class="wp-hooks__col">
    <h2 class="wp-hooks__h2">Filters</h2>
    <?php foreach ( $filters as $hook ) : ?>
    <p class="wp-hooks__hook"><?php echo $hook; ?></p>
    <?php endforeach; ?>
    </div>
    </div>
    </section>
    <style>
    .wp-hooks {
        padding: 30px;
        margin: 30px;
        border-radius: 4px;
        background: white;
        font-size: 16px;
        line-height: 1.4;
        height: 50vh;
        min-height: 500px;
        overflow-y: scroll;
    }
    .wp-hooks__lists {
        display: flex;
    }
    .wp-hooks__col {
        flex: 1;
        width: 50%;
    }
    .wp-hooks__h1 {
        margin: 0 0 20px;
    }
    .wp-hooks__h2 {
        line-height: 1;
        font-size: 18px;
        margin: 0 0 10px;
    }
    .wp-hooks__hook {
        padding: 0;
        margin: 0;
    }
    </style>
    <?php
    ob_end_flush();
}
add_action( 'shutdown', 'kevinlearynet_hooks_reference' );

The output looks like this:

enter image description here

I wrote about this on my own, so here's the original source for reference. This includes a little more detail on decisions behind sorting and functionality.

1

No two requests are exactly the same. A quick and dirty (but very accurate) way to find out what's going on is to temporarily add a line to the start of the do_action function in wp-includes/plugin.php which logs the $tag, e.g.:

if (isset($some_get_or_post_trigger_var)) file_put_contents(ABSPATH . 'action.log', "$tag\n", FILE_APPEND);

You might want to do the same to apply_filters function in the same file. I haven't found a better way to get the complete chronological sequence.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.