Skip to content

Allt om WordPress, webbutveckling — och mer därtill

🛠 Hur man skapar ett widget-plugin för WordPress: en steg-för-steg-guide

🛠 Hur man skapar ett widget-plugin för WordPress: en steg-för-steg-guide

💡 Så bygger du ett widget-plugin för WordPress

  • Skapa en mapp my-widget-plugin i wp-content/plugins/ och en fil my-widget-plugin.php med plugin-huvudet, aktivera sedan i adminpanelen
  • Deklarera en klass som ärver WP_Widget och åsidosätt metoderna __construct(), form(), update() och widget()
  • Registrera widgeten med funktionen register_widget() på hooken widgets_init så att WordPress kan se den i listan
  • I form()-metoden renderar du inställningsfälten; i update() sanerar och sparar du data via sanitize_text_field()
  • I widget()-metoden skriver du ut innehåll på frontend och escape:ar värden med esc_html() och wp_kses_post()

Steg 1: skapa plugin-stommen

En widget i WordPress är inte bara en rad i functions.php. Om du vill att inställningar ska sparas, adminformuläret ska fungera och widgeten ska överleva nästa temauppdatering, måste du paketera den som ett plugin. Det isolerar koden och gör widgeten oberoende av temabyten.

Börja med en tom mapp. Navigera till wp-content/plugins/ och skapa en katalog som heter my-widget-plugin. Skapa en fil med namnet my-widget-plugin.php inuti den. Det är den här filen WordPress läser först vid aktivering. Öppna filen och lägg till standardhuvudet för plugins:

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*/

Spara filen. Gå nu till WordPress adminpanel → Plugins. Om pluginet dyker upp i listan är stommen klar. Klicka på "Aktivera". Det gör inget användbart än, vi har bara meddelat att det finns. Men WordPress vet redan att pluginet existerar och är redo att köra dess kod. Det här är en viktig princip: registrering först, logik sedan.

Hur pluginet fungerar internt, vilka hooks som körs vid aktivering och hur WordPress hittar din fil tas upp i videon ovan. Nu går vi vidare till den mest intressanta delen: widget-klassen.

Steg 2: registrera widgeten via WP_Widget

WordPress har en inbyggd klass som heter WP_Widget; den har funnits i kärnan sedan version 2.8 och är fortfarande grunden för alla anpassade widgetar. Du behöver inte skriva sparlogik, fältgenerering eller registrering från grunden: ärv bara klassen och åsidosätt fyra metoder.

Lägg till den här koden i din my-widget-plugin.php direkt efter plugin-huvudet, före avslutande ?>:

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' );

Låt oss bryta ner vad som händer här. Klassen My_Custom_Widget ärver WP_Widget, vilket ger dig färdiga metoder som get_field_id() och get_field_name() för att generera formulärfältsattribut. I konstruktorn skickar vi tre saker till föräldraklassen: ett unikt widget-ID (gemener, inga mellanslag, my_custom_widget), dess läsbara namn (funktionen __() gör det översättningsbart) och en options-array. Parametern customize_selective_refresh => true gör att widgeten kan uppdateras i anpassaren utan att ladda om hela sidan, en liten detalj som sparar mycket frustration under konfigurationen.

Funktionen my_register_custom_widget() anropar register_widget() på hooken widgets_init. Det är så WordPress får kännedom om din widget. Utan den här raden kommer inget att synas i adminpanelen.

Nu fyller vi metoderna form(), update() och widget() med riktig logik.

Steg 3: skapa widgetformuläret i adminpanelen

Formuläret är vad administratören ser när widgeten dras in i en sidopanel. Det består av fält: textinmatningar, dropdowns, kryssrutor. Varje fält måste kunna spara sitt värde och visa det aktuella värdet när det öppnas igen.

3.1. Funktionen form() och inmatningsfält

Lägg till den här koden i din klass form()-metod. Den skapar fem fält: en titel, en textinmatning, en textarea, en kryssruta och en 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 }

Var uppmärksam på två saker. För det första har vi ersatt den föråldrade funktionen extract() med direkt array-åtkomst. extract() har länge varit utesluten ur WordPress kodningsstandarder; den skapar variabler namngivna efter array-nycklar, vilket är osäkert och gör felsökning svårare. För det andra passerar varje utdatavärde genom esc_attr(), wp_kses_post() eller esc_html(). Det här är inte paranoia: data från databasen kan komma från varsomhelst, och sanering är obligatoriskt.

3.2. Funktionen update() för att spara

Metoden update() anropas när "Spara"-knappen klickas i widgetformuläret. Dess uppgift är att validera varje fält och returnera en sanerad array för att skrivas till databasen.

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}

Här använder vi sanitize_text_field() istället för wp_strip_all_tags() eftersom den också normaliserar blanktecken, tar bort osynliga kontrolltecken och konverterar strängen till säker UTF-8. Det här är en mer grundlig rensning. wp_strip_all_tags() lämnar "rå" text med alla blankteckensartefakter intakta. För textformulärfält, välj alltid sanitize_text_field().

Textarean behåller wp_kses_post(): den tillåter grundläggande HTML (länkar, fet text, listor) men tar bort skript. Kryssrutan returnerar 1 eller false, vilket är läsbart och entydigt i databasen.

Steg 4: rendera widgeten på frontend

Funktionen widget() är vad besökaren ser. Den tar emot två parametrar: $args (widget-omslaget, taggar före och efter titeln och sidopanelen) och $instance (de sparade inställningarna för just den här instansen).

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}

Nyckelpunkten här: vi har ersatt extract( $args ) med direkt åtkomst via $args['before_widget']. Anledningen är densamma: extract() är föråldrad och osäker. Vi har också slagit in utdatan i esc_html() där vanlig text förväntas (titeln, textsträngen, select-värdet). Textarean renderas genom wp_kses_post(), så om administratören infogade en länk eller fet text kommer de att bevaras.

CSS-klassen wp_widget_plugin_box låter dig styla blocket från temats stilmall. Byt gärna namn på den, se bara till att klassen är unik och inte krockar med temaklasser.

Fullständig plugin-kod

Låt oss sätta ihop allt. Här är den kompletta filen my-widget-plugin.php, redo att kopieras och aktiveras:

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' );

Placera den här filen i wp-content/plugins/my-widget-plugin/, aktivera pluginet och dra widgeten till valfri sidopanel via Utseende → Widgetar. Fyll i fälten, spara och kontrollera din webbplats.

Den färdiga koden finns också på GitHub: wpexplorer/my-widget-plugin, där du kan jämföra den med originalversionen från 2017 och se exakt vad vi ändrade.

⁉️🤔 Vanliga frågor

Varför lägga en widget i ett plugin när man bara kan lägga till kod i temats functions.php?

Kod i functions.php är knuten till det aktiva temat. Byt tema, och widgeten försvinner. Ett plugin fungerar oberoende av temat. Dessutom kan ett plugin aktiveras selektivt på olika webbplatser, vilket temakod inte kan. Om widgeten löser ett affärsbehov (till exempel att visa ett prenumerationsformulär med en specifik layout) hör den hemma i ett plugin.

Varför är sanitize_text_field() bättre än wp_strip_all_tags()?

sanitize_text_field() tar inte bara bort HTML-taggar; den normaliserar också blanktecken, tar bort osynliga kontrolltecken och konverterar strängen till UTF-8. Det här är en mer grundlig rensning. wp_strip_all_tags() lämnar "rå" text med alla blankteckensartefakter intakta. För textformulärfält, välj alltid sanitize_text_field().

Varför ersatte ni extract() med direkt array-åtkomst?

Funktionen extract() har varit utesluten ur WordPress kodningsstandarder sedan version 4.3. Den skapar variabler namngivna efter array-nycklar i det lokala scopet; om en nyckel matchar en befintlig variabel skriver du över den och får en svårhittad bugg. Direkt åtkomst som $args['before_widget'] är läsbar och säker.

Behöver jag stödja blockwidgetar (Gutenberg)?

Den klassiska WP_Widget fungerar med blockbaserade sidopaneler genom bakåtkompatibilitet: WordPress slår automatiskt in den i ett Legacy Widget Block. Detta räcker för de flesta scenarier. Om du vill skapa verkligt inbyggda block, se Block Editor Handbook, som har ett separat API. Men att börja med WP_Widget är enklare: koden är kortare, felsökning går snabbare och det fungerar på alla WordPress-versioner utan extra kompatibilitetsplugins.

Hur felsöker jag en widget som inte syns i adminpanelen?

Kontrollera tre saker. För det första: kördes hooken widgets_init? Lägg till error_log( 'Widget registered' ) i registreringsfunktionen och kolla loggarna. För det andra: finns det ett fatalt fel i konstruktorn? Aktivera WP_DEBUG i wp-config.php. För det tredje: matchar klassnamnet i register_widget() namnet på klassen som ärver WP_Widget? Ett enda stavfel, och widgeten kommer inte att visas.

Vad händer nu: från mall till din egen widget

Vi har byggt ett fungerande plugin med fem fälttyper. Det här är inte en färdig produkt; det är en stomme. Ta den som din grund och anpassa den efter dina behov. Behöver du en widget för prenumerationsformulär? Byt ut textfälten mot e-post- och namn-fält, och lägg till ett API-anrop till en e-posttjänst inuti widget(). Behöver du ett block med kampanjer och banners? Ladda bilder via mediauppladdaren och rendera dem i din markup. Mekaniken är alltid densamma: form() ritar fälten, update() sparar dem, widget() renderar utdatan.

Börja smått: kopiera hela koden ovan, aktivera den på en testsajt och experimentera med inställningarna. När du förstår hur data flödar från formuläret till frontend kan du börja lägga till egna fält. Och om widgeten kraschar med en vit skärm, gå tillbaka till steg 2 och kontrollera konstruktorn.