Learn more

WordPress Hook Priority and Execution Order

WordPress Hook Priority and Execution Order

WordPress hook order is governed by two runtime controls: the priority set on an add_action or add_filter registration, and the order callbacks with the same priority run. The priority integer and that same-priority order together determine which callback runs, and in what sequence, when a WordPress hook fires. Neither control concerns building a new hook. Both govern timing, the runtime execution order of code already attached to a hook.

For an agency developer inheriting a client build, the practical question is rarely how hooks are wired and almost always which callback runs, and when. Two plugins bind to the same hook, and one quietly overrides the other. A theme’s callback fires before a custom one, or after it, and the rendered result changes with it. That outcome is decided by priority first and registration order second, a mechanism that stays invisible right up until two pieces of code attach to the same hook at the same priority.

Order operates at two scales. On a single hook, the attached callbacks run in a definite sequence set by their priorities. Across a whole request, the action hooks themselves fire in a fixed sequence (plugins_loaded before init, init before template_redirect), so a callback’s real timing depends both on which hook it joins and on where it sits within that hook. Both scales come back to one idea: priority controls execution order.

What Is the Execution Order of Callbacks on One Hook?

The execution order of callbacks on one hook is the exact sequence in which WordPress runs every function attached to that hook, the WordPress hook order resolved down to a single firing. When a WordPress hook fires, it does not call those callbacks at random. The hook order follows two sort keys, applied in strict succession. First, the priority integer sorts the callbacks in ascending order: a callback at priority 5 runs before one at 10, and 10 runs before 20. Second, when two callbacks share the same priority, the tie resolves by occurrence order, whichever registered first runs first.

Priority defaults to 10 when an add_action or add_filter call omits it, so most callbacks land in one middle band and their relative order comes down to registration sequence alone. Lower numbers run earlier, higher numbers run later; a negative priority runs ahead of everything at zero or above. Each add_action registration has its own priority argument, which is why two callbacks bound as WordPress action hooks at priorities 5 and 20 run low number before high, whichever line of code declared them first.

add_action( 'init', 'run_third', 20 );
add_action( 'init', 'run_first', 5 );
add_action( 'init', 'run_second' );      // priority argument omitted → defaults to 10

function run_first()  { error_log( 'first  (priority 5)'  ); }
function run_second() { error_log( 'second (priority 10)' ); }
function run_third()  { error_log( 'third  (priority 20)' ); }

The three registrations appear in source in the order 20, 5, 10, yet the output never follows that source order. It follows priority, ascending:

first  (priority 5)
second (priority 10)
third  (priority 20)

WordPress resolves the run order of callbacks on a single hook by applying two rules in sequence:

  1. The priority integer sorts callbacks in ascending order, from the lowest number to the highest.
  2. Callbacks that share one priority run in registration order, the order in which their add_action or add_filter calls executed during the request.

That second rule is where an inherited site tends to surprise its next maintainer: several plugins register at the default priority 10, so the hooks order among them comes down entirely to which loaded first. When the source of a callback is unclear, every registered callback and its priority sits in the global $wp_filter array, keyed by hook name.

// Which callbacks are attached to 'init', and at what priority?
global $wp_filter;
print_r( $wp_filter['init'] );

This prints every callback bound to init, grouped by priority in the same ascending order WordPress applies at runtime. The callback that fires first becomes directly visible: it is the first entry under the lowest priority key. The global $wp_filter; line is what makes the array reachable from inside a function; without it the variable is out of scope and the dump is empty. Because $wp_filter fills only as registrations run, the dump also has to execute late enough that the callbacks already exist; a dump that runs before anything has bound to init returns nothing.

Execution order decides the run order of callbacks on a single hook. A callback’s absolute timing across the page load rests on one more factor. When its host hook fires relative to every other action hook, the firing sequence of the request itself.

The Firing Sequence of WordPress Action Hooks Across a Request

A WordPress action hook fires at a fixed point in the request, and the firing sequence is the order those points occur in: which hook fires first, which follows, and which runs last as a single request is served. Nothing about that order is incidental. WordPress loads, then runs, then prepares the response, and core action hooks fire at each point so code attached to a hook runs on a schedule the platform guarantees.

That firing sequence spans the whole request lifecycle, from the earliest point WordPress has loaded enough to run plugin code through to the point it prepares what to send back. It sits on a different axis from the priority integer. The firing sequence orders the hooks across the request; the priority integer orders the callbacks registered on any one of those hooks. One fixes when init runs relative to wp_loaded; the other fixes which function bound to init runs first.

Firing order, execution order, and priority are three runtime facets of one mechanism, the WordPress hooks and filters system that lets a plugin or theme run its own code at points WordPress defines. Priority operates inside a single hook. The firing sequence spans them, deciding how the hooks line up as the request moves forward.

A request arrives in one of two shapes, and the ordered set of core hooks differs between them. A public-facing front-end request, a visitor loading a post or an archive, fires one sequence. A wp-admin request (a dashboard screen, a settings page) fires another. The core hooks in each are few, and knowing them is enough to place a callback correctly whenever that callback attaches to one of those core stages, without tracing every hook WordPress fires along the way.

Front-End Firing Order

On a front-end request, the firing order runs through four core action hooks that fire in a fixed progression as WordPress builds the public page. Each fires once. Each marks a stage the request has reached, and a callback registered on the wrong one either runs too early to see what it needs or too late to change anything.

First plugins_loaded fires, once every active plugin has loaded, the earliest safe point for code that depends on another plugin being present. Then init runs, after core, plugins, and the theme are all loaded but before any output is sent; this is the general-purpose registration point where custom post types and taxonomies are declared. wp_loaded follows, signalling that the full environment is up and initialization is complete. Last, template_redirect fires on the front-end just before a template is chosen, the final point at which a request can be redirected or intercepted.

  1. plugins_loaded: every active plugin has finished loading.
  2. init: core, plugins, and theme are ready; the common registration point.
  3. wp_loaded: the full environment is up and initialization is complete.
  4. template_redirect: the front-end request is about to render a template.

That progression belongs to the front-end alone. A request into wp-admin never reaches template_redirect, because it renders no public template; it fires its own, shorter order instead.

Admin Firing Order

On a wp-admin request, the firing order is shorter and distinct from the front-end sequence because the dashboard resolves its own screens rather than a public template. Two core action hooks fire on the admin request, marking the two stages that matter for back-end code targeting.

admin_init fires first, at the start of every admin page load once the admin environment is ready — the admin counterpart to the front-end init, and the place to register settings or run access checks. current_screen follows, after WordPress has resolved which admin screen is being requested, which is what lets a callback act on one specific page rather than every admin load.

  1. admin_init: every admin page load begins here, after the admin environment is ready.
  2. current_screen: WordPress has resolved the specific admin screen being loaded.

Both sequences fix the order the hooks themselves fire. Neither answers a second question that surfaces the moment more than one callback waits on the same hook. Several functions all bound to init, all set to run. What decides which of them runs first is not the firing sequence at all, but a single integer attached to each callback at the moment it registers.

What Is a Hook Callback’s Priority?

A hook callback’s priority is the integer that add_action and add_filter accept as their third argument, and it defaults to 10. This number determines the order in which callbacks attached to a hook run when the hook fires. Priority controls WordPress hook order for a given hook: it decides which registered callback runs first, which follows, and which runs last.

do_action is what puts that order into effect. When do_action fires a hook, WordPress iterates through every callback registered on it and runs them by priority, lowest number first. Lower priorities run earlier; higher priorities run later. The default 10 sits between the two extremes, so a callback left at the default 10 waits behind anything registered at 5 and still runs ahead of anything registered at 20.

The third argument holds the priority, and omitting it is identical to passing 10:

// No priority given: WordPress applies the default, 10.
add_action( 'init', 'load_core_settings' );

// Priority 5 registers the same callback to run earlier.
add_action( 'init', 'load_core_settings', 5 );

The integer maps to timing directly:

Priority valueEffect
Negative (for example -10)Callback runs before the default set
Lower integer (for example 5)Callback runs earlier
10The default, applied when no priority is passed
Higher integer (for example 20)Callback runs later

The same priority integer governs add_filter. On a filter, the priority decides which transformation wins the final value, because filter callbacks run in the same ascending order and each one receives whatever the previous callback returned. The timing model the WordPress filter hooks guide sets out for value transformation. One integer, two registration functions, one ordering rule.

A fourth argument, accepted_args, sits beside the priority and sets how many arguments the prioritized callback receives when the hook fires. It changes what the callback is handed, not when it runs. Priority owns the timing; accepted_args owns the payload.

Because priority is just a number passed at registration, resetting it moves a callback earlier or later relative to every other callback on the same hook. That reset is the mechanism behind reordering, the control that decides when one plugin’s output runs relative to another’s, after it, or ahead of it.

How Does Priority Order Callbacks on a Hook?

Priority orders callbacks on a hook by the integer each one registers with: that priority integer sets where a callback runs among the others, and changing it reorders the callback relative to the rest. Two callbacks bound to the same hook run in ascending priority, the lower number first. Reordering is not a separate mechanism, then. It is the priority argument passed to add_action or add_filter, set to a different number.

Setting that argument is the direct control. A developer who owns the registration changes one value: register a callback with a priority below the default 10 and it moves earlier, register it above 10 and it moves later.

// Runs earlier than default-priority callbacks on init.
add_action( 'init', 'acme_early_setup', 5 );

// Runs later, after the default-priority callbacks have run.
add_action( 'init', 'acme_late_cleanup', 20 );

Reordering a callback the developer does not own takes one more step. A plugin or theme may register a callback at the default priority, and there is no argument to edit inside that foreign code. remove_action removes it; a fresh add_action registers the same function at the chosen priority. The pair moves an existing callback without touching the original file:

// The plugin registered this at priority 10.
remove_action( 'init', 'acme_load_textdomain' );

// Re-register the same callback later, at priority 20.
add_action( 'init', 'acme_load_textdomain', 20 );

For that removal to land, remove_action has to match the hook, the callback name, and, where the original set one, the same priority the callback was added with; a mismatch on any of the three misses silently. Timing is the second constraint: the remove_action call has to run after the plugin registered the callback and before the hook fires. Placed too early, nothing exists to remove yet; placed after the hook has already fired, the removal arrives too late, and either way, it misses with no warning. Once removed and re-added inside that window, the callback carries its new priority and runs in the new position the next time the hook fires.

Priority ordering is not only about the order of visible output. On security-sensitive hooks it decides whether a guard runs in time. An AJAX nonce check registered on a wp_ajax_* action hook has to run early enough to reject an invalid request before later callbacks act on it, which turns its priority into a security decision rather than a preference. WordPress AJAX nonces covers that verification path and why a check’s position on the hook matters. Where a callback sits comes down to the priority integer, and that integer ranges wider than the everyday 5, 10, and 20 suggest.

WordPress Hook Priority Value Limits

A WordPress hook priority value has almost no limit in the ordinary sense: priority accepts any integer, and the only practical bounds are the ones PHP places on integers themselves. No separate list of allowed priorities exists to consult. Any whole number is a valid priority value, and its size alone decides how early or how late the callback runs.

The extremes are what the word limit really points to. A negative priority runs first, before the default 10, before 0, before every positive value on the hook. A very high priority runs last, after everything else registered on the same hook. Between the two sits the default 10, the value WordPress assigns when add_action or add_filter is called without a priority argument. The upper bound is a PHP rule rather than a WordPress one: the largest usable priority is the platform’s maximum integer, PHP_INT_MAX, and no real ordering needs to come near it.

These edge values resolve to predictable run positions:

Priority valueRun position on the hook
Negative (e.g. -100)Runs first, ahead of 0 and the default 10
0Runs before every positive-priority callback
10 (default)The value applied when no priority argument is passed
High (e.g. 999)Runs late, after ordinary callbacks
PHP_INT_MAXThe practical ceiling runs last

None of that range changes how the ordering itself works. The subject reduces to two attributes. Priority is the add_action or add_filter integer, defaulting to 10 and allowing values from negative to the PHP ceiling. Execution order is the run sequence that the integer determines when a hook fires. A developer sets the priority; the execution order follows.

Our related services
More Articles by Topic
Outdated SEO practices are deprecated tactics that once lifted search rankings and now work against them, which is why experienced…
Learn more
Most people watched Google I/O 2026 and came away thinking, sure, more AI in Search, another year of the same…
Learn more
The WordPress REST API create-post operation brings a new post into existence by sending an authenticated POST to the wp/v2/posts…
Learn more

Contact

Feel free to reach out! We are excited to begin our collaboration!

Don't like forms?
Shoot us an email at info@itmonks.com
CEO, Strategic Advisor
Reviewed on Clutch

Send a Project Brief

Fill out and send a form. Our Advisor Team will contact you promptly!