WordPress Actions vs Filters: A Beginner-Friendly Guide to Hooks

feature image

If you have searched for what are actions and filters in WordPress, you may be looking at a snippet that starts with add_action or add_filter and wondering which one belongs on your site. The names are similar, the examples can look intimidating, and one wrong assumption can leave a change invisible or affect more pages than you expected. If a customization ever produces suspicious behavior, website malware removal is a useful recovery next step.

This guide explains hooks, shows how to choose between an action and a filter, and covers the checks that matter before adding custom PHP to a live site. You do not need to know every hook, but you do need to understand the one you use.

TL;DR: An action gives WordPress a moment to run your task; a filter lets you adjust a value before WordPress uses it. Choose by the hook’s contract: actions do work, while filters return the value WordPress should keep. Before testing custom PHP, create a backup of your WordPress site so you have a recovery point.

What are WordPress actions and filters?

Actions and filters are the two main kinds of hooks in WordPress. A hook is a named opening in the structure of a WordPress site, a theme, or a plugin where another function can join the process. It lets you add behavior without editing the original files directly.

The function you attach is called a callback. It is simply the piece of PHP you want WordPress to run. The functions add_action and add_filter register that callback, which means they tell WordPress where and when to use it.

Code Snippets list showing active action and filter examples

The difference is what the hook expects back:

  • An action gives your callback a point in the process where it can do something. It might print a notice, load an asset, save information, or respond after an event. WordPress does not use a replacement value from the callback.
  • A filter gives your callback a value. Your callback can change that value or leave it alone, then it must return the value WordPress should continue using.

That is the beginner-friendly rule, but the hook’s declaration gives you the reliable answer. A developer creates an action point with do_action and creates a filter point with apply_filters:

// This creates a moment for other code to respond.
do_action( 'acme_after_report', $report_id );

// This sends a value through callbacks and uses the result.
$label = apply_filters( 'acme_report_label', $label, $report_id );

In the first example, the report ID gives a callback useful context, but there is no replacement report for the caller to receive. In the second, the label goes into the filter, and the value returned by the callbacks comes back out.

This is the hook’s contract. It tells you when the hook runs, which arguments it passes, and what type of result a filter expects. The hook name by itself is not enough.

Plugins and themes can create their own hooks, too. For an unfamiliar hook, start with its documentation or declaration rather than a copied registration snippet. do_action() and apply_filters() show what WordPress expects; add_action() and add_filter() show how another function attaches to that point.

Actions let you respond to a moment

Use an action when your goal is to run a task at a particular point, such as adding markup or recording information. You connect the callback with add_action; the action tells it when to run. This example adds a notice near the bottom of a public page:

function acme_footer_notice() {
    echo '<p class="acme-footer-notice">Site notice goes here.</p>';
}

add_action( 'wp_footer', 'acme_footer_notice' );
Code Snippets editor showing the wp_footer action callback

When the active theme calls wp_footer, WordPress runs the callback, which prints the notice. This hook passes no arguments. If nothing appears, the theme may omit the hook, or the request may be an admin, AJAX, REST, or other context where the output does not belong.

An action does not mean “anything is safe here.” It only tells you that WordPress reached a particular point. The callback still needs the right timing, permissions, conditions, escaping, and scope.

wp_footer is theme-dependent. If you need to load a stylesheet or script, use the enqueue APIs at the appropriate enqueue hook instead of treating footer output as a general asset loader.

Be careful with save actions

The save_post action is useful when you need to respond after a post is saved. A save can be an edit, autosave, revision, import, REST request, or another workflow. Before changing data or sending a notification, check the post type, request context, capability, and whether the callback could trigger itself again.

The wider lesson is important: an action tells you when something happened, not whether every situation around that event is right for your code.

🛡️ Note: If the callback applies to one post type, save_post_page can reduce unnecessary runs. You still need revision, autosave, capability, and input checks, and must consider other callbacks’ order.

Filters let you adjust a value

Use a WordPress filter when WordPress gives you a value, and you want to change what happens to it next. It might be a title, WordPress URL, array, number, Boolean, object, query setting, or another documented type. The contract tells you what the value represents and where the change applies.

🔬 Note: “Filter” does not mean “string.” Check the reference before applying string functions, and return the documented type.

Here is a filter that adds a prefix to titles in the main public loop:

function acme_prefix_post_title( $title, $post_id ) {
    if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
        return $title;
    }

    return 'Guide: ' . $title;
}

add_filter( 'the_title', 'acme_prefix_post_title', 10, 2 );
Code Snippets editor showing the_title filter callback

The callback receives the title first and the post ID second. Outside the intended public loop it returns the original title; otherwise it returns the prefixed title. The last two registration values are worth understanding:

  • The priority is 10. A lower number runs earlier, and a higher number runs later.
  • The final value says that WordPress should pass two arguments to the callback.

That final setting does not create a post ID. It only tells WordPress how many declared arguments to pass. If the hook supplies one argument, asking for two does not make a second one appear; a callback that requires it can fail. Match the signature and request only what the callback uses.

This title example changes what is displayed during that request. It does not permanently edit the title stored in the database. If you want to change saved content, use the appropriate data-update process rather than asking a display filter to act like a database editor.

Always return the filtered value

Every path through a filter callback should return a compatible value. If you do not change the input, return it unchanged:

function acme_change_label( $label ) {
    if ( ! is_string( $label ) ) {
        return $label;
    }

    return 'Updated: ' . $label;
}

If a filter callback reaches the end without returning, PHP can provide null where WordPress expected the original value. That result may be passed to another callback or to the code that ultimately uses it. Forgetting the return statement is one of the easiest ways to break a filter.

🧪 Note: Match the hook’s format and escape at the final output context. Escaping every filtered value can corrupt HTML, URLs, or text.

Actions vs filters: which should you use?

Use the task you want to perform as your starting point:

What you want to doChooseWhat the callback does
Run a task at a named event or execution pointActionPerforms the task; its return value is ignored
Change a value WordPress will use afterwardFilterReceives, changes or preserves, and returns a compatible value
Live comparison showing a filtered title and an action footer notice

Adding a footer notice is an action because the callback performs output when the footer point is reached. Adding a title prefix is a filter because a title comes in and a title goes back out.

add_action and add_filter use closely related registration machinery, but that does not make them interchangeable. Use the function that matches the hook’s declared contract so the code’s purpose stays clear.

⚙️ Note: add_action() wraps registration code also used by add_filter(). Their contracts still differ: do_action() ignores returns; apply_filters() uses them.

A safer way to add custom hook code

Once you know which hook type you need, check the details that decide whether the snippet behaves well on your site.

1. Check when the hook runs

Find out whether the hook runs on the public site, dashboard, post save, REST or AJAX request, or a scheduled WordPress task. A callback for a public post page may be wrong in the editor or a background process.

Use the narrowest hook that matches the job. Putting unrelated work on a very early or broad hook can make the code run in more places than you intended.

Code Snippets controls showing status, conditions, location, and priority

2. Match the callback arguments

Read the hook reference and note the arguments in order. Make the callback accept only what it needs and set the accepted-argument count to match.

Do not guess from a similar hook. Two hooks with similar names can pass different arguments, pass them in a different order, or expect different value types.

🧰 Note: pre_get_posts passes a mutable WP_Query object by reference. Adjust it with $query->set() and return nothing; target the intended query with $query->is_main_query().

3. Use priority on purpose

Priority controls execution order. The default is 10. Lower numbers run earlier, and callbacks with the same priority normally run in the order they were registered. Priority is not a quality score. Change it only when your callback must run before or after another callback, and document why.

Code Snippets editor showing callback arguments and priority

4. Keep names and callbacks focused

Give functions, variables, and custom hook names a distinctive project or company prefix. A generic name can collide with another theme or plugin. Keep each callback focused on one job. A small callback is easier to understand, test, disable, and remove than one that changes titles, saves metadata, sends mail, and prints markup.

5. Put the code where updates will not erase it

Avoid editing WordPress core, a parent theme, or a third-party plugin directly. An update can replace those files. A small custom plugin is usually better for site behavior that should remain active after a theme change. A child theme fits presentation tied to one parent theme.

This is update-resistant organization, not a guarantee of safe code. A callback can still conflict with another plugin, use the wrong arguments, fail after a hook changes, output at the wrong time, or cause a PHP configuration error. Test changes on a staging site before pushing them live when possible. Before testing custom PHP, use a backup plugin to create a recent restore point and confirm that you know how to restore WordPress from a backup. Keep a way to disable the customization.

Code Snippets General settings for managing snippets safely

6. Remove callbacks with matching details

To stop a customization, use remove_action or remove_filter. The hook name, callback, and priority must match the registration, and removal must happen after the callback is added.

For the title example, the matching removal is:

remove_filter( 'the_title', 'acme_prefix_post_title', 10 );
Code Snippets controls for saving, deactivating, exporting, or trashing a callback

Named callbacks make removal easier. Anonymous functions and methods require the exact callable reference WordPress received, so keep registration details together if you may switch the customization off later.

Final thoughts

When a snippet leaves you unsure which function to use, find the hook declaration. do_action() marks a moment for work; apply_filters() passes a value through callbacks and uses what comes back. Then check timing, arguments, priority, value type, and code location. Hooks reduce update-overwrite risk, but they do not replace backups, staging, access controls, escaping, or compatibility testing.

A WordPress hook is a named point where WordPress, a plugin, or a theme lets another function join its work. Actions run tasks; filters change and return values.
An action runs a task at a named point, and WordPress ignores the callback's return value. A filter receives a value, changes or preserves it, and returns the result WordPress should use.
They register a callback for a named hook. **add_action** connects it to an action; **add_filter** connects it to a filter whose returned value WordPress uses. Both can set priority and accepted arguments.
The result can become null or otherwise invalid. Return the original value when no change is needed, or a compatible replacement when you make a change.
Use a small custom plugin for behavior that should survive a theme change, and a child theme for presentation tied to one parent theme. Keep code outside files updates overwrite, test on staging when possible, and retain a way to disable it.

Akshat is the Founder and CEO of BlogVault, MalCare, and WP Remote. These WordPress plugins, designed for complete website management, allows 100,000+ customers to build and manage high-performance websites with ease.