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 End Admin Pages
muplugins_loaded muplugins_loaded
registered_taxonomy registered_taxonomy
registered_post_type registered_post_type
plugins_loaded plugins_loaded
sanitize_comment_cookies sanitize_comment_cookies
setup_theme setup_theme
load_textdomain load_textdomain
after_setup_theme after_setup_theme
auth_cookie_malformed  
auth_cookie_valid auth_cookie_valid
set_current_user set_current_user
init init
widgets_init * widgets_init *
register_sidebar register_sidebar
wp_register_sidebar_widget wp_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 End Admin 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.

One Comment 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!

Share Your Insight

%d bloggers like this: