WordPress hooks help you to modify the code, add functionality to a website, and change the way data is rendered on the front end without changing the WordPress core or digging too deep into the structure of the theme or plugins (which, by the way, use hooks a lot, too).
Let’s imagine a WordPress core, along with themes and plugins, as a machine with many interconnected gears and hooks (in a general sense of this word). So, if we want to change or upgrade something in this machine, we can either hang our function on an existing hook or create our custom hook – so it will be executed at the specific point in the overall sequence.
Hooks can also be compared to checkpoints or bus stops, where the bus driver (WordPress) checks at those stops (hooks) if someone needs to be picked up (hooked functions).
What Are WordPress Hooks?
Hooks are special trigger points in WordPress code that let developers run their own functions at specific moments in the execution. They don’t modify core code directly but allow extending or altering functionality cleanly.
There are two types of hooks:
- Action hooks – add_action() – which perform tasks at certain times (e.g., sending an email after a post is published).
- Filter hooks – add_filter() – which modify data (e.g., changing a title before it’s displayed).
You use add_action() or add_filter() to register a custom function to a hook, and WordPress will run it when that hook is triggered. Hooks actually “hook” or “pin” a specific function to a particular location in the template or to a specific event in the WordPress runtime. So, we can write a function and then call the hook somewhere where (and when) we need it several times if required.
These custom hooks are found throughout the PHP files of WordPress core, themes, and plugins.
A WordPress page is generated through a series of function calls and database queries initiated by WordPress core, along with contributions from active plugins and the current theme. These components work together to load and structure page elements, such as HTML, images, scripts, and styles, before the browser renders the final output.
Hooks are built-in extension points within this execution flow. They allow you to run your own code “hooked” at specific points during the page load process without modifying WordPress core files. This is what makes WordPress highly extendable.
For customizations, it’s best to place hook-related code in a child theme’s functions.php or in a custom plugin to avoid losing changes during updates.
Hooks, functions, callbacks, and examples
Hooks are part of the WordPress Plugin API. The official documentation includes a list of core hooks that let you run your code at specific points in the WordPress execution flow.
You use them by registering callback functions via functions like add_action() or add_filter(), depending on the hook type.
Once registered, your callback function will execute when the associated hook is triggered, either by WordPress core or another plugin or theme. This gives you the power to inject functionality, modify data, or override default behavior.
Where a hook is placed in the execution timeline directly affects what you can do with it. And timing impacts behavior greatly.
NOTE
You might be confused with the terminology, because, in everyday WordPress developer talk, people often say “write a hook” even though technically they mean “write a callback function and hook it to a hook.”
Additionally, as I’ve already mentioned, there are numerous hooks in the WordPress core, themes, plugins, and so on. However, if you need to, you can create your own hook (to hook your callback functions there). The best way to describe this is to say “register” or “declare” a hook, but in everyday life, people can also say “write a hook.” 😅
The anatomy hooks and callback functions
Look at the image – you see an example, with structural parts numbered:

- Function – add_action or add_filter: this tells WordPress whether you want to hook into an action or a filter.
- Hook name – the specific hook you want to connect to (e.g., ‘init’, ‘body_class’).
- Callback name – the name of the function that will run when the hook fires.
- Priority – determines the order your function runs compared to others hooked to the same point (default is 10).
- Number of arguments – how many parameters your callback accepts (default is 1).
After this registration, the callback function itself is declared – this is the code that executes when WordPress reaches the hook.
📌 The callback function can be declared either before or after the add_action / add_filter call, thanks to how PHP processes function declarations.
Let’s see an example of the callback hooked to the ‘body_class’.
When WordPress loads a page, it runs through many core files, including ones in wp-includes/ that build your final HTML. One of these files triggers the ‘body_class’ filter, which controls the CSS classes added to your <body> tag.
By default, WordPress includes classes like home, logged-in, or single-post. But you can inject your own by “hooking” into that filter:
add_filter( 'body_class', 'my_body_classes' );
function my_body_classes( $classes ) {
$classes[] = 'has-sidebar';
return $classes;
}
<body <?php body_class(); ?>>Here’s what happens step by step:
- WordPress loads core files from wp-includes/.
- When it’s time to output the <body> tag, the body_class filter is fired.
- Your function (hooked earlier) runs and modifies the $classes array.
- A header.php of the theme has this line:
<body <?php body_class(); ?>>- It prints the updated list of classes in the HTML:
<body class="has-sidebar">- You can now style elements with CSS using your new class (.has-sidebar, in my example).
That’s the beauty of WordPress filters: they let you “catch” a moment in the page-building process and tweak the result, without editing core code.
Let’s see more examples of using different hooks.
Where can I find the default (core) hooks?
💡 Useful links and references:
- WordPress Developer Handbook;
- HookOrder.com;
- Plugin API Action Reference (for actions);
- Plugin API Filter Reference (for filters);
- All WordPress Core hooks.
- Extra: Crocoblock hook reference.
Registering a custom post type by writing a hook
For example, in the list of core hooks (check the links above), we can find the init hook registered deep in the WordPress core, and what we need to know about it is the point in the WordPress render process at which it will fire. And Codex gives us the answer: “Fires after WordPress has finished loading but before any headers are sent.”
So, it’s a perfect candidate to “hang” some functions to this hook, for example, to register a custom post type:
// Register Custom Post Type My_Cool_CPT
function create_my_cool_cpt() {
$args = array(
// your array of args goes here
$labels = array(
// the nested array of labels goes here
);
);
register_post_type( 'mycoolcpt', $args );
}
add_action( 'init', 'create_my_cool_cpt', 0 );
(By the way, if you want to easily generate argument arrays, which I’ve omitted here so as not to clutter up this code snippet, just use one of the online generators like this).
Therefore, the code above performs an action (in this case, adding a custom post type).
There are two logical parts in this piece of code:
- Details about the new custom post type (CPT): a custom function that I named “create_my_cool_cpt,” which determines all the details about this CPT, and a built-in WordPress PHP hook, “register_post_type.”
- The “add_action” built-in WordPress function is used to attach a custom function to a specific action (or, in other words, a hook to hang our custom code on it).
Let’s look closer at the structure of add_action: the function that I want to attach – or callback – (‘create_my_cool_cpt’) is passed as the second parameter, and the action to which I want to attach it (‘init’) is passed as the first parameter. Both of those parameters are required.
The value ‘0’ at the end indicates the priority (0 = the highest priority); this parameter is optional.
Finding all the hooks on your website
There are the most typical action events that are take place when the site loads, so many action functions can be hooked into them:

But to see all the hooks that exist on the website pages (that core, your theme, plugins have, plus custom ones), you can use plugins that show them, e.g., WP Hooks Finder. It looks quite scary when you switch it on, but it gives a lot of important information and even more details on hover:

For more practical examples, check this article on how you can use this tool and hooks to customize WooCommerce pages.
But also, to “play” with them, you can write the simplest action hook (I made the dummy text bold and red to be even more visible):
function my_hi_there_function() {
echo "<p style='color:red;font-weight:800;'>Hi there! I am your hook</p>";
}
add_action( 'generate_after_entry_title', 'my_hi_there_function' );

I’ve hooked it to “generate_after_entry_title” so that the text is displayed after each title. You can insert a useful function and pin it there instead of this text. This echo function is just for showing visually where exactly your code will fire.
Types of WordPress Hooks
As mentioned earlier, there are two types of hooks: actions and filters. Let’s have a closer look at each of them.
Actions (action hooks)
Action hooks are used to perform a specific task during the WordPress runtime – they fire at the moment certain existing WordPress core, theme, or plugin processes are being executed; they do this at a specified point in the process flow and then return to normal. Its callback function doesn’t return anything to the calling action hook – just do the job and “leave.”
These built-in functions are most frequently used with actions:
- add_action attaches a certain callback function to a certain hook or hook name (as in the example in the paragraph above).
Syntax: add_action( $hook, $function_to_add, priority, accepted_args ); - remove_action detaches the callback function that was previously attached to the hook earlier with add_action.
Syntax: remove_action( $hook, $function_to_remove, priority ); - do_action is used for creating custom action hooks; it calls the callback function added to the matching action hook.
Syntax: do_action( $hook_name, $arg ).
Custom action hooks
I talked mostly about functions hooked to already existing hooks in WordPress core. But what if I need a custom hook – in case I can’t find an existing hook that matches my needs? Let’s create it and see how it works.
Let’s say I need to display the text “Look, this is my website’s URL” in various places on the site. Not somewhere where the WordPress core, my theme, or plugins already have hooks, but exactly where/when I want. So, a custom hook is needed.
I will call it “my website link” and first write a callback function “my_link_echo” that will be attached to this hook (to set what will actually happen once my custom hook is executed). So, in functions.php, I will add this:
add_action( 'my_website_link', 'my_link_echo', 10, 1 );
function my_link_echo( $url ){
echo "Look, this is my favorite URL -- <a href= {'$url'}>$url</a>";
}
And now I can register my custom hook where I want:
do_action( 'my_website_link', home_url( '/' ) );As you can see, it passes only one, the “home_url” argument – one is just a default number of arguments. If more than one argument is passed, then this number should be changed and be equal to the required number of arguments.
If I want to see it before the main footer, I will place it here:

If I want to see it somewhere else, I should also insert it there. The “home_url()” has an argument in brackets that is passed there, which means that it can be changed in every place I use it.
For instance, in the footer, I will use the home URL (‘/’), and at the top of each post, I want to use a link to the blog archive, so I will simply change the argument to the blog URL and add do_action in single.php (a template for posts):

If I or someone else later wants to execute any other callback function, they can pin it to my “my_website_link” function, and it will be fired at both places or wherever this hook (starting with do_action();) is called.
If there are many callback functions pinned to the same hook, it’s better to change their priority values from the default value of 10 to the order you want them to fire.
Let’s look at one more example – using a WordPress built-in hook and then adding a custom hook to it.
For starters, what if I want to keep the post called “Logged Only” only for logged-in users, and the rest should be redirected to the home page? This is what I need:
add_action ('template_redirect', 'logged_only');
function logged_only(){
if(is_single('logged-only') && !is_user_logged_in() ) {
wp_redirect(home_url() );
die();
}
}
Here, I use the built-in hook “template_redirect,” which fires before it’s determined which template to load. I added my callback “logged_only” and used the function “wp_redirect().”
What if I want to add add a custom hook that adds information to the log file each time someone attempts to access the Logged Only page? It should write, “Someone just tried to access Logged Only page on” and add the date and time of the attempt. The log will be stored in the access_log.txt file, which I’ve already created.
add_action ('template_redirect', 'logged_only');
function logged_only(){
if(is_single('logged-only') && !is_user_logged_in() ) {
do_action( 'write_to_log', date("F j, Y, g:i a")); //date is an argument here
wp_redirect(home_url() );
die();
}
}
add_action( 'write_to_log', 'log_when_accessed' );
function log_when_accessed($date) {
$access_log = get_stylesheet_directory() . '/access_log.txt';
$message = 'Someone just tried to access Logged Only page on ' . $date;
if( file_exists( $access_log) ) {
$file = fopen( $access_log, 'a' ); //a PHP function where "a" means that message is placed at the end of the file
fwrite($file, $message."\n"); //\n says it will be written from a new line
}
else {
$file = fopen( $access_log, 'w' ); //attempts to create a file if it doesn’t exist
fwrite($file, $message."\n");
}
fclose($file);
}
Filters (filter hooks)
Filter hooks are very similar to action hooks: they fire at a certain moment during the WordPress runtime, they have a similar code structure, etc. But there are at least two differences that should be named:
- Filter hooks always return something: because filters are literally filtering and modifying content, they should return filtered/modified content. So, the callback code always has the word “return.”
- The whole idea of filters is different from the idea of actions. While actions interfere with the entire site rendering process, filters work independently. They are like workers of a theatre backstage who prepare props and costumes without being in the spotlight and reveal the results of their work only when required – while actions can be compared with actors on stage.
These built-in functions are most frequently used with filters:
- add_filter hooks a callback function to a certain filter hook.
Syntax: add_filter( $hook_name, $callback, priority, accepted_args ); - remove_filter detaches a callback function from the certain hook.
Syntax: remove_filter( $hook_name, $callback, priority ); - apply_filters calls a callback attached to the filter hook.
Syntax: apply_filters( $hook_name, $value, $args ).
NOTE
In some cases, both actions and filters can be used to accomplish the same task.
Filter hooks examples
Probably one of the easiest filters is the one that returns the custom excerpt length:
add_filter( 'excerpt_length', 'short_excerpt' );
function short_excerpt ( $length) {
$length = 125;
return $length;
}
Where “excerpt_length” is a default WordPress core hook with a standard value of 55 characters. Using the simple code above, I’ve changed it to 125.
And this is an example for understanding how apply_filters works; it will return a discounted price, which is 10% cheaper in the product_price_filter widget:
function modify_product_price($price) {
return $price * 0.9;
}
$product_price = 100;
$discounted_price = apply_filters('product_price_filter', $product_price);
echo "Original Price: $product_price";
echo "Discounted Price: $discounted_price";
add_filter('product_price_filter', 'modify_product_price');
Custom filter hooks
If you need to modify data at a specific point where no existing filter hook fits, you can create a custom filter hook (similarly to what I’ve discussed earlier for action hooks).
For example, you can set up a WordPress website name, and your theme can display it somewhere, normally, next to the logo. You can create a custom filter to add text to the site name everywhere it’s displayed without changing core or theme files.
add_filter( 'my_custom_site_name_filter', 'append_to_site_name', 10, 1 );
function append_to_site_name( $site_name ) {
return $site_name . ' - Welcome!';
}Then, wherever you want to show the modified site name in your PHP template, insert this:
$site_name = apply_filters( 'my_custom_site_name_filter', get_bloginfo( 'name' ) );echo $site_name;
As a result, if the original site name is “My Cool Website,” after the filter hook, it will become “My Cool Website – Welcome!”
NOTE
You can try a WordPress Hook Generator tool create your hooks, it will save you time if you are not very familiar with PHP.
Common mistakes when using hooks
Even experienced developers sometimes make these common errors when working with WordPress hooks:
- Registering the hook after do_action() or apply_filters().
If add_action() or add_filter() is called after the hook has already fired, your callback won’t run as WordPress doesn’t retroactively apply late hooks.

- Callback function doesn’t exist when the hook runs.
If the callback function hasn’t been defined before the hook fires (especially in dynamically loaded files or with conditional logic), it will throw a fatal error.

- Mismatch in the number of arguments.
If add_filter() or add_action() expects two arguments in the callback, but your function accepts only one (or vice versa), it will cause unexpected behavior or warnings.
Always make sure the number in the hook registration matches the number of parameters in the callback.

- Forgetting to return a value in filters
When using add_filter(), your callback must return the modified (or unmodified) value. If you forget the return, the filter will pass null, breaking functionality (e.g., returning empty body classes or image sizes).

FAQ
For most core files, WordPress uses PHP, which has numerous built-in functions. Additionally, WordPress provides its own functions in the Codex Function Reference.
There are two types of hooks: actions and filters. Actions create an event and execute a function at a particular point of the WordPress runtime. It may or may not pass data; also, they don’t return anything. In contrast, filters modify data used by other functions and always return something.
No, they’re completely different things. Filter hooks are PHP functions that modify data while WordPress runs. “Filters” in the UX (like category or price filters) are interface elements powered by queries, PHP, and JavaScript behind the scenes.
Wrapping Up
Hooks are what make WordPress so flexible and expandable. Understanding how they work is essential for those who want to gain insight into how WordPress operates “under the hood” and customize it.



