Back to blog
Guideswordpress · hooks

WordPress Hooks 2026: complete developer guide — actions, filters, creation, best practices

Complete guide to WordPress hooks 2026: actions and filters, creating custom hooks, best practices, real-world examples for developers. The ultimate extension system.

Volade teamJune 8, 20267 min read
0 views0 comments0 reviews0 shares
Share on
WordPress Hooks 2026 — Complete guide actions, filters, creation

Hooks are the nervous system of WordPress. Without them, extending the CMS would require modifying the source code directly. Actions and filters are what make WordPress an extensible platform rather than just software.

Yet many developers use hooks without truly understanding their mechanics: priority, accepted arguments, conditional hooks, creating custom hooks for your own plugins and themes.

This guide is designed for developers who want to go from "I use hooks" to "I master the hook system." We cover theory, practice, pitfalls, and advanced patterns — with concrete examples from our work at Volade.

Actions vs Filters: the fundamental difference

Actions

An action is an execution point in WordPress code where you can add your own code. The action returns nothing — it executes.

// Basic hook
add_action('wp_head', 'my_meta_function');
function my_meta_function() {
    echo '<meta name="author" content="Volade">';
}

// Action with parameters
add_action('save_post', 'my_save_log', 10, 3);
function my_save_log($post_id, $post, $update) {
    error_log("Post $post_id saved. Update: " . ($update ? 'yes' : 'no'));
}

Filters

A filter receives a value, modifies it, and returns it. It should never cause side effects (echo, redirect, DB insert).

// Basic filter
add_filter('excerpt_length', 'my_excerpt_length');
function my_excerpt_length($length) {
    return 30; // 30 words instead of 55
}

// Filter with additional parameters
add_filter('the_content', 'my_add_cta', 20, 1);
function my_add_cta($content) {
    if (is_single()) {
        $cta = '<p><a href="/contact" class="cta">Contact us</a></p>';
        $content .= $cta;
    }
    return $content;
}

Comparison table

CriteriaActionFilter
PurposeExecute codeModify a value
Return valueNothing (void)The modified value
Hook functiondo_action()apply_filters()
Hook registrationadd_action()add_filter()
Main parameterHook nameThe value to filter
Side effects allowedYesNo (ideally)

Priority and parameters

Understanding priority

Priority (3rd argument) determines the execution order of hooks attached to the same point. Lower numbers execute first.

// Execution order: C (5) → A (10) → B (10) → D (100)
add_action('init', 'function_a', 10);
add_action('init', 'function_b', 10);
add_action('init', 'function_c', 5);
add_action('init', 'function_d', 100);

:::callout-warning

Don't put all your functions at priority 10 by default. If three plugins hook into wp_enqueue_scripts at priority 10, the order is undefined. Use 5, 10, 20, 100 to leave room for others.

:::

Managing parameters

The 4th argument of add_action / add_filter is the number of parameters your function accepts.

// The save_post hook passes 3 parameters
add_action('save_post', 'my_handler', 10, 3);

function my_handler($post_id, $post, $update) {
    // $post_id: ID of the saved post
    // $post: complete WP_Post object
    // $update: boolean (true = update, false = creation)
}

If you don't specify accepted_args, WordPress only passes one parameter — the rest are silently ignored.

Common mistakeSymptomSolution
accepted_args not setFunction receives less data than expectedAlways specify accepted_args when > 1
Identical priorityRandom execution order between pluginsSpace priorities (5, 10, 20, 50, 100)
Parameter not passed by referenceChanges not persistedUse &$var if the hook allows it

Creating your own hooks

When to create a custom hook

  • Your plugin or theme needs to be extensible by other developers
  • You want to allow modifying values without touching your code
  • You have strategic points in your code where others might need to intervene

Creating a custom action

// In your plugin
function volade_send_welcome_email($user_id) {
    $user = get_userdata($user_id);
    // Send email...

    // Allow other plugins to hook after sending
    do_action('volade_after_welcome_email', $user_id, $user);
}

// In another plugin or theme
add_action('volade_after_welcome_email', 'my_add_email_log', 10, 2);
function my_add_email_log($user_id, $user) {
    error_log("Welcome email sent to {$user->user_email}");
}

Creating a custom filter

// In your plugin
function volade_product_price($product_id) {
    $price = get_post_meta($product_id, 'price', true);

    // Allow extensions to modify the price
    return apply_filters('volade_product_price', $price, $product_id);
}

// In an extension
add_filter('volade_product_price', 'my_apply_discount', 10, 2);
function my_apply_discount($price, $product_id) {
    if (has_term('sale', 'product_tag', $product_id)) {
        return $price * 0.9; // -10%
    }
    return $price;
}

Naming conventions

ConventionExampleUsage
{plugin}_{action}_volade_before_email_sendActions
{plugin}_filter_{name}volade_filter_priceFilters (alternative)
{plugin}_{resource}_{action}volade_order_before_paymentSpecific actions
{plugin}_{resource}_filtervolade_order_filter_totalSpecific filters

Conditional hooks

Run a hook only in certain contexts

// Conditional hook based on capability
if (current_user_can('manage_options')) {
    add_action('admin_bar_menu', 'my_add_admin_bar_link', 100);
}

// Conditional hook based on post type
if (get_post_type() === 'product') {
    add_filter('the_content', 'my_product_cta');
}

// Conditional hook based on a specific page
add_action('template_redirect', function() {
    if (is_page('contact')) {
        add_action('wp_footer', 'my_contact_tracking_script');
    }
});

Native WordPress conditional hooks

HookTrigger
initWordPress loaded, before any headers
wp_loadedAll WordPress requests handled
template_redirectBefore template selection
wp_enqueue_scriptsLoading scripts and styles
admin_enqueue_scriptsLoading admin scripts
save_postSaving a post
wp_loginUser login
wp_logoutUser logout
user_registerNew user created
deleted_postPost deleted

Real-world hook examples

Example 1: Add a custom admin column

// Add the column
add_filter('manage_posts_columns', 'add_excerpt_column');
function add_excerpt_column($columns) {
    $columns['excerpt'] = 'Excerpt';
    return $columns;
}

// Display column content
add_action('manage_posts_custom_column', 'display_excerpt_column', 10, 2);
function display_excerpt_column($column_name, $post_id) {
    if ($column_name === 'excerpt') {
        echo wp_trim_words(get_the_excerpt($post_id), 20);
    }
}

Example 2: Modify the main query

add_action('pre_get_posts', 'modify_archive_query');
function modify_archive_query($query) {
    if (!is_admin() && $query->is_main_query() && is_post_type_archive('product')) {
        $query->set('posts_per_page', 24);
        $query->set('orderby', 'meta_value_num');
        $query->set('meta_key', 'price');
        $query->set('order', 'ASC');
    }
}

Example 3: Custom login redirect

add_filter('login_redirect', 'redirect_after_login', 10, 3);
function redirect_after_login($redirect_to, $request, $user) {
    if (isset($user->roles) && in_array('subscriber', $user->roles)) {
        return home_url('/my-account/');
    }
    return $redirect_to;
}

Best practices

Well-written hook checklist

  • Unique hook name prefixed with your namespace
  • Explicit priority spaced from others
  • accepted_args always specified when > 1
  • Documentation of the hook (parameters, return type, example)
  • Hook fires at the right moment in the WordPress lifecycle
  • In a filter: always return the modified value (never null unless explicit)
  • In an action: no return value used by the caller

Anti-patterns to avoid

// AVOID: filter with side effects
add_filter('the_title', 'bad_filter');
function bad_filter($title) {
    update_post_meta(get_the_ID(), 'last_access', time()); // SIDE EFFECT
    return $title;
}

// AVOID: overly broad priority
add_action('init', 'generic_function', 10); // 10,000 plugins on init at 10
add_action('init', 'important_function', 10); // random order

// AVOID: missing parameters
add_action('save_post', 'my_handler'); // receives 1 parameter, not 3

What we take away

WordPress's hook system is both simple and deep. Actions and filters share the same mechanics but serve distinct purposes.

Master priorities: don't leave execution order to chance. Space your priorities (5, 10, 20, 50, 100) to leave room for other extensions.

Always specify accepted_args: it's the #1 source of silent bugs in hooks. If you expect 3 parameters, write add_action('hook', 'func', 10, 3).

Create custom hooks: this is what separates a closed plugin from an extensible one. do_action('my_prefix_event') and apply_filters('my_prefix_filter', $value) are your best tools.

Document your hooks: a hook without documentation is a hook nobody will use.


Article updated July 9, 2026. Sources: WordPress Plugin Developer Handbook, Code Reference documentation, Volade experience feedback.

Ready to take action?

Explore the Volade catalog — no account required to get started.

Browse extensionsSee V+ pricing
Free to startNo credit cardWooCommerce-firstMaintained in 2026
Discussion

Your feedback matters

Comment on “WordPress Hooks 2026: complete developer guide — actions, filters, creation, best practices” or rate this article to help the community.

0

people shared this article

Share on

Sources & credits

WordPress documentation, Volade support tickets, and field testing on merchant sites.

#wordpress#hooks#actions#filters#development#plugins#2026

Don't miss a release

WordPress, WooCommerce and TikTok Shop guides — straight to your inbox.