WordPress Hooks and Filters Order Of Precedence

When building plugins and themes I often need to reference the WordPress order of precedence of hooks.   This helps ensure various components are loaded only when needed and at the right time.   The base list I reference is the old Codex Plugin API/Action Reference page.   Its sister resource, the Codex Plugin API/Filter Reference is also useful.

The only problem I have with those resources is when I need to determine what will fire on the front-end, backend (admin only) , AJAX, and Cron.    This is my cheat sheet to help sort out the first two (front-end v. admin processing).

WordPress Action Hooks

Stage 1 : Loading

Front EndAdmin Pages
muplugins_loadedmuplugins_loaded
registered_taxonomyregistered_taxonomy
registered_post_typeregistered_post_type
plugins_loadedplugins_loaded
sanitize_comment_cookiessanitize_comment_cookies
setup_themesetup_theme
load_textdomainload_textdomain
after_setup_themeafter_setup_theme
auth_cookie_malformed 
auth_cookie_validauth_cookie_valid
set_current_userset_current_user
initinit
widgets_init *widgets_init *
register_sidebarregister_sidebar
wp_register_sidebar_widgetwp_register_sidebar_widget
wp_default_scripts wp_default_scripts
wp_default_styles wp_default_styles
admin_bar_init admin_bar_init
add_admin_bar_menus add_admin_bar_menus
wp_loaded wp_loaded

Notes

widgets_init is fired by hooking the init action.  It fires at priority 1.

Stage 2 : Processing

After wp_loaded is called, this is where the front end and admin processes diverge.

Front EndAdmin Pages
parse_request
  rest_api_init *conditionally
 auth_redirect
send_headers _admin_menu
parse_query  admin_menu
pre_get_posts  admin_init
posts_selection current_screen
 load-(page)
 send_headers
 pre_get_posts
 posts_selection
wp  wp
template_redirect 
get_header 
wp_enqueue_scripts admin_enqueue_scripts
wp_head 
  admin_print_styles-(hookname)
  admin_print_styles
  admin_print_scripts-(hookname)
  admin_print_scripts
wp_print_scripts  wp_print_scripts
  admin_head-(hookname)
  admin_head
  adminmenu
 in_admin_header
get_search_form admin_notices
 all_admin_notices
loop_start restrict_manage_posts
the_post the_post
get_template_part_content  pre_user_query
loop_end  
get_sidebar  
dynamic_sidebar  
get_search_form  
pre_get_comments  
wp_meta  
get_footer   in_admin_footer
get_sidebar  
wp_footer  admin_footer
wp_print_footer_scripts  
admin_bar_menu  admin_bar_menu
wp_before_admin_bar_render wp_before_admin_bar_render
wp_after_admin_bar_render wp_after_admin_bar_render
  admin_print_footer_scripts
  admin_footer-(hookname)
shutdown shutdown
 wp_dashboard_setup

 

Starting Up A Plugin

I use PHP autoloading to eliminate require and include statements throughout the code.   It requires a consistent directory structure and file naming convention.  If you follow PHP best practices this should not be an issue.  I also recommend naming your PHP files after the class they contain and keeping every class in its own file.

Here is an example from my latest side project that sets up the autoloader and loads up the bulk of the plugin code when the WordPress plugins_loaded hook is fired.   It also exits the plugin early if a WordPress heartbeat comes in since this plugin, like 99% of those out there, has no need to listen to the heartbeat and load up all the overhead.

<?php /* Plugin Name: Geo Census Plugin URI: https://www.storelocatorplus.com/ Description: United States geography intelligence plugin. Author: Store Locator Plus Author URI: https://www.storelocatorplus.com License: GPL3 Tested up to: 4.8.2 Version: 0.1 ---> Change GEO_CENSUS_VERSION define as well.

Text Domain: geo-census
Domain Path: /languages/
*/

if ( defined( 'DOING_AJAX' ) && DOING_AJAX && ! empty( $_POST[ 'action' ] ) && ( $_POST[ 'action' ] === 'heartbeat' ) ) {
 return;
}

/**
 * Autoload our classes.
 *
 * @param string $class_name
 */
function GeoCensus_autoload( $class_name ) {
 if ( strpos( $class_name , 'GeoCensus_' ) === false ) {
 return;
 }

 // Set submodule and file name.
 //
 preg_match( "/GeoCensus_([a-zA-Z]*)_/" , $class_name , $matches );
 $file_name = GEO_CENSUS_DIR . 'include/module/' . ( isset( $matches[ 1 ] ) ? strtolower( $matches[ 1 ] ) . '/' : '' ) . $class_name . '.php';

 // If the include/module/submodule/class.php file exists, load it.
 //
 if ( is_readable( $file_name ) ) {
 require_once( $file_name );
 }
}
spl_autoload_register( 'GeoCensus_autoload' );


/**
 * Start this up when the plugins are loaded in WP.
 */
function GeoCensus_loader() {
 defined( 'GEO_CENSUS_VERSION' ) || define( 'GEO_CENSUS_VERSION' , '0.1' );
 defined( 'GEO_CENSUS_FILE' ) || define( 'GEO_CENSUS_FILE' , __FILE__ );
 defined( 'GEO_CENSUS_DIR' ) || define( 'GEO_CENSUS_DIR' , plugin_dir_path( __FILE__ ) );
 require_once( 'include/base/GeoCensus_Object.php' );

 $GLOBALS[ 'geo_census' ] = new GeoCensus_Plugin();
}
add_action( 'plugins_loaded' , 'GeoCensus_loader' );

Loading Code As Needed

Rather than load up a pile of code into memory when a plugin loads, I prefer to break down my code into classes that are only loaded as needed.   No need to load up all that admin settings page overhead if we are only rendering a shortcode for a user on the front-end.

I use plugins_loaded to fire up my main plugin class and let the construction of that class manage the logic of when to load up the other modules.

class GeoCensus_Plugin {

    /**
     * GeoCensusPlugin constructor.
     */
    public function __construct() {
        $this->connect_frontend();
        $this->add_hooks_and_filters();
        $this->cron_spawn_bot_babies();
        $this->ajax_spackle();
    }
...

Front End Only Processing

The main plugin loads a class that runs several methods during construction, adding hooks to admin_menu to load up the admin class when on the back end, testing for Cron or AJAX calls, and checking is_admin() and loaded the front end if NOT running an admin page.

  /**
     * Load the front-end only module.
     */
    private function connect_frontend() {
        if ( is_admin() ) return;
        new GeoCensus_FrontEnd();
    }

Admin Only Processing

The admin_menu Hook

I often use the admin_menu hook to load up classes that are only needed on admin pages.   My typical structure is to have a controlling MyClass_Admin object which loads via admin_menu and within that add intelligence to the loading by relegating any code that does not have to run during the admin_menu hook into further subclasses such as MyClass_Admin_Init which is loaded up via a method attached to the ‘admin_init’ hook.

I use this method if I do not need to load up features earlier in the call stack (Stage 1 Loading) such as tweaking the admin_bar.

 /**
     * WordPress Hooks and Filters
     */
    private function add_hooks_and_filters() {
        add_action( 'admin_menu' , function () {
            new GeoCensus_Admin();
            new GeoCensus_AdminorAjax();
        } );       // This will fire after our plugin is activated.
    }

The is_admin() Test

This built-in WordPress function can also be used to check if admin pages are being loaded.   I tend to leave this as a sanity check to ensure we are on an admin page within my objects.  This avoids the overhead of calling and is_admin() test on every single page load, though I’ve not yet tested if this is more or less efficient than tying into the admin_menu hook.

I use this method if I need things to happen earlier in the call stack than admin_menu allows such as modifying the admin menu bar.

AJAX Only Processing

I often load this when the main plugin is loaded, which gets invoked via the plugins_loaded hook.    I typically use a method in the class that is loaded to manage the plugin that does a simple AJAX test:

 /**
     * Stuff we do when processing AJAX requests.
     */
    private function ajax_spackle() {
        if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) return;
        new GeoCensus_AdminorAjax();
    }

Cron Only Processing

Similar to AJAX, Cron can fire at any time.  It too is managed via plugins_loaded with the main plugin class and has a check for DOING_CRON:

 /**
     * Go forth and spawn our cron bot babies.
     */
    private function cron_spawn_bot_babies() {
        if ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) return;

        $cron_mom = GeoCensus_Cron_Manager::get_instance();
        $cron_mom->make_bot_babies();
    }

REST API Process

The REST endpoints need to be configured early in the process. Using the rest_api_init hook is a good place to attach things — but recently I ran into an issue when doing cross-domain REST processing where the endpoints are not connected soon enough.

The REST API invokes rest_api_loaded() via the WP parse_request action hook.

The rest_api_loaded() function lives in rest-api.php. It looks for a query variable named ‘rest_route’ via $GLOBALS[ ‘wp’ ]->query_vars[‘rest_route’]. If set it defines REST_REQUEST as true AND invokes rest_get_server().

Inside the rest_get_server() it will check if the global $wp_rest_server is created, if not it will create it and in the process fire off the rest_api_init hook.

3 thoughts on “WordPress Hooks and Filters Order Of Precedence

  1. One of the most useful lists for WordPress hooks firing sequence as you made this great tabular distinction between backend and frontend. Thank you for the nice tutorial, gets a bookmark!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.