If you have ever wondered how it is possible for a plugin to modify WordPress behavior without touching a single line of its source code, the answer lies in WordPress hooks. Hooks are the extension mechanism upon which the entire ecosystem is built: themes, plugins, and even the core itself constantly use them to insert functionality or alter data at specific points of execution. Understanding them is the boundary that separates those who copy and paste code snippets from those who truly know what they are doing with their website. In this guide, we will see what actions and filters are exactly, how they differ, how to use them with real and functional examples, how to control priorities and arguments, and how to create your own hooks so that your code can also be extensible. All with tested PHP code that you can adapt to your project today.
What WordPress hooks are and why they exist
WordPress was designed from the beginning with a golden rule: never modify the core. If each developer edited the files of wp-includes to change a behavior, each update would erase those changes and maintaining a website would be a nightmare. The solution was to sow the core code with thousands of hook points: specific moments of execution where WordPress stops for an instant and asks «does anyone want to do something here?».
That's a hook: an extension point with its own name. When WordPress loads a page, it executes, in order, hooks like init, wp_loaded, template_redirect o wp_head. Your code can «hook» into any of them by registering a callback function, and WordPress will execute it at that exact moment without you having to touch anything else.
The system rests on two families:
- Actions: execute code at a given moment. They return nothing; they are used to do things (send an email, enqueue a script, register a post type).
- Filters: receive data, modify it, and return it. They are used to transform information (change the text of an excerpt, alter a title, modify a query).
This distinction is the first question you should ask yourself with any hook: am I doing something or am I modifying something?
Actions: execute code at the right time
An action is registered with add_action(), which needs at least two parameters: the hook name and the function you want to execute. Let's look at the classic example, loading a stylesheet correctly:
<?php
function biblioweb_cargar_estilos() {
wp_enqueue_style(
'biblioweb-personalizado',
get_stylesheet_directory_uri() . '/css/personalizado.css',
array(),
'1.0.0'
);
}
add_action( 'wp_enqueue_scripts', 'biblioweb_cargar_estilos' );
Here we tell WordPress: "when it's time to enqueue frontend scripts and styles (wp_enqueue_scripts), execute my function." Another very common example is reacting to an event, such as sending a notice when a post is published:
<?php
function biblioweb_avisar_publicacion( $post_id, $post ) {
// Evitamos revisiones y autoguardados.
if ( wp_is_post_revision( $post_id ) ) {
return;
}
$asunto = 'Nueva entrada publicada: ' . $post->post_title;
$mensaje = 'Se ha publicado una entrada. Revísala aquí: ' . get_permalink( $post_id );
wp_mail( 'editor@tudominio.com', $asunto, $mensaje );
}
add_action( 'publish_post', 'biblioweb_avisar_publicacion', 10, 2 );
Look at the last two parameters of add_action(): the 10 is the priority and the 2 indicates how many arguments our function accepts. We will see them in detail later, because they are the cause of 90% of errors with hooks.
Essential Actions to know
init: WordPress is already loaded; custom post types, taxonomies, and shortcodes are registered here.wp_enqueue_scripts: correct point to enqueue frontend CSS and JS.admin_inityadmin_menu: equivalents for the administration area.wp_headywp_footer: print content in the<head>and before closing the<body>.save_post: fires when saving any post; ideal for processing metadata.wp_loginyuser_register: react to login and user registration.
Filters: modify data before it is used
A filter works the same as an action with a crucial difference: your function receives a value and must return it, modified or not. If you forget the return, the data arrives empty at its destination and you will break something (blank excerpts, missing titles...). It is the most common error when starting with WordPress hooks.
Real example: change the excerpt length and the "read more" text:
<?php
function biblioweb_longitud_extracto( $longitud ) {
return 30; // palabras
}
add_filter( 'excerpt_length', 'biblioweb_longitud_extracto' );
function biblioweb_leer_mas( $more ) {
return '… <a href="' . esc_url( get_permalink() ) . '">Seguir leyendo</a>';
}
add_filter( 'excerpt_more', 'biblioweb_leer_mas' );
Another very useful case: automatically add a notice at the end of each post's content, only on the frontend:
<?php
function biblioweb_nota_final( $content ) {
if ( is_singular( 'post' ) && in_the_loop() && is_main_query() ) {
$nota = '<p class="nota-final">¿Te ha resultado útil? Compártelo o déjanos un comentario.</p>';
return $content . $nota;
}
return $content;
}
add_filter( 'the_content', 'biblioweb_nota_final' );
Observe the conditional checks: the_content is applied in many contexts (feeds, widgets, pages), and filtering without conditions ends up showing your note where it shouldn't. Always return $content at the end, even if you don't modify it, it is mandatory.
Filters you will use again and again
the_contentythe_title: the body and title of posts.excerpt_lengthyexcerpt_more: excerpt control.body_class: adds CSS classes to the<body>.upload_mimes: allows (or blocks) file types in the media library.wp_mail_fromywp_mail_from_name: sender of WordPress emails.login_errors: login error message (useful for not giving clues to attackers).
Priority and arguments: the two numbers that change everything
Both add_action() as add_filter() accept four parameters:
add_filter( 'nombre_del_hook', 'mi_funcion', $prioridad, $num_argumentos );
| Parameter | Default value | What it controls |
|---|---|---|
| Priority | 10 | The order of execution: lower number, executes earlier. With the same priority, the one registered first wins. |
| No. of arguments | 1 | How many hook parameters your callback receives. If the hook passes 3 and you declare 1, you will only receive the first one. |
Priority matters when several plugins touch the same data. If your filter on the_content must run after another plugin has done its thing, use a high priority (e.g. 99). If you need to get ahead of everyone, use 1. The number of arguments, on the other hand, must match what you declare in your function's signature: if you put 10, 3 but your function only accepts one parameter, PHP will throw a fatal error.
To unhook a function there is remove_action() y remove_filter(), with a strict condition: you must pass exactly the same function name and the same priority with which it was registered:
<?php
// Quitar el generador de versión del head (lo añade el core con prioridad por defecto).
remove_action( 'wp_head', 'wp_generator' );
How to create your own custom hooks
Here's the next level: in addition to consuming hooks, you can create them. If you develop a theme or a plugin, seeding your code with do_action() y apply_filters() allows others (or yourself, in the future) to extend it without touching it. This is how WooCommerce works, and that's why its ecosystem is gigantic.
A custom action is created with do_action():
<?php
// En tu plugin, tras procesar un pedido de ejemplo:
function biblioweb_procesar_solicitud( $solicitud_id ) {
// ... lógica principal ...
// Punto de extensión: cualquiera puede engancharse aquí.
do_action( 'biblioweb_solicitud_procesada', $solicitud_id );
}
// Otro desarrollador (u otro archivo) se engancha:
add_action( 'biblioweb_solicitud_procesada', function ( $solicitud_id ) {
error_log( 'Solicitud procesada: ' . $solicitud_id );
} );
And a custom filter, with apply_filters(), which defines the default value and offers it to anyone who wants to change it:
<?php
function biblioweb_obtener_limite() {
$limite = 25;
// Nombre del filtro, valor por defecto y argumentos extra opcionales.
return apply_filters( 'biblioweb_limite_resultados', $limite );
}
// Cualquier otro código puede ajustarlo sin tocar tu función:
add_filter( 'biblioweb_limite_resultados', function ( $limite ) {
return 50;
} );
Two good practices: always prefix your hooks with your project name to avoid collisions, and document what arguments each one passes. An undocumented hook is a hook that no one will use.
Where to place your hook code
You have three reasonable options, from least to most recommended depending on the case:
- The functions.php of a child theme: valid for design-related adjustments (excerpts, body classes). If you change themes, you lose them.
- A snippet plugin: the most convenient option for most. Tools like WPCode, the snippet manager we analyze in depth, allow you to activate and deactivate each snippet separately, with syntax validation that prevents crashing the website due to a semicolon.
- A custom plugin: the correct way for functionality that must survive the theme: custom post types, integrations, business logic.
What you should never do is edit the functions.php of the parent theme or, of course, the core files. If you are starting out and still don't control the essential plugins to get started with WordPress, start there before getting into code.
Common errors when working with hooks
- Forgetting the return in a filter. The typical symptom: disappearing content or titles. Every filter always returns a value.
- Hooking too early. If you call user functions before
initor to template functions beforewp, you will get errors or empty values. Each hook has its moment in the loading cycle. - Not declaring the arguments. Registering
add_action( 'save_post', 'mi_funcion', 10, 3 )with a function that only accepts one parameter causes a fatal error in PHP 8. - Infinite loops. Calling
wp_update_post()inside a callback ofsave_postfires againsave_post. This is avoided by removing the hook before updating and re-adding it afterwards. - Anonymous callbacks that you can't remove later. Anonymous functions are convenient, but
remove_filter()cannot easily remove them. For code that others should be able to deactivate, use named functions.
To explore what hooks exist and what arguments they pass, the official reference of developer.wordpress.org about hooks is the definitive map: it includes the complete list of core actions and filters with their documentation.
Frequently Asked Questions
What is the real difference between an action and a filter?
An action executes code at a point in the flow and returns nothing: it serves to do things. A filter receives data, can modify it, and is obliged to return it: it serves to transform information. Internally both use the same system (in fact add_action() calls add_filter()), but conceptually it is convenient to treat them as distinct tools.
Can I use hooks without knowing how to program?
You can use pre-written snippets by copying them into a snippet plugin like WPCode, which validates the syntax before saving. However, understanding at least what each line does will prevent you from pasting malicious or obsolete code. WordPress hooks are PHP: a minimum base will give you a lot of autonomy.
How many hooks does WordPress have?
The core exceeds 3,000 between actions and filters, and the number grows with each version. Added to these are those registered by your plugins and your theme: WooCommerce, for example, adds hundreds of its own. You don't have to memorize them: just know the usual dozen and know how to find the rest in the official documentation.
Do hooks affect my website's performance?
The system itself is very efficient; what penalizes is what you do inside the callback. A heavy query hooked to init will run on every page load. Hook your code to the most specific hook possible and use conditionals to exit early when it's not time to act.
Conclusion
WordPress hooks are the language in which the core, themes, and plugins communicate. Mastering the actions and filters pair allows you to customize practically any behavior without touching the source code and without fear of updates: enqueue resources, react to events, transform content, and, when you make the leap to development, offer your own extension points with do_action() y apply_filters(). Start by reproducing the examples in this guide in a test environment or using a snippet manager, pay attention to the priorities and the return of the filters, and consult the official documentation when in doubt about what arguments a hook passes. From there, every customization problem you encounter in WordPress will almost always have the same answer: there's a hook for that.