
🛠 Cómo crear un plugin de widget para WordPress: una guía paso a paso
💡 Cómo crear un plugin de widget en WordPress
- Cree una carpeta
my-widget-plugindentro dewp-content/plugins/y un archivomy-widget-plugin.phpcon la cabecera del plugin, luego actívelo en el panel de administración - Declare una clase que extienda
WP_Widgety sobrescriba los métodos__construct(),form(),update()ywidget() - Registre el widget con la función
register_widget()en el hookwidgets_initpara que WordPress pueda verlo en la lista - En el método
form(), renderice los campos de configuración; enupdate(), sanitice y guarde los datos mediantesanitize_text_field() - En el método
widget(), emita el contenido en el front-end, escapando los valores conesc_html()ywp_kses_post()
Paso 1: crear el andamiaje del plugin
Un widget en WordPress no es solo una línea en functions.php. Si quiere que la configuración persista, que el formulario de administración funcione y que el widget sobreviva a la próxima actualización del tema, necesita empaquetarlo como un plugin. Esto aísla el código y hace que el widget sea independiente de cualquier cambio de tema.
Comience con una carpeta vacía. Navegue a wp-content/plugins/ y cree un directorio llamado my-widget-plugin. Dentro de él, cree un archivo llamado my-widget-plugin.php. Este es el archivo que WordPress leerá primero al activarse. Abra el archivo y añada la cabecera estándar del plugin:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */
Guarde el archivo. Ahora vaya al panel de administración de WordPress → Plugins. Si el plugin aparece en la lista, el andamiaje está listo. Haga clic en «Activar». Todavía no hace nada útil; solo hemos anunciado su existencia. Pero WordPress ya sabe que este plugin existe y está listo para ejecutar su código. Este es un principio importante: primero el registro, después la lógica.
En el video anterior se explica cómo funciona el plugin internamente, qué hooks se disparan en la activación y cómo WordPress localiza su archivo. Ahora pasemos a la parte más interesante: la clase del widget.
Paso 2: registrar el widget mediante WP_Widget
WordPress proporciona una clase integrada llamada WP_Widget; ha sido parte del núcleo desde la versión 2.8 y sigue siendo la base para cualquier widget personalizado. No necesita escribir la lógica de guardado, la generación de campos ni el registro desde cero: solo extienda la clase y sobrescriba cuatro métodos.
Añada este código a su my-widget-plugin.php justo después de la cabecera del plugin, antes del cierre ?>:
1 // Widget class 2 class My_Custom_Widget extends WP_Widget { 3 4 public function __construct() { 5 parent::__construct( 6 'my_custom_widget', 7 __( 'My Custom Widget', 'text_domain' ), 8 array( 9 'customize_selective_refresh' => true, 10 ) 11 ); 12 } 13 14 public function form( $instance ) { 15 /* ... admin form ... */ 16 } 17 18 public function update( $new_instance, $old_instance ) { 19 /* ... saving settings ... */ 20 } 21 22 public function widget( $args, $instance ) { 23 /* ... frontend output ... */ 24 } 25 } 26 27 // Widget registration 28 function my_register_custom_widget() { 29 register_widget( 'My_Custom_Widget' ); 30 } 31 add_action( 'widgets_init', 'my_register_custom_widget' );
Analicemos lo que está sucediendo aquí. La clase My_Custom_Widget extiende WP_Widget, lo que le proporciona métodos ya preparados como get_field_id() y get_field_name() para generar atributos de campos de formulario. En el constructor pasamos tres cosas a la clase padre: un ID de widget único (caracteres latinos en minúscula, sin espacios, my_custom_widget), su nombre legible (la función __() lo hace traducible) y un array de opciones. El parámetro customize_selective_refresh => true permite que el widget se refresque en el Personalizador sin recargar toda la página, un pequeño detalle que ahorra mucha frustración durante la configuración.
La función my_register_custom_widget() llama a register_widget() en el hook widgets_init. Así es como WordPress se entera de su widget. Sin esta línea, no aparecerá nada en el panel de administración.
Ahora llenemos los métodos form(), update() y widget() con lógica real.
Paso 3: crear el formulario del widget en el panel de administración
El formulario es lo que ve el administrador al arrastrar el widget a una barra lateral. Consiste en campos: entradas de texto, listas desplegables, casillas de verificación. Cada campo debe poder guardar su valor y mostrar el actual al reabrirse.
3.1. La función form() y los campos de entrada
Añada este código al método form() de su clase. Crea cinco campos: un título, una entrada de texto, un área de texto, una casilla de verificación y una lista desplegable.
1 public function form( $instance ) { 2 3 $defaults = array( 4 'title' => '', 5 'text' => '', 6 'textarea' => '', 7 'checkbox' => '', 8 'select' => '', 9 ); 10 11 $args = wp_parse_args( (array) $instance, $defaults ); 12 $title = $args['title']; 13 $text = $args['text']; 14 $textarea = $args['textarea']; 15 $checkbox = $args['checkbox']; 16 $select = $args['select']; 17 ?> 18 19 <p> 20 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 21 <?php _e( 'Widget Title', 'text_domain' ); ?> 22 </label> 23 <input class="widefat" 24 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 25 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 26 type="text" 27 value="<?php echo esc_attr( $title ); ?>" /> 28 </p> 29 30 <p> 31 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 32 <?php _e( 'Text:', 'text_domain' ); ?> 33 </label> 34 <input class="widefat" 35 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 36 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 37 type="text" 38 value="<?php echo esc_attr( $text ); ?>" /> 39 </p> 40 41 <p> 42 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 43 <?php _e( 'Textarea:', 'text_domain' ); ?> 44 </label> 45 <textarea class="widefat" 46 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 47 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 48 echo wp_kses_post( $textarea ); 49 ?></textarea> 50 </p> 51 52 <p> 53 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 54 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 55 type="checkbox" 56 value="1" 57 <?php checked( '1', $checkbox ); ?> /> 58 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 59 <?php _e( 'Show additional block', 'text_domain' ); ?> 60 </label> 61 </p> 62 63 <p> 64 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 65 <?php _e( 'Display variant', 'text_domain' ); ?> 66 </label> 67 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 68 id="<?php echo $this->get_field_id( 'select' ); ?>" 69 class="widefat"> 70 <?php 71 $options = array( 72 '' => __( '— Select —', 'text_domain' ), 73 'option_1' => __( 'Option 1', 'text_domain' ), 74 'option_2' => __( 'Option 2', 'text_domain' ), 75 'option_3' => __( 'Option 3', 'text_domain' ), 76 ); 77 foreach ( $options as $key => $name ) { 78 printf( 79 '<option value="%s" %s>%s</option>', 80 esc_attr( $key ), 81 selected( $select, $key, false ), 82 esc_html( $name ) 83 ); 84 } 85 ?> 86 </select> 87 </p> 88 89 <?php }
Preste atención a dos cosas. Primero, reemplazamos la función obsoleta extract() por acceso directo al array. extract() ha sido excluida de los estándares de codificación de WordPress durante mucho tiempo; crea variables con los nombres de las claves del array, lo cual es inseguro y dificulta la depuración. Segundo, cada valor de salida se pasa por esc_attr(), wp_kses_post() o esc_html(). Esto no es paranoia: los datos de la base de datos pueden venir de cualquier parte, y la sanitización es obligatoria.
3.2. La función update() para guardar
El método update() se llama cuando se hace clic en el botón «Guardar» en el formulario del widget. Su trabajo es validar cada campo y devolver un array sanitizado para escribirlo en la base de datos.
1 public function update( $new_instance, $old_instance ) { 2 $instance = $old_instance; 3 4 $instance['title'] = isset( $new_instance['title'] ) 5 ? sanitize_text_field( $new_instance['title'] ) : ''; 6 $instance['text'] = isset( $new_instance['text'] ) 7 ? sanitize_text_field( $new_instance['text'] ) : ''; 8 $instance['textarea'] = isset( $new_instance['textarea'] ) 9 ? wp_kses_post( $new_instance['textarea'] ) : ''; 10 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 11 $instance['select'] = isset( $new_instance['select'] ) 12 ? sanitize_text_field( $new_instance['select'] ) : ''; 13 14 return $instance; 15 }
Aquí usamos sanitize_text_field() en lugar de wp_strip_all_tags() porque también normaliza los espacios en blanco, elimina caracteres de control invisibles y convierte la cadena a UTF-8 seguro. Esta es una limpieza más exhaustiva. wp_strip_all_tags() deja el texto «en bruto» con todos los artefactos de espacio en blanco intactos. Para los campos de formulario de texto, elija siempre sanitize_text_field().
El área de texto conserva wp_kses_post(): permite HTML básico (enlaces, texto en negrita, listas) pero elimina los scripts. La casilla de verificación devuelve 1 o false, lo cual es legible e inequívoco en la base de datos.
Paso 4: renderizar el widget en el front-end
La función widget() es lo que ve el visitante. Recibe dos parámetros: $args (el envoltorio del widget, etiquetas antes y después del título y la barra lateral) y $instance (la configuración guardada para esta instancia en particular).
1 public function widget( $args, $instance ) { 2 3 $title = isset( $instance['title'] ) 4 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 5 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 6 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 7 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 8 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 9 10 echo $args['before_widget']; 11 12 echo '<div class="widget-text wp_widget_plugin_box">'; 13 14 if ( $title ) { 15 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 16 } 17 18 if ( $text ) { 19 echo '<p>' . esc_html( $text ) . '</p>'; 20 } 21 22 if ( $textarea ) { 23 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 24 } 25 26 if ( $select ) { 27 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 28 } 29 30 if ( $checkbox ) { 31 echo '<p class="widget-checkbox-result">' . esc_html__( 'Additional block activated', 'text_domain' ) . '</p>'; 32 } 33 34 echo '</div>'; 35 36 echo $args['after_widget']; 37 }
El punto clave aquí: reemplazamos extract( $args ) por acceso directo mediante $args['before_widget']. La razón es la misma: extract() está obsoleto y es inseguro. También envolvimos la salida en esc_html() donde se espera texto plano (el título, la cadena de texto, el valor del select). El área de texto se renderiza a través de wp_kses_post(), por lo que si el administrador insertó un enlace o texto en negrita, se conservarán.
La clase CSS wp_widget_plugin_box le permite estilizar el bloque desde la hoja de estilos del tema. Siéntase libre de renombrarla, solo asegúrese de que la clase sea única y no entre en conflicto con las clases del tema.
Código completo del plugin
Vamos a reunirlo todo. Aquí está el archivo completo my-widget-plugin.php, listo para copiar y activar:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */ 11 12 class My_Custom_Widget extends WP_Widget { 13 14 public function __construct() { 15 parent::__construct( 16 'my_custom_widget', 17 __( 'My Custom Widget', 'text_domain' ), 18 array( 'customize_selective_refresh' => true ) 19 ); 20 } 21 22 public function form( $instance ) { 23 $defaults = array( 24 'title' => '', 'text' => '', 'textarea' => '', 25 'checkbox' => '', 'select' => '' 26 ); 27 $args = wp_parse_args( (array) $instance, $defaults ); 28 $title = $args['title']; 29 $text = $args['text']; 30 $textarea = $args['textarea']; 31 $checkbox = $args['checkbox']; 32 $select = $args['select']; 33 ?> 34 <p> 35 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 36 <?php _e( 'Widget Title', 'text_domain' ); ?> 37 </label> 38 <input class="widefat" 39 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 40 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 41 type="text" value="<?php echo esc_attr( $title ); ?>" /> 42 </p> 43 <p> 44 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 45 <?php _e( 'Text:', 'text_domain' ); ?> 46 </label> 47 <input class="widefat" 48 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 49 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 50 type="text" value="<?php echo esc_attr( $text ); ?>" /> 51 </p> 52 <p> 53 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 54 <?php _e( 'Textarea:', 'text_domain' ); ?> 55 </label> 56 <textarea class="widefat" 57 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 58 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 59 echo wp_kses_post( $textarea ); 60 ?></textarea> 61 </p> 62 <p> 63 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 64 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 65 type="checkbox" value="1" <?php checked( '1', $checkbox ); ?> /> 66 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 67 <?php _e( 'Show additional block', 'text_domain' ); ?> 68 </label> 69 </p> 70 <p> 71 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 72 <?php _e( 'Display variant', 'text_domain' ); ?> 73 </label> 74 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 75 id="<?php echo $this->get_field_id( 'select' ); ?>" class="widefat"> 76 <?php 77 $options = array( 78 '' => __( '— Select —', 'text_domain' ), 79 'option_1' => __( 'Option 1', 'text_domain' ), 80 'option_2' => __( 'Option 2', 'text_domain' ), 81 'option_3' => __( 'Option 3', 'text_domain' ), 82 ); 83 foreach ( $options as $key => $name ) { 84 printf( 85 '<option value="%s" %s>%s</option>', 86 esc_attr( $key ), 87 selected( $select, $key, false ), 88 esc_html( $name ) 89 ); 90 } 91 ?> 92 </select> 93 </p> 94 <?php 95 } 96 97 public function update( $new_instance, $old_instance ) { 98 $instance = $old_instance; 99 $instance['title'] = isset( $new_instance['title'] ) 100 ? sanitize_text_field( $new_instance['title'] ) : ''; 101 $instance['text'] = isset( $new_instance['text'] ) 102 ? sanitize_text_field( $new_instance['text'] ) : ''; 103 $instance['textarea'] = isset( $new_instance['textarea'] ) 104 ? wp_kses_post( $new_instance['textarea'] ) : ''; 105 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 106 $instance['select'] = isset( $new_instance['select'] ) 107 ? sanitize_text_field( $new_instance['select'] ) : ''; 108 return $instance; 109 } 110 111 public function widget( $args, $instance ) { 112 $title = isset( $instance['title'] ) 113 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 114 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 115 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 116 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 117 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 118 119 echo $args['before_widget']; 120 echo '<div class="widget-text wp_widget_plugin_box">'; 121 122 if ( $title ) { 123 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 124 } 125 if ( $text ) { 126 echo '<p>' . esc_html( $text ) . '</p>'; 127 } 128 if ( $textarea ) { 129 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 130 } 131 if ( $select ) { 132 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 133 } 134 if ( $checkbox ) { 135 echo '<p class="widget-checkbox-result">' 136 . esc_html__( 'Additional block activated', 'text_domain' ) . '</p>'; 137 } 138 139 echo '</div>'; 140 echo $args['after_widget']; 141 } 142 } 143 144 function my_register_custom_widget() { 145 register_widget( 'My_Custom_Widget' ); 146 } 147 add_action( 'widgets_init', 'my_register_custom_widget' );
Coloque este archivo en wp-content/plugins/my-widget-plugin/, active el plugin y arrastre el widget a cualquier barra lateral a través de Apariencia → Widgets. Rellene los campos, guarde y compruebe su sitio.
El código terminado también está disponible en GitHub: wpexplorer/my-widget-plugin, donde puede compararlo con la versión original de 2017 y ver exactamente qué cambiamos.
⁉️🤔 Preguntas frecuentes
¿Por qué poner un widget en un plugin cuando se puede simplemente añadir código al functions.php del tema?
El código en functions.php está vinculado al tema activo. Cambie de tema y el widget desaparece. Un plugin funciona independientemente del tema. Además, un plugin puede activarse selectivamente en diferentes sitios, mientras que el código del tema no. Si el widget resuelve una necesidad de negocio (por ejemplo, mostrar un formulario de suscripción con un diseño específico), pertenece a un plugin.
¿Por qué es mejor sanitize_text_field() que wp_strip_all_tags()?
sanitize_text_field()no solo elimina las etiquetas HTML; también normaliza los espacios en blanco, elimina caracteres de control invisibles y convierte la cadena a UTF-8. Esta es una limpieza más exhaustiva.wp_strip_all_tags()deja el texto «en bruto» con todos los artefactos de espacio en blanco intactos. Para los campos de formulario de texto, elija siempresanitize_text_field().
¿Por qué reemplazó extract() por acceso directo al array?
La función
extract()ha sido excluida de los estándares de codificación de WordPress desde la versión 4.3. Crea variables con los nombres de las claves del array en el ámbito local; si una clave coincide con una variable existente, la sobrescribirá y terminará con un error difícil de encontrar. El acceso directo como$args['before_widget']es legible y seguro.
¿Necesito soportar widgets de bloque (Gutenberg)?
El
WP_Widgetclásico funciona con barras laterales basadas en bloques mediante retrocompatibilidad: WordPress lo envuelve automáticamente en un Bloque de Widget Heredado. Esto es suficiente para la mayoría de los escenarios. Si quiere crear bloques verdaderamente nativos, consulte el Manual del Editor de Bloques, que tiene una API separada. Pero empezar conWP_Widgetes más fácil: el código es más corto, la depuración es más rápida y funciona en todas las versiones de WordPress sin plugins de compatibilidad adicionales.
¿Cómo depuro un widget que no aparece en el panel de administración?
Compruebe tres cosas. Primero: ¿se disparó el hook
widgets_init? Añadaerror_log( 'Widget registered' )a la función de registro y revise los registros. Segundo: ¿hay un error fatal en el constructor? ActiveWP_DEBUGen wp-config.php. Tercero: ¿coincide el nombre de la clase enregister_widget()con el nombre de la clase que extiendeWP_Widget? Un solo error tipográfico y el widget no aparecerá.
¿Qué sigue? De la plantilla a su propio widget
Hemos construido un plugin funcional con cinco tipos de campo. Esto no es un producto terminado; es un andamiaje. Tómelo como su base y adáptelo a sus necesidades. ¿Necesita un widget de formulario de suscripción? Reemplace los campos de texto por campos de correo electrónico y nombre, y añada una llamada a la API del servicio de mailing dentro de widget(). ¿Necesita un bloque con promociones y banners? Cargue imágenes a través del cargador de medios y renderícelas en su marcado. La mecánica es siempre la misma: form() dibuja los campos, update() los guarda, widget() renderiza la salida.
Empiece poco a poco: copie el código completo de arriba, actívelo en un sitio de pruebas y experimente con la configuración. Una vez que entienda cómo fluyen los datos del formulario al front-end, empiece a añadir sus propios campos. Y si el widget falla con una pantalla en blanco, vuelva al paso 2 y revise el constructor.



