WordPress Actions vs Filters: The Complete Guide with Real Examples

What Are WordPress Hooks?

WordPress hooks are the backbone of every plugin and theme. They let you run your own code at specific points in WordPress’s execution cycle — without touching core files. There are two types: actions and filters. Understanding the difference is the single most important concept in WordPress plugin development.

The short version: an action does something at a point in time. A filter modifies something and returns the modified value. Every hook in WordPress is one or the other.

Actions: Running Code at the Right Moment

An action hook fires at a specific point — when a post is saved, when the page header loads, when a user logs in. You attach your function to it with add_action(). Your function runs, does its work, and returns nothing.

// Basic action: add something to the footer
add_action( 'wp_footer', 'my_plugin_footer_script' );

function my_plugin_footer_script() {
    echo '';
}
PHP

The add_action() function signature:

add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );
PHP

Priority controls execution order when multiple callbacks are attached to the same hook. Lower number = runs earlier. Default is 10. If two callbacks have the same priority, they run in the order they were registered.

// Runs before most other callbacks on init
add_action( 'init', 'my_early_init', 1 );

// Runs after most other callbacks on init
add_action( 'init', 'my_late_init', 99 );

function my_early_init() {
    // fires first
}

function my_late_init() {
    // fires last
}
PHP

Accepting Arguments in Actions

Some action hooks pass data to your callback. You need to declare how many arguments you want to receive using the fourth parameter of add_action():

// The save_post hook passes 3 arguments: $post_id, $post, $update
add_action( 'save_post', 'my_save_post_handler', 10, 3 );

function my_save_post_handler( int $post_id, WP_Post $post, bool $update ) {
    if ( $update ) {
        // Post is being updated, not created
        error_log( 'Post updated: ' . $post->post_title );
    }
}
PHP

Filters: Modifying Data Before It’s Used

A filter hook intercepts a value, lets you modify it, and expects you to return something back. The key rule: always return a value in a filter callback. If you forget to return, the filtered value becomes null and you’ll break things silently.

// Filter: modify the excerpt length
add_filter( 'excerpt_length', 'my_custom_excerpt_length' );

function my_custom_excerpt_length( int $length ): int {
    return 30; // Return 30 words instead of the default 55
}
PHP

The add_filter() function signature is identical to add_action():

add_filter( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );
PHP

Practical Filter Examples

// Modify post content before display
add_filter( 'the_content', 'my_append_signature' );

function my_append_signature( string $content ): string {
    if ( ! is_single() ) {
        return $content; // Don't modify on archive pages
    }
    return $content . '

Written by Kamal Hosen

';
} // Modify the login error message (hide which field is wrong for security) add_filter( 'login_errors', 'my_generic_login_error' ); function my_generic_login_error( string $error ): string { return 'Invalid credentials. Please try again.'; } // Change the number of posts per page on a specific post type archive add_filter( 'pre_get_posts', 'my_portfolio_posts_per_page' ); function my_portfolio_posts_per_page( WP_Query $query ): void { if ( ! is_admin() && $query->is_main_query() && $query->is_post_type_archive( 'portfolio' ) ) { $query->set( 'posts_per_page', 12 ); } // Note: pre_get_posts is an action that receives the query by reference, // but it's also used as a filter pattern — no return needed here }
PHP

Actions vs Filters: Side-by-Side

ActionsFilters
PurposeRun code at a point in timeModify a value before it’s used
Return valueNot required (ignored)Required — always return something
Functionadd_action()add_filter()
Triggered bydo_action()apply_filters()
Common examplesinit, wp_head, save_postthe_content, the_title, excerpt_length
Side effectsExpected (echoing, DB writes)Should be avoided

Removing Hooks

You can remove any action or filter that another plugin or theme registered — as long as you know the exact callback name and priority.

// Remove a named function hook
remove_action( 'wp_head', 'wp_generator' ); // Remove WP version from head
remove_filter( 'the_content', 'wpautop' );  // Remove auto paragraph wrapping

// The priority must match what was used in add_action/add_filter
// Default priority is 10 — if the original used a different priority, match it:
remove_action( 'woocommerce_before_main_content', 'woocommerce_output_content_wrapper', 10 );
PHP

Removing hooks from class methods is slightly different — you need a reference to the object:

// If a plugin registered a hook like this:
// add_action( 'init', [ $this, 'setup' ] );

// You need the object instance to remove it
global $my_plugin_instance;
remove_action( 'init', [ $my_plugin_instance, 'setup' ] );

// Anonymous functions CANNOT be removed — avoid them for public hooks
// This cannot be undone by other code:
add_action( 'init', function() {
    // anonymous — irremovable
} );
PHP

Creating Your Own Hooks

If you’re building a plugin for others to extend, you should add your own hooks. This is what makes plugins like WooCommerce so extensible — they fire hundreds of custom actions and filters that other developers hook into.

// In your plugin — fire a custom action
function my_plugin_process_order( int $order_id ): void {
    // ... do processing ...

    // Let other code run after order processing
    do_action( 'my_plugin_after_order_processed', $order_id );
}

// In your plugin — apply a custom filter
function my_plugin_get_price( float $base_price, int $product_id ): float {
    // Allow other code to modify the price
    return apply_filters( 'my_plugin_product_price', $base_price, $product_id );
}

// Another plugin/theme can now hook into yours:
add_action( 'my_plugin_after_order_processed', function( int $order_id ) {
    // Send a Slack notification, update a CRM, etc.
} );

add_filter( 'my_plugin_product_price', function( float $price, int $product_id ): float {
    return $price * 0.9; // Apply 10% discount
}, 10, 2 );
PHP

Name your hooks with a unique prefix matching your plugin slug — myplugin_ — to avoid collisions with WordPress core or other plugins.

Using Hooks Inside Classes

Most modern plugins use OOP. Registering hooks inside a class is straightforward with [ $this, 'method_name' ]:

class My_Plugin {

    public function __construct() {
        add_action( 'init', [ $this, 'register_post_types' ] );
        add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
        add_filter( 'the_content', [ $this, 'filter_content' ] );
    }

    public function register_post_types(): void {
        // register_post_type() calls here
    }

    public function enqueue_assets(): void {
        wp_enqueue_style( 'my-plugin', plugin_dir_url( __FILE__ ) . 'assets/style.css' );
    }

    public function filter_content( string $content ): string {
        // modify and return
        return $content;
    }
}

// Instantiate once
new My_Plugin();
PHP

For static methods, use the class name string instead of $this:

add_action( 'init', [ 'My_Plugin', 'static_method' ] );
// or shorthand:
add_action( 'init', 'My_Plugin::static_method' );
PHP

Essential Action Hooks Every Developer Should Know

HookWhen it firesCommon use
initAfter WordPress loads, before headers sentRegister CPTs, taxonomies, rewrite rules
wp_enqueue_scriptsFront-end asset loadingEnqueue CSS/JS for themes and plugins
admin_enqueue_scriptsAdmin asset loadingEnqueue admin-only CSS/JS
wp_headInside tagAdd meta tags, inline styles
wp_footerBefore closing Add inline scripts, tracking code
save_postAfter a post is savedSave custom meta, trigger notifications
admin_menuAdmin menu is builtRegister admin pages and subpages
plugins_loadedAll plugins are loadedPlugin compatibility checks
template_redirectBefore template loadsRedirects, access control
wp_ajax_{action}Ajax request (logged in)Handle admin-side AJAX
wp_ajax_nopriv_{action}Ajax request (public)Handle front-end AJAX

Essential Filter Hooks Every Developer Should Know

HookFiltersCommon use
the_contentPost content before displayAppend/prepend content, shortcode processing
the_titlePost titleAdd prefixes, icons, labels
excerpt_lengthExcerpt word countChange default 55-word limit
excerpt_moreExcerpt trailing textChange “[…]” to a Read More link
body_classArray of body CSS classesAdd conditional classes
upload_mimesAllowed upload file typesAdd SVG, custom file types
wp_mailEmail args before sendingChange from address, add CC/BCC
login_redirectURL after loginRole-based login redirects
cron_schedulesWP-Cron intervalsAdd custom cron intervals

Debugging Hooks

When hooks aren’t firing as expected, these techniques help:

// Check if a function is hooked to an action
if ( has_action( 'init', 'my_function' ) ) {
    echo 'my_function is hooked to init';
}

// Get the priority it's registered at
$priority = has_filter( 'the_content', 'my_content_filter' );
// Returns false if not hooked, or the priority integer if it is

// Check what's hooked to a given action (inspect the global)
global $wp_filter;
var_dump( $wp_filter['init'] ); // Shows all callbacks and priorities

// The best approach: install Query Monitor plugin
// It shows all hooks fired on the current request, their callbacks, and priorities
PHP

Common Mistakes to Avoid

  • Forgetting to return in a filter. The most common mistake. If your filter callback doesn’t return, the value becomes null and breaks the output silently.
  • Wrong priority when removing hooks. remove_action() must use the same priority as the original add_action() or it won’t work.
  • Hooking too early. If you call get_post() inside an init callback, the global query isn’t set up yet. Use wp or template_redirect for anything that needs query data.
  • Using anonymous functions for public hooks. You can’t remove an anonymous function later. Use named functions or class methods for any hook other code might need to unhook.
  • Running heavy code on every hook fire. save_post fires multiple times per save (once for the post, once for each revision). Always add a guard: if ( wp_is_post_revision( $post_id ) ) return;

Frequently Asked Questions

What is the difference between an action and a filter in WordPress?

An action runs your code at a specific point in WordPress’s execution and returns nothing. A filter intercepts a value, lets you modify it, and must return something. Use add_action() when you want to do something; use add_filter() when you want to change something.

Can I use add_filter() for action hooks?

Technically yes — WordPress uses the same internal registry for both. But you should use the semantically correct function: add_action() for actions and add_filter() for filters. It makes your code readable and is expected by other developers.

What does hook priority mean in WordPress?

Priority is an integer (default 10) that controls the order callbacks run when multiple functions are attached to the same hook. Lower numbers run first. If two callbacks share the same priority, they run in registration order. Use a higher priority (e.g. 99) to run after core and other plugins, or a lower priority (e.g. 1) to run before them.

How do I find all hooks available in WordPress?

The WordPress Developer Reference lists all core hooks. The Query Monitor plugin shows every hook fired on any given page load, including which callbacks are attached. For WooCommerce hooks specifically, the WooCommerce hooks documentation is the best reference.

When should I create my own hooks in a plugin?

Add your own hooks wherever another developer might reasonably want to extend or modify your plugin’s behaviour — before and after major operations, and around any value that should be customisable. This is what makes your plugin “developer-friendly” and is standard practice for any plugin you plan to sell or distribute.

About Me

Gemini_Generated_Image_6ed8rn6ed8rn6ed8

I’m Kamal, a WordPress developer focused on plugins, APIs, and scalable products.

Learn More