Tiempo de lectura: 5 min
Valencia, 18/09/2026, G.B.
Ya he hablado varias veces por aquí de hooks, actions y filters en WordPress, pero siempre viene bien un ejemplo real y completo en vez de fragmentos sueltos. Así que voy a usar mi propio plugin Mis Plugins como caso práctico: es pequeño, no tiene dependencias externas, y toca casi todos los hooks que se usan en el día a día de un plugin.
Table of Contents
Qué es un hook
Un hook es un punto del código de WordPress donde puedes «engancharte» para ejecutar tu propia función, sin tocar el núcleo. Hay dos tipos:
- Actions: ejecutan tu función en un momento concreto (por ejemplo, cuando se activa un plugin, o cuando se carga el panel de administración). No devuelven nada, solo hacen algo.
- Filters: reciben un valor, tu función lo modifica y lo devuelve. Se usan para transformar datos antes de que WordPress los use.
Mis Plugins usa sobre todo actions, así que empiezo por ahí.
register_activation_hook: al activar el plugin
Este hook se dispara una sola vez, justo cuando activas el plugin desde el panel. Lo uso para crear la tabla propia en la base de datos:
register_activation_hook( __FILE__, 'indaga_mp_activar' );
function indaga_mp_activar() {
global $wpdb;
$table_name = indaga_mp_table_name();
$charset_collate = $wpdb->get_charset_collate();
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$sql = "CREATE TABLE {$table_name} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
fecha DATE NOT NULL,
nombre_plugin VARCHAR(191) NOT NULL,
PRIMARY KEY (id)
) {$charset_collate};";
dbDelta( $sql );
}La función dbDelta es la forma segura de crear o actualizar tablas propias: compara la estructura que le pasas con la que ya existe y hace los cambios necesarios sin borrar datos.
plugins_loaded: comprobar la versión en cada carga
register_activation_hook solo se dispara al activar, así que si subo una versión nueva del plugin con columnas añadidas, necesito otro punto de enganche que se ejecute siempre. Para eso uso plugins_loaded, comparando un número de versión guardado en las opciones:
add_action( 'plugins_loaded', 'indaga_mp_comprobar_version_bd' );
function indaga_mp_comprobar_version_bd() {
if ( get_option( 'indaga_mp_db_version' ) !== INDAGA_MP_DB_VERSION ) {
indaga_mp_activar();
}
}Así, cuando subí la versión 2.0.0 con los campos de autor y tags, la tabla se actualizó sola en cuanto WordPress cargó los plugins, sin que el usuario tuviera que desactivar y reactivar nada.
admin_menu: añadir la página al panel
Este hook se dispara mientras WordPress construye el menú de administración. Es el sitio correcto para registrar una nueva pantalla:
add_action( 'admin_menu', 'indaga_mp_menu_admin' );
function indaga_mp_menu_admin() {
add_menu_page(
'Mis Plugins',
'Mis Plugins',
'manage_options',
INDAGA_MP_SLUG,
'indaga_mp_render_pagina_admin',
'dashicons-admin-plugins',
80
);
}admin_init: procesar el formulario, nunca en el callback del menú
Este es el hook con el que más cuidado tengo, porque un error aquí ya me ha dado algún quebradero de cabeza en otros plugins: procesar un formulario POST dentro de la función que pinta la página de administración puede dejar la pantalla congelada. La solución es mover ese procesamiento a admin_init, comprobando primero en qué pantalla estamos:
add_action( 'admin_init', 'indaga_mp_procesar_admin_init' );
function indaga_mp_procesar_admin_init() {
if ( ! isset( $_GET['page'] ) || INDAGA_MP_SLUG !== $_GET['page'] ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Aquí se valida el nonce y se guarda o borra el registro.
}admin_enqueue_scripts: cargar estilos solo donde tocan
Para no meter CSS en todas las pantallas del panel, engancho los estilos a este hook y compruebo en qué página estoy antes de cargarlos:
add_action( 'admin_enqueue_scripts', 'indaga_mp_admin_estilos' );
function indaga_mp_admin_estilos( $hook ) {
if ( strpos( (string) $hook, INDAGA_MP_SLUG ) === false ) {
return;
}
wp_register_style( 'indaga-mp-admin', false );
wp_enqueue_style( 'indaga-mp-admin' );
wp_add_inline_style( 'indaga-mp-admin', '/* CSS aquí */' );
}add_shortcode: mostrarlo en el front end
No es técnicamente un action ni un filter, pero funciona con la misma lógica de enganche: le dices a WordPress que, cuando encuentre el shortcode mis_plugins (entre brackets, que si no se ejecuta aquí) en el contenido, ejecute tu función y sustituya el shortcode por lo que devuelva:
add_shortcode( 'mis_plugins', 'indaga_mp_shortcode' );
function indaga_mp_shortcode( $atts ) {
$atts = shortcode_atts( array(
'por_pagina' => 10,
), $atts, 'mis_plugins' );
// Consulta a la base de datos y devuelve el HTML con ob_start()/ob_get_clean().
}Un detalle interesante para quien quiera profundizar en filters: shortcode_atts, por dentro, aplica el filter shortcode_atts_mis_plugins, que permite a otro plugin modificar los atributos por defecto sin tocar mi código. Es un buen ejemplo de cómo WordPress deja puertas abiertas incluso en funciones tan pequeñas como esta.
Resumen
Con seis hooks (uno de activación, uno de carga, tres de administración y un shortcode) queda montado un plugin completo, con su propia tabla, su formulario y su vista en el front end. Si quieres verlo funcionando, tienes el listado en Control de plugins, con el catálogo de todos los que he ido publicando aquí.
