How to Create a Custom WordPress Widget
WordPress is the most popular CMS (Content Management System) in large part due to its flexibility. Thanks to its plugins and widgets, any user can create and manage a website. Furthermore, plugin and theme authors are constantly enhancing the default WordPress functionalities, often by adding new widgets. In doing so, they are helping all users, especially those with little to no coding experience. But, widget creation isn’t reserved only for the most advanced WordPress users. While it is true that creating new widgets is more suited to developers, there is no reason why any intermediate WordPress user shouldn’t try to create a WordPress custom widget. By creating custom widgets for your website, you can add or extend any existing website features. In this article, we will tackle how this can be done and share an example that we made for this occasion.
What are WordPress widgets
Before diving into the how-to part of this article, let us first clarify what WordPress widgets are. WordPress widgets are small pieces of content that are added to specialized areas called widget areas. Widget areas are located outside of the main page or post content.
Widgets display various kinds of information that, and depending on the widget area where they are put, they could show on all pages or just some. The placement and number of possible widget areas depend on the WordPress theme you are using. Generally, most themes have widget areas in the sidebar, footer, and header. Certain widgets are available by default, while others are made available through a theme or a plugin.
To find out which widgets and widget areas are at your disposal, you only need to navigate to Appearance > Widgets. Then, if you want to use a certain widget, you can simply drag it from the list of Available Widgets on the left and drop it in the desired widget area on the right.
Sometimes, specific widgets would be most effective in areas of your website that aren’t covered by a theme’s widget areas. In those cases, a user may decide to create custom widget areas to display those widgets. However, there are cases where the desired functionality simply isn’t available in the form of currently available widgets. Then, if you weren’t able to find a suitable plugin, you should create a WordPress custom widget that meets your needs.
How to create a custom WordPress widget
Creating a custom WordPress widget involves adding a specifically created code either into the functions.php file of your theme or into your site-specific plugin. Inserting the code should be done via FTP. We trust that you are familiar with the use of FTP, or have taken a look at our article on it and are ready to proceed. Without going into the details of inserting it, we can focus on the process of creating the code for a WordPress custom widget. It’s important to stress that you should make a backup of your website beforehand as any code error could break your website. After doing that, you can proceed to the steps required to create the code.
Basic widget structure
Since the 2.8.0 update, WordPress comes with a built-in class called WP_Widget, which is responsible for creating widgets. And every WordPress widget is made by extending the functionality of the WP_Widget class. The WP_Widget class currently has 18 available (non-deprecated) methods that can be used when creating new widgets.
For this article, we will cover the four most important methods, which are sufficient for creating a basic custom widget. Those are the following: __construct(), widget(), form() and update() methods. Depending on what kind of widget you wish to create, more methods might be needed. Those can be either some of the 18 belonging to the WP_Widget class or a specifically created method.
To create a basic custom widget, you need to insert the four methods mentioned above within your class to extend the WP_Widget class. And, to be able to use the Widget within the dashboard, you also need to register it using the register_widget() function. As such, the structure of a basic custom widget would look like this:
class Widget_Name extends WP_Widget { function __construct() { // Some code here } public function widget( $args, $instance ) { // Some code here } public function form( $instance ) { // Some code here } public function update( $new_instance, $old_instance ) { // Some code here } }function name_of_the_register_function() { register_widget( 'Widget_Name' ); }add_action( 'widgets_init', 'name_of_the_register_function' );
We’re going to quickly elaborate the purpose of every piece of the code, before proceeding to study the example we made for this article.
The __construct() method creates the widget using four possible parameters: ID, name, widget, and control options. All parameters are optional, but the ID needs to be lowercase and unique.
The widget() method is the one that displays the widget content on the frontend.
The form() method displays the widget options form in the backend. It is responsible both for the availability and the design of the options within the widget. These options are seen when the widget is added to a widget area.
The update() method updates the options selected within the widget.
The content of each of the above-mentioned methods differs significantly based on the purpose of the widget. Technically speaking, it means that every subclass of the WP_Widget class (i.e. every widget) overrides the above-mentioned parent methods of the WP_Widget class differently.
Apart from that, you also need to register the widget. To do so, you need to “hook” it to the corresponding hook using the add_action() function. You can do it by adding two arguments – “widgets_init” as the hook and the name of your callback function. That callback function needs to contain the use of the register_widget function, with the widget class name as the argument (in our example that’s Widget_Name).
Our example
Now that the widget structure is clearer, we can proceed to an actual example. We made the following widget for the purposes of this article.
/** * Adds Custom_Widget widget. */ class Custom_Widget extends WP_Widget { /** * Register widget with WordPress. */ function __construct() { parent::__construct( 'custom_widget', // Base ID esc_html__( 'My Custom Widget', 'text_domain' ), // Name array( 'description' => esc_html__( 'A basic custom widget', 'text_domain' ), ) // Args ); } /** * Front-end display of the widget. * * @param array $args Widget arguments. * @param array $instance Saved values from the database. * * @see WP_Widget::widget() * */ public function widget( $args, $instance ) { echo $args['before_widget']; if ( ! empty( $instance['title'] ) ) { echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title']; } if ( ! empty( $instance['text'] ) ) { ?> <p class="custom-widget-text"> <?php echo nl2br( esc_html( $instance['text'] ) ) ?> </p> <?php } if ( ! empty( $instance['link'] ) ) { ?> <a href="<?php echo esc_url( $instance['link'] ); ?>" target="<?php echo esc_attr( $instance['link_target'] ); ?>" class="custom-widget-link"> <span><?php echo esc_html__( 'Learn more', 'text_domain' ); ?></span> </a> <?php } echo $args['after_widget']; } /** * Back-end widget form. * * @param array $instance Previously saved values from the database. * * @see WP_Widget::form() * */ public function form( $instance ) { $title = ! empty( $instance['title'] ) ? $instance['title'] : ''; $text = ! empty( $instance['text'] ) ? $instance['text'] : ''; $link = ! empty( $instance['link'] ) ? $instance['link'] : ''; $link_target = ! empty( $instance['link_target'] ) ? $instance['link_target'] : '_blank'; ?> <!-- Title --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> <?php esc_attr__( 'Widget Title:', 'text_domain' ); ?> </label> <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>"> </p> <!-- Text --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> <?php esc_attr__( 'Widget Text:', 'text_domain' ); ?> </label> <textarea class="widefat" rows="16" cols="20" id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>"><?php echo esc_attr( $text ); ?> </textarea> </p> <!-- Link URL --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'link' ) ); ?>"> <?php esc_attr__( 'Insert URL:', 'text_domain' ); ?> </label> <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'link' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'link' ) ); ?>" type="text" value="<?php echo esc_attr( $link ); ?>"> </p> <!-- Link target --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'link_target' ) ); ?>"> <?php esc_attr_e( 'Open link in a new tab?', 'text_domain' ); ?> </label> <select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'link_target' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'link_target' ) ); ?>"> <option value="_blank" <?php echo ( $link_target == '_blank' ) ? 'selected' : ''; ?>>Yes</option> <option value="_self" <?php echo ( $link_target == '_self' ) ? 'selected' : ''; ?>>No</option> </select> </p> <?php } /** * Sanitize widget form values as they are saved. * * @param array $new_instance Values just sent to be saved. * @param array $old_instance Previously saved values from the database. * * @return array Updated safe values to be saved. * @see WP_Widget::update() * */ public function update( $new_instance, $old_instance ) { $instance = array(); $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? sanitize_text_field( $new_instance['title'] ) : ''; if ( current_user_can( 'unfiltered_html' ) ) { $instance['text'] = ( ! empty( $new_instance['text'] ) ) ? $new_instance['text'] : ''; } else { $instance['text'] = ( ! empty( $new_instance['text'] ) ) ? sanitize_text_field( $new_instance['text'] ) : ''; } $instance['link'] = ( ! empty( $new_instance['link'] ) ) ? sanitize_text_field( $new_instance['link'] ) : ''; $instance['link_target'] = ( ! empty( $new_instance['link_target'] ) ) ? sanitize_text_field( $new_instance['link_target'] ) : ''; return $instance; } } // class Custom_Widget // register Custom_Widget widget function register_custom_widget() { register_widget( 'Custom_Widget' ); } add_action( 'widgets_init', 'register_custom_widget' );
Even though this code may appear complicated at first glance, the widget it creates is rather simple. We developed it from the basic widget example given on the Widgets API page, which we suggest you peruse. The code is properly commented to make it easier to review. Let us do so, part by part.
Firstly, the widget is created using a subclass called Custom_Widget that extends the WP_Widget class. The same widget is registered at the end, by using the register_widget() function and the widgets_init hook.
/** * Adds Custom_Widget widget. */ class Custom_Widget extends WP_Widget { // Some code here } // class Custom_Widget // register Custom_Widget widget function register_custom_widget() { register_widget( 'Custom_Widget' ); } add_action( 'widgets_init', 'register_custom_widget' );
The Custom_Widget class has four methods. Using the __construct() method, we created a widget with custom_widget as its ID. The other two arguments mean that the name of this widget is My Custom Widget, while the description is A basic custom widget. You will be able to see this after navigating to Appearance > Widgets, within the Available Widgets section.
/** * Register widget with WordPress. */ function __construct() { parent::__construct( 'custom_widget', // Base ID esc_html__( 'My Custom Widget', 'text_domain' ), // Name array( 'description' => esc_html__( 'A basic custom widget', 'text_domain' ), ) // Args ); }
The widget() method displays three parts of the widget—the title of the widget, a piece of text, and a Learn more link. By wrapping each of the parts with if{…} statements, we have made sure that those parts are only shown if they are inserted within the widget options. We also included the ‘before_widget’ and ‘after_widget’ values around the widget content, and the ‘before_title’ and ‘after_title’ values around the widget title. These four values are defined for all widget areas, default or custom. That is done during the widget area registration process using the register_sidebar() function.
Additionally, the labels were escaped for secure use with the following functions: esc_attr(), esc_url(), esc_html(), and esc_html__(). Furthermore, most of the labels are translatable. However, to translate them, you would need to make sure that the text_domain string, which was added as a placeholder translate domain, is replaced with a proper theme or site-specific plugin translate domain. This is especially helpful for the Learn more label, placed on the link.
Finally, by further wrapping the text with the nl2br() function, we have ensured that any line breaks that might have been inserted in the textarea in the backend are properly displayed in the frontend.
/** * Front-end display of the widget. * * @param array $args Widget arguments. * @param array $instance Saved values from the database. * * @see WP_Widget::widget() * */ public function widget( $args, $instance ) { echo $args['before_widget']; if ( ! empty( $instance['title'] ) ) { echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title']; } if ( ! empty( $instance['text'] ) ) { ?> <p class="custom-widget-text"> <?php echo nl2br( esc_html( $instance['text'] ) ) ?> </p> <?php } if ( ! empty( $instance['link'] ) ) { ?> <a href="<?php echo esc_url( $instance['link'] ); ?>" target="<?php echo esc_attr( $instance['link_target'] ); ?>" class="custom-widget-link"> <span><?php echo esc_html__( 'Learn more', 'text_domain' ); ?></span> </a> <?php } echo $args['after_widget'];
Using the form() function, we have created four simple widget option fields. Those are the following:
-
an input field for inserting a widget title, which has the description Widget Title: above it,
-
a textarea for inserting text, with the description Widget Text: above,
-
an input field for inserting the URL, with the description Insert URL: above, and
-
a select field for choosing if the link is to open a new tab or not, with two options—Yes and No, and a description above asking Open link in a new tab?
None of the fields except the last one have any default values. And, for the last one, the Yes value is set as a default within the select field, meaning the link target is set to _blank by default.
/** * Back-end widget form. * * @param array $instance Previously saved values from the database. * * @see WP_Widget::form() * */ public function form( $instance ) { $title = ! empty( $instance['title'] ) ? $instance['title'] : ''; $text = ! empty( $instance['text'] ) ? $instance['text'] : ''; $link = ! empty( $instance['link'] ) ? $instance['link'] : ''; $link_target = ! empty( $instance['link_target'] ) ? $instance['link_target'] : '_blank'; ?> <!-- Title --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> <?php esc_attr__( 'Widget Title:', 'text_domain' ); ?> </label> <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>"> </p> <!-- Text --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> <?php esc_attr__( 'Widget Text:', 'text_domain' ); ?> </label> <textarea class="widefat" rows="16" cols="20" id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>"><?php echo esc_attr( $text ); ?> </textarea> </p> <!-- Link URL --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'link' ) ); ?>"> <?php esc_attr__( 'Insert URL:', 'text_domain' ); ?> </label> <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'link' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'link' ) ); ?>" type="text" value="<?php echo esc_attr( $link ); ?>"> </p> <!-- Link target --> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'link_target' ) ); ?>"> <?php esc_attr_e( 'Open link in a new tab?', 'text_domain' ); ?> </label> <select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'link_target' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'link_target' ) ); ?>"> <option value="_blank" <?php echo ( $link_target == '_blank' ) ? 'selected' : ''; ?>>Yes</option> <option value="_self" <?php echo ( $link_target == '_self' ) ? 'selected' : ''; ?>>No</option> </select> </p> <?php }
We used the update() method to save the values within the fields into the $instance associative array. But, the values are first sanitized using the sanitize_text_field() function. This is true for all fields, except for the textarea, which requires a special explanation.
The sanitize_text_field() function strips the argument of any unnecessary white spaces, tabs, or line breaks. Therefore, to ensure that multi-paragraph texts are displayed properly, we have used the nl2br() function in the widget() method, as mentioned before.
Furthermore, with the use of if{…} else {…} statement next to the textarea part we have ensured that users with the privileges for inserting unfiltered HTML code are free to do so. On the other hand, if any user without such a privilege inserts HTML code into the textarea, all tags from that code will be stripped thanks to the sanitize_text_field() function. The users that have such privileges, or more precisely capabilities, are (in the case of a regular WordPress website) the ones with the administrator or editor user roles. In the case of a WordPress multisite, such privileges are only reserved for the super admin user role.
/** * Sanitize widget form values as they are saved. * * @param array $new_instance Values just sent to be saved. * @param array $old_instance Previously saved values from the database. * * @return array Updated safe values to be saved. * @see WP_Widget::update() * */ public function update( $new_instance, $old_instance ) { $instance = array(); $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? sanitize_text_field( $new_instance['title'] ) : ''; if ( current_user_can( 'unfiltered_html' ) ) { $instance['text'] = ( ! empty( $new_instance['text'] ) ) ? $new_instance['text'] : ''; } else { $instance['text'] = ( ! empty( $new_instance['text'] ) ) ? sanitize_text_field( $new_instance['text'] ) : ''; } $instance['link'] = ( ! empty( $new_instance['link'] ) ) ? sanitize_text_field( $new_instance['link'] ) : ''; $instance['link_target'] = ( ! empty( $new_instance['link_target'] ) ) ? sanitize_text_field( $new_instance['link_target'] ) : ''; return $instance; }
Since we’ve examined how the code was put together, let us explain how you can use this widget. Start by navigating to Appearance > Widgets and looking for the widget name you used in the __construct() function. In our case, the widget is called My Custom Widget. Drag and drop it in the widget area of your choice and adjust its options according to your needs. As we explained before, you can choose a widget title, text, link below the text, and whether the link should open in a new tab or not. After filling out the options, press the Save button below. The output of the widget depends on your default theme stylization. In our case, using the Lekker WordPress theme, your widget could look similar to the one below.
Depending on the functionality you wish to add, you might need to insert additional JS code for the widget to work properly. Alternatively, you might need to include some CSS code, to adjust the stylization. As this depends entirely on the type of widget you wish to make, creating such JS or CSS code should be done on a case-by-case basis.
In our case, since our example WordPress custom widget is quite simple, we only needed a small amount of CSS to stylize the content. Namely, the link we included. For the widget developed for this article, we created the CSS shown below. As our widget is simple, it required only a small style adjustment. And this amount of CSS can safely be included in Appearance > Customize > Additional CSS.
.custom-widget-link { display: inline-block; background-color: #000; margin-top: 10px; font-weight: 600; border-radius: 5px; padding: 14px 26px; } .custom-widget-link span { color: #fff; }
However, we must note that the best practice for adding widget-related CSS and JS code is to enqueue it. For that, you need to create separate .css and .js files with the code, place the files within the folder of your theme (or within the folder of the site-specific plugin, depending on where you placed the initial code), and use the wp_enqueue_style and wp_enqueue_script functions. For more information on this topic, we advise you to take a look at our article on enqueueing custom scripts and stylesheets in WordPress.
Final Thoughts
Creating custom WordPress widgets might seem daunting at first since it requires a great deal of prior coding and WordPress knowledge. However, widgets can be quite straightforward to create. That’s mostly because they are very well structured and the process for creating them has a lot of run-of-the-mill steps.
We endeavored to explain each of the steps necessary, both in theory as well as in practice using a carefully created example. If you follow these steps closely, we are confident that you will manage to create a custom widget of your own. Hopefully, you will have learned something new in the process, as well!