Skip to content
🛠 Come creare un plugin widget per WordPress: una guida passo passo

🛠 Come creare un plugin widget per WordPress: una guida passo passo

💡 Come creare un plugin widget per WordPress

  • Crea una cartella my-widget-plugin dentro wp-content/plugins/ e un file my-widget-plugin.php con l'intestazione del plugin, poi attivalo dal pannello di amministrazione
  • Dichiara una classe che estende WP_Widget e sovrascrivi i metodi __construct(), form(), update() e widget()
  • Registra il widget con la funzione register_widget() sull'hook widgets_init in modo che WordPress possa vederlo nell'elenco
  • Nel metodo form(), renderizza i campi delle impostazioni; in update(), sanitizza e salva i dati tramite sanitize_text_field()
  • Nel metodo widget(), produci il contenuto nel front end, facendo l'escape dei valori con esc_html() e wp_kses_post()

Step 1: creare lo scaffold del plugin

Un widget in WordPress non è solo una riga nel functions.php. Se vuoi che le impostazioni persistano, che il form di amministrazione funzioni e che il widget stesso sopravviva al prossimo aggiornamento del tema, devi impacchettarlo come plugin. Questo isola il codice e rende il widget indipendente da qualsiasi cambio di tema.

Inizia con una cartella vuota. Vai in wp-content/plugins/ e crea una directory chiamata my-widget-plugin. Al suo interno, crea un file chiamato my-widget-plugin.php. Questo è il file che WordPress leggerà per primo all'attivazione. Apri il file e aggiungi l'intestazione standard del plugin:

1<?php
2/*
3Plugin Name: My Widget Plugin
4Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/
5Description: Adds a customizable widget with text, textarea, checkbox, and dropdown.
6Version: 1.0
7Author: AJ Clarke
8Author URI: https://www.wpexplorer.com/
9License: GPL2
10*/

Salva il file. Ora vai nel pannello di amministrazione di WordPress → Plugin. Se il plugin compare nell'elenco, lo scaffold è pronto. Clicca "Attiva". Per ora non fa nulla di utile; abbiamo solo annunciato la sua esistenza. Ma WordPress sa già che questo plugin esiste ed è pronto a eseguire il suo codice. Questo è un principio importante: prima la registrazione, poi la logica.

Come funziona il plugin internamente, quali hook vengono attivati all'attivazione e come WordPress localizza il tuo file sono tutti argomenti trattati nel video qui sopra. Ora passiamo alla parte più interessante: la classe del widget.

Step 2: registrare il widget tramite WP_Widget

WordPress fornisce una classe integrata chiamata WP_Widget; fa parte del core dalla versione 2.8 e resta la base per qualsiasi widget personalizzato. Non devi scrivere da zero la logica di salvataggio, la generazione dei campi o la registrazione: ti basta estendere la classe e sovrascrivere quattro metodi.

Aggiungi questo codice al tuo my-widget-plugin.php subito dopo l'intestazione del plugin, prima del ?> di chiusura:

1// Widget class
2class 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
28function my_register_custom_widget() {
29 register_widget( 'My_Custom_Widget' );
30}
31add_action( 'widgets_init', 'my_register_custom_widget' );

Analizziamo cosa sta succedendo qui. La classe My_Custom_Widget estende WP_Widget, che ti fornisce metodi già pronti come get_field_id() e get_field_name() per generare gli attributi dei campi del form. Nel costruttore passiamo tre cose alla classe padre: un ID widget univoco (caratteri latini minuscoli, senza spazi, my_custom_widget), il suo nome leggibile (la funzione __() lo rende traducibile) e un array di opzioni. Il parametro customize_selective_refresh => true permette al widget di aggiornarsi nel Personalizzatore senza ricaricare l'intera pagina, un piccolo dettaglio che evita molte frustrazioni durante la configurazione.

La funzione my_register_custom_widget() chiama register_widget() sull'hook widgets_init. È così che WordPress viene a conoscenza del tuo widget. Senza questa riga, non comparirà nulla nel pannello di amministrazione.

Ora riempiamo i metodi form(), update() e widget() con la logica reale.

Step 3: creare il form del widget nel pannello di amministrazione

Il form è ciò che l'amministratore vede quando trascina il widget in una sidebar. È composto da campi: input di testo, dropdown, checkbox. Ogni campo deve poter salvare il proprio valore e mostrare quello corrente quando viene riaperto.

3.1. La funzione form() e i campi di input

Aggiungi questo codice al metodo form() della tua classe. Crea cinque campi: un titolo, un input di testo, una textarea, una checkbox e un dropdown.

1public 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 }

Presta attenzione a due cose. Primo, abbiamo sostituito la funzione deprecata extract() con l'accesso diretto all'array. extract() è stata esclusa da tempo dagli standard di codifica di WordPress; crea variabili con i nomi delle chiavi dell'array, il che è insicuro e rende il debug più difficile. Secondo, ogni valore in output passa attraverso esc_attr(), wp_kses_post() o esc_html(). Non è paranoia: i dati dal database possono arrivare da qualsiasi fonte, e la sanitizzazione è obbligatoria.

3.2. La funzione update() per il salvataggio

Il metodo update() viene chiamato quando si clicca il pulsante "Salva" nel form del widget. Il suo compito è convalidare ogni campo e restituire un array sanitizzato da scrivere nel database.

1public 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}

Qui usiamo sanitize_text_field() invece di wp_strip_all_tags() perché normalizza anche gli spazi bianchi, rimuove i caratteri di controllo invisibili e converte la stringa in UTF-8 sicuro. Questa è una pulizia più approfondita. wp_strip_all_tags() lascia il testo "grezzo" con tutti gli artefatti di spaziatura intatti. Per i campi di testo dei form, scegli sempre sanitize_text_field().

La textarea mantiene wp_kses_post(): permette l'HTML di base (link, grassetto, liste) ma rimuove gli script. La checkbox restituisce 1 o false, che è leggibile e non ambiguo nel database.

Step 4: renderizzare il widget nel front end

La funzione widget() è ciò che vede il visitatore. Riceve due parametri: $args (il wrapper del widget, i tag prima e dopo il titolo e la sidebar) e $instance (le impostazioni salvate per questa specifica istanza).

1public 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}

Il punto chiave qui: abbiamo sostituito extract( $args ) con l'accesso diretto tramite $args['before_widget']. Il motivo è lo stesso: extract() è deprecata e insicura. Abbiamo anche racchiuso l'output in esc_html() dove ci si aspetta testo semplice (il titolo, la stringa di testo, il valore del select). La textarea viene renderizzata tramite wp_kses_post(), così se l'amministratore ha inserito un link o del testo in grassetto, questi verranno preservati.

La classe CSS wp_widget_plugin_box ti permette di stilizzare il blocco dal foglio di stile del tema. Sentiti libero di rinominarla, assicurati solo che la classe sia univoca e non entri in conflitto con le classi del tema.

Codice completo del plugin

Mettiamo tutto insieme. Ecco il file my-widget-plugin.php completo, pronto da copiare e attivare:

1<?php
2/*
3Plugin Name: My Widget Plugin
4Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/
5Description: Adds a customizable widget with text, textarea, checkbox, and dropdown.
6Version: 1.0
7Author: AJ Clarke
8Author URI: https://www.wpexplorer.com/
9License: GPL2
10*/
11
12class 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
144function my_register_custom_widget() {
145 register_widget( 'My_Custom_Widget' );
146}
147add_action( 'widgets_init', 'my_register_custom_widget' );

Inserisci questo file in wp-content/plugins/my-widget-plugin/, attiva il plugin e trascina il widget in qualsiasi sidebar tramite Aspetto → Widget. Compila i campi, salva e controlla il tuo sito.

Il codice finito è disponibile anche su GitHub: wpexplorer/my-widget-plugin, dove puoi confrontarlo con la versione originale del 2017 e vedere esattamente cosa abbiamo cambiato.

⁉️🤔 Domande frequenti

Perché mettere un widget in un plugin quando si può semplicemente aggiungere codice al functions.php del tema?

Il codice nel functions.php è legato al tema attivo. Cambia tema e il widget sparisce. Un plugin funziona indipendentemente dal tema. Inoltre, un plugin può essere attivato selettivamente su siti diversi, mentre il codice del tema no. Se il widget risolve un'esigenza di business (ad esempio, mostrare un modulo di iscrizione con un layout specifico), deve stare in un plugin.

Perché sanitize_text_field() è migliore di wp_strip_all_tags()?

sanitize_text_field() non si limita a rimuovere i tag HTML; normalizza anche gli spazi bianchi, elimina i caratteri di controllo invisibili e converte la stringa in UTF-8. Questa è una pulizia più approfondita. wp_strip_all_tags() lascia il testo "grezzo" con tutti gli artefatti di spaziatura intatti. Per i campi di testo dei form, scegli sempre sanitize_text_field().

Perché avete sostituito extract() con l'accesso diretto all'array?

La funzione extract() è stata esclusa dagli standard di codifica di WordPress dalla versione 4.3. Crea variabili con i nomi delle chiavi dell'array nello scope locale; se una chiave corrisponde a una variabile esistente, la sovrascriverai ottenendo un bug difficile da trovare. L'accesso diretto come $args['before_widget'] è leggibile e sicuro.

Devo supportare i block widget (Gutenberg)?

Il classico WP_Widget funziona con le sidebar basate su blocchi grazie alla retrocompatibilità: WordPress lo racchiude automaticamente in un Blocco Widget Legacy. Questo è sufficiente per la maggior parte degli scenari. Se vuoi creare blocchi veramente nativi, consulta il Manuale dell'Editor a Blocchi, che ha un'API separata. Ma iniziare con WP_Widget è più semplice: il codice è più breve, il debug è più veloce e funziona su tutte le versioni di WordPress senza plugin di compatibilità aggiuntivi.

Come faccio il debug di un widget che non compare nel pannello di amministrazione?

Controlla tre cose. Primo: l'hook widgets_init è stato attivato? Aggiungi error_log( 'Widget registered' ) alla funzione di registrazione e controlla i log. Secondo: c'è un errore fatale nel costruttore? Attiva WP_DEBUG in wp-config.php. Terzo: il nome della classe in register_widget() corrisponde al nome della classe che estende WP_Widget? Un solo errore di battitura e il widget non comparirà.

Prossimi passi: dal template al tuo widget personale

Abbiamo costruito un plugin funzionante con cinque tipi di campo. Questo non è un prodotto finito; è uno scaffold. Prendilo come base e adattalo alle tue esigenze. Ti serve un widget per un modulo di iscrizione? Sostituisci i campi di testo con campi per email e nome, e aggiungi una chiamata API al servizio di mailing dentro widget(). Ti serve un blocco con promozioni e banner? Carica le immagini tramite il media uploader e renderizzale nel tuo markup. La meccanica è sempre la stessa: form() disegna i campi, update() li salva, widget() renderizza l'output.

Inizia in piccolo: copia il codice completo qui sopra, attivalo su un sito di test e sperimenta con le impostazioni. Una volta capito come i dati fluiscono dal form al front end, inizia ad aggiungere i tuoi campi. E se il widget va in crash con una schermata bianca, torna allo step 2 e controlla il costruttore.