WordPress Hooks – Actions and Filters Complete Guide
Published: April 10, 2026 · Author: Marcin Szewczyk-Wilgan
The WordPress hook system is the architectural foundation that makes the platform extensible. Think of hooks as electrical sockets distributed throughout the WordPress codebase – at specific points during execution, WordPress calls do_action() or apply_filters(), inviting any registered code to run or modify data at that moment. WordPress core contains over 2,500 hooks. WooCommerce adds several hundred more. Every plugin and theme that extends WordPress functionality does so primarily through hooks. Understanding how they work in detail is the single most important technical skill for WordPress development.
Actions vs Filters – The Core Distinction
There are exactly two types of hooks in WordPress. The distinction between them is precise and important:
do_action('hook_name', $arg1, $arg2). Callbacks registered with add_action() execute at that point. Actions do not pass a value and do not need to return one. Examples: sending a notification email when a post is published, enqueuing a script on the frontend, logging a user activity.apply_filters('hook_name', $value, $arg1). Callbacks registered with add_filter() receive the value, optionally modify it, and must return it. The value passes through each registered callback in priority order. The final return value is used by WordPress. Examples: modifying post content before display, changing the excerpt length, filtering search results.null, replacing the original value with null. This is one of the most common and confusing WordPress development mistakes.add_action() and add_filter() Parameters
add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 ): trueadd_filter( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 ): true'my_function'), a static method (['MyClass', 'my_method']), an instance method ([$this, 'my_method'] or [$object, 'method']), or a closure (function() {}). Avoid closures for any callback that might need to be removed later – closures cannot be removed with remove_action() or remove_filter().save_post passes ($post_id, $post, $update). If your callback needs the second or third argument, set $accepted_args accordingly: add_action('save_post', 'my_callback', 10, 3). WordPress passes only as many arguments as this value specifies, regardless of what the hook actually provides.Essential Action Hooks
These are the WordPress core action hooks used in the vast majority of plugins and themes:
After WordPress loads, before output
The most commonly used hook for plugin setup: registering custom post types, taxonomies, shortcodes, and REST API routes. Fires after WordPress has loaded but before any HTML output. Runs on both frontend and admin. Use admin_init for admin-only setup.
Enqueuing frontend assets
The correct hook for registering and enqueuing CSS and JavaScript on the frontend. Never use wp_head to output script tags directly – always use wp_enqueue_scripts with wp_enqueue_script() and wp_enqueue_style() so WordPress can manage dependencies and prevent duplicates.
Post create and update
Fires when a post is created or updated. Passes ($post_id, $post, $update). Always verify the nonce and check wp_is_post_revision() and wp_is_post_autosave() to avoid running on autosave and revision saves. Check user capabilities before modifying anything.
Before template loading
Fires just before WordPress determines which template to load. The correct place to redirect visitors, enforce login requirements, or perform actions based on the current page context. At this point, template tags and conditional functions like is_single(), is_page(), and is_archive() are available.
Essential Filter Hooks
The most frequently used filter hooks in WordPress plugin and theme development:
the_content() call, including in loops and REST API responses. Always return $content.($title, $post_id). Use $accepted_args = 2 to access the post ID. Common use: appending badges, icons, or status labels to titles in specific contexts.excerpt_length filters the word count for auto-generated excerpts (default 55). excerpt_more filters the "read more" string appended after truncated excerpts (default "[…]"). Simple filters for customizing the excerpt presentation globally.$mimes['svg'] = 'image/svg+xml'; return $mimes;. Remove types by unsetting array keys. Note that adding SVG upload support also requires sanitization – never add SVG support without a sanitization plugin like Safe SVG.($redirect_to, $requested_redirect_to, $user). Use to send different user roles to different admin screens or front-end dashboards after login.$schedules['every_5_minutes'] = ['interval' => 300, 'display' => 'Every 5 minutes']; return $schedules;. Required before you can register a recurring cron job at custom intervals.WooCommerce Hooks
WooCommerce adds hundreds of action and filter hooks, providing extension points throughout the store experience. Key hooks for common customizations:
woocommerce_before_shop_loop
Fires before the product loop on shop/archive pages. Use to add content above the product grid: filter UI, promotional banners, category descriptions. The complementary woocommerce_after_shop_loop fires after the grid closes.
loop_shop_per_page
Filters the number of products displayed per page on shop archives. add_filter('loop_shop_per_page', function() { return 24; });. More reliable than changing the WooCommerce setting directly, as it survives setting resets and can be conditional.
woocommerce_product_get_price
Filters the raw price returned by $product->get_price(). Receives ($price, $product). Use for dynamic pricing: role-based discounts, time-limited offers, or external pricing integrations. Always return a numeric string or empty string, not null.
woocommerce_order_status_changed
Fires when an order status changes. Receives ($order_id, $old_status, $new_status, $order). The correct hook for triggering fulfilment actions, sending custom notifications, integrating with external systems, and logging order lifecycle events.
Removing Hooks
Removing a hook added by WordPress core, a theme, or another plugin requires matching three things exactly: the hook name, the callback reference, and the priority.
remove_action('init', 'their_function_name', 10);Works straightforwardly when the callback is a named function. The priority must match exactly what was used in the original
add_action() call. If you are not sure of the priority, use Query Monitor's Hooks panel to inspect registered callbacks.remove_action('init', [$plugin_instance, 'their_method'], 10);You need a reference to the object instance. If the plugin stores it in a global or provides an accessor (common patterns:
Plugin_Name::instance(), $GLOBALS['plugin_var']), retrieve it first. Removing hooks from class instances is more reliable after the instance is created, typically in a later hook like plugins_loaded.Diagnostic Functions
did_action('init') returns the number of times the specified action has fired. Returns 0 if it has not fired yet. Use this to check whether you are calling code after or before a given hook fires – for example, checking whether wp_loaded has fired before calling certain WordPress functions.has_filter('the_content', 'my_function') returns the priority at which the callback is registered, or false if it is not registered. Use to check whether a specific callback is active before attempting to remove it, or to conditionally add a callback only if another is not already registered.if (current_filter() === 'the_excerpt') { /* excerpt-specific logic */ }Custom Hooks and Best Practices
When building plugins or themes, define your own hooks to make your code extensible by others:
do_action('myplugin_after_save', $data), not do_action('after_save', $data). Unprefixed hook names collide with other plugins and core hooks. The prefix makes it immediately clear which plugin defines the hook.do_action() or apply_filters() call. Well-documented hooks are what makes a plugin genuinely developer-friendly.wp_ai_client_before_prompt and wp_ai_client_after_response filters for intercepting AI prompts and responses. The Abilities API exposes hooks for registering and filtering available AI abilities. Real-Time Collaboration adds hooks for collaborator events. Review the WordPress 7.0 developer notes for the complete list.Common Mistakes
Missing return in a filter
A filter callback that performs operations but forgets return $value at the end returns null. This replaces the filtered value with null, which can break anything expecting a string, array, or object. In the the_content filter this wipes the entire post content from the page. Always return in filters.
Side effects in filters
Filters should modify and return their input – they should not send emails, make database writes, or redirect as side effects. These side effects will run wherever the filter fires, including in REST API responses and background processes. Put side effects in action hooks; keep filter callbacks pure.
Using anonymous closures for removable callbacks
Registering callbacks with anonymous closures makes them impossible to remove later: add_action('init', function() { /* ... */ });. Another plugin or theme cannot remove this callback. Use named functions or class methods for any callback that should be removable.
Wrong $accepted_args
A callback on save_post that needs $post_id and $post but uses the default $accepted_args = 1 will only ever receive the post ID. The second argument will always be null. Specify add_action('save_post', 'cb', 10, 2) to receive both arguments.
Summary
The WordPress hook system is elegant in its simplicity: two functions to register (add_action, add_filter), two to trigger (do_action, apply_filters), and four diagnostic functions to inspect state. Everything else is application of these primitives. Mastering hooks means understanding the execution lifecycle well enough to know which hooks are available at each point, what each one passes, and how to use priority to control ordering. The mistakes to avoid are few and consistent: always return in filters, use named callbacks for removability, set $accepted_args when you need multiple arguments. With this foundation, any WordPress feature becomes approachable – because all of WordPress is built on these same mechanisms.
See also: WordPress 7.0 Guide · WordPress Cron · WordPress Technical SEO


