Creating custom WordPress widget

TutorialsWordPress

Creating custom WordPress widget has always been daunting if you are just starting up or learning to use WordPress coding. Let’s see how easy this process is to learn and customize the WordPress widget on your own. For now let’s start with a brief introduction of a WordPress Widget.

What is a WordPress widget ?

Widgets are the small components within the WordPress that add content and features to your sidebar areas. These may be a small search form provided by the WordPress itself for searching the posts and pages within the site or a complex product search form added by the Theme and Plugins for searching the product with distinct features. Most of the WordPress Themes and Plugins have custom widget of their on to provide the additional function and feature to the website. For example Newsletter plugin ( An email marketing system for WordPress blog ) provides a widget for displaying subscriber form on the sidebar.

Creating WordPress Widget

First and foremost it is always recommended to create a separate plugin to add the WordPress widget otherwise if you switch or update the theme in the future the widget might get lost. However, if you are looking for adding it to the theme itself you are advised to do it so by creating a child theme. If you want to learn to create a child theme you may visit this link Creating WordPress Child Theme. For this tutorial we will be creating a separate plugin to add the widget.

In this article we will be learning how to create a simple recent post widget that displays WordPress posts with the option to display its featured image and custom date format.

Create a custom WordPress Plugin

First and foremost connect to your site FTP and navigate to wp-contents/plugins folder and create a folder with your plugin name. For mine I will be naming it mystical-recent-posts-widget. Keep in mind that you keep the plugin name unique and self explanatory. For instance mystical- prefixing would make it a unique plugin and recent-posts-widget would describe what the motive of the plugin is.

Once the plugin folder has been created next we will be creating a PHP file with a name derived from your Plugin name. As I have named by plugin with mystical-recent-posts-widget I will be creating a php file with the exact same name mystical-recent-posts-widget.php file. Now Paste the following code to this file. Later on we will be describing on each code and how they contribute in creating a functional WordPress widget.

<?php
    /*
    Plugin Name: Mystical Recent Post Widget
    Plugin URI: https://mysticalthemes.com/
    Description: A plugin that displays recent posts.
    Version: 1.0.0
    Author: bnayawebguy
    Author URI: https://mysticalthemes.com/
    License: GPL2
    */

    // Register the widget
    function mystical_register_widget() {
        register_widget( 'Mystical_Recent_Post_Widget' );
    }
    add_action( 'widget_init', 'mystical_register_widget' );

    class Mystical_Recent_Post_Widget extends WP_Widget {
        // class constructor
        public function __construct() {}

        // output the widget content on the front-end
        public function widget( $args, $instance ) {}

        // output the option form field in admin Widgets screen
        public function form( $instance ) {}

        // save options
        public function update( $new_instance, $old_instance ) {}
    }

Does above code seems very complex to you ? Don’t worry I will be explaining each and every block for you and by the end of this article you will be able to create a custom widget on your own.

Plugin Header

Header comment is used to identify that it is plugin and it provides the information of the plugin to the WordPress. header comment must contain the Plugin Name at least. This parameter is mandatory while creating a plugin and everything else are optional.

<?php
     /*
      * Plugin Name: Mystical Recent Post Widget
      */

However, Other parameters are recommended if you are thinking of submitting the plugin to the WordPress. These parameters ensures that your plugin is properly listed in the WordPress. If you want to learn more on those optional field feel free to visit WordPress Codex. It has a detailed explanation on those fields.

/*
    Plugin Name: Mystical Recent Post Widget
    Plugin URI: https://mysticalthemes.com/
    Description: A plugin that displays recent posts.
    Version: 1.0.0
    Author: bnayawebguy
    Author URI: https://mysticalthemes.com/
    License: GPL2
    */

Registering the WordPress Widget

WordPress provides a rich set of action and filter hooks to work with. I will be discussing about these hooks on my next article as it is a vast topic to cover in one. For now lets take hooks as the mechanism to add certain block of code after a action is being executed. As in the following example I have hooked the function mystical_register_widget which registers a custom made Mystical_Recent_Post_Widget class when the widget is initialized by widget_init function ( which is a custom built in action of the WordPress ). register_widget function as the name of function registers new widget to the WordPress.

    // Register the widget
    function mystical_register_widget() {
        register_widget( 'Mystical_Recent_Post_Widget' );
    }
    add_action( 'widget_init', 'mystical_register_widget' );

Extend WP_Widget class to create custom Widget

All the WordPress widgets either built-in or added by the Themes & Plugins make use of WP_Widget to create custom widgets. WP_Widget provides a base for creating the new widgets. Think of it as an already built-in template over which we will be extending or overriding it’s built in functionality to create our own widget.

    class Mystical_Recent_Post_Widget extends WP_Widget {
        // class constructor
        public function __construct() {}

        // output the widget content on the front-end
        public function widget( $args, $instance ) {}

        // output the option form field in admin Widgets screen
        public function form( $instance ) {}

        // save options
        public function update( $new_instance, $old_instance ) {}
    }

__construct() :

This function is executed immediately after the widget has been registered. We will be modifying this method to display name of the widget by adding following code:

        // class constructor
        public function __construct() {
            parent::__construct( false, __( 'MT: Post List', 'mystical-themes' ) );
        }

form() :

This function as name suggests is used to create form fields to get the inputs from the users. The form field might contain text field to display the Widget Title, Number field to display the Number of posts to display in the widget and so on. For this example we will be providing users option to set Widget Title, No. of posts, Order By and Order of the post.

        // output the option form field in admin Widgets screen
        public function form( $instance ) {
            $title = isset( $instance[ 'title' ] ) ? $instance[ 'title' ] : esc_html__( 'New Title', 'mystical-themes' );
            $no_of_posts = isset( $instance[ 'no_of_posts' ] ) ? $instance[ 'no_of_posts' ] : 4;
            $orderby = isset( $instance[ 'orderby' ] ) ? $instance[ 'orderby' ] : 'none';
            $order = isset( $instance[ 'order' ] ) ? $instance[ 'order' ] : 'ASC';
            
            $oderby_list = $this->orderby_list();
            $order_list = $this->order_list();
            ?>
            <p>
                <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title', 'mystical-themes' ); ?></label> 
                <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_html( $title ); ?>" />
            </p>
            <p>
                <label for="<?php echo $this->get_field_id( 'no_of_posts' ); ?>"><?php esc_html_e( 'No. of Posts', 'mystical-themes' ); ?></label> 
                <input class="widefat" id="<?php echo $this->get_field_id( 'no_of_posts' ); ?>" name="<?php echo $this->get_field_name( 'no_of_posts' ); ?>" type="number" value="<?php echo absint( $no_of_posts ); ?>" />
            </p>
            <p>
                <label for="<?php echo $this->get_field_id( 'orderby' ); ?>"><?php esc_html_e( 'Order By', 'mystical-themes' ); ?></label>
                <select class="widefat" id="<?php echo $this->get_field_id( 'orderby' ); ?>" name="<?php echo $this->get_field_name( 'orderby' ); ?>" >
                    <?php foreach( $oderby_list as $key => $label ) : ?>
                        <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $orderby, $key ); ?> ><?php echo esc_html( $label ); ?></option>
                    <?php endforeach; ?>
                </select> 
            </p>
            
            <p>
                <label for="<?php echo $this->get_field_id( 'order' ); ?>"><?php esc_html_e( 'Order', 'mystical-themes' ); ?></label>
                <select class="widefat" id="<?php echo $this->get_field_id( 'order' ); ?>" name="<?php echo $this->get_field_name( 'order' ); ?>" >
                    <?php foreach( $order_list as $key => $label ) : ?>
                        <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $order, $key ); ?> ><?php echo esc_html( $label ); ?></option>
                    <?php endforeach; ?>
                </select> 
            </p>
            <?php
        }

update() :

This function ensures that the form fields are saved/updated to the WordPress database. Make sure you’ve properly sanitized the form inputs properly before saving it to the database. Sanitization ensures the security of the website. If you are unsure which escaping to use visit WordPress Codex on more information about the Escaping & Sanitization.

        // save options
        public function update( $new_instance, $old_instance ) {
            $instance = array();
            $instance['title'] = ( !empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
            $instance['no_of_posts'] = ( !empty( $new_instance['no_of_posts'] ) ) ? absint( $new_instance['no_of_posts'] ) : 4;
            $instance['orderby'] = ( !empty( $new_instance['orderby'] ) ) ? sanitize_text_field( $new_instance['orderby'] ) : 'none';
            $instance['order'] = ( !empty( $new_instance['order'] ) ) ? sanitize_text_field( $new_instance['order'] ) : 'ASC';
            
            return $instance;
        }

widget() :

This function is responsible for the frontend part of the widget. This is what the end visitors will be seeing. The function retrieves the widget form fields saved earlier using update() function. Make sure you’ve properly escaped the options before displaying. Escaping the options ensures the security of the website. If you are unsure which escaping to use visit WordPress Codex on more information about the Escaping & Sanitization.

        // output the widget content on the front-end
        public function widget( $args, $instance ) {
            $title = ( isset( $instance['title'] ) ) ? $instance['title'] : '';
            $title = apply_filters( 'widget_title', $title );
 
            // before and after widget arguments are defined by themes
            echo $args['before_widget'];
            if ( ! empty( $title ) ) {
                echo $args['before_title'] . $title . $args['after_title'];
            }
            
            $post_args = $this->query_args( $instance );
            $post_query = new WP_Query( $post_args );
            
            if( $post_query->have_posts() ) :            
                ?>
                <ul class="post-list">
                    <?php while( $post_query->have_posts() ) : $post_query->the_post(); ?>
                        <?php if( has_post_thumbnail() ) : ?>
                            <?php
                                $post_image = wp_get_attachment_image_src( get_post_thumbnail_id(), 'thumbnail' );
                            ?>
                            <li>
                                <div class="post-image">
                                    <a href="<?php the_permalink(); ?>"><img src="<?php echo esc_url( $post_image[0] ); ?>" alt="" /></a>
                                </div>
                                <div class="post-content">
                                    <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
                                    <div class="post-meta">
                                        <?php $this->get_author(); ?>
                                        <?php $this->get_date(); ?>
                                    </div>
                                </div>
                            </li>
                        <?php endif; ?>
                    <?php endwhile; ?>
                </ul>    
                <?php
            wp_reset_postdata();
            endif;
            
            echo $args['after_widget'];
        }

Additional Functions :

I have added additional function to the class for code readability and for the purpose of easy code debugging. These functions are not the native methods of the WP_Widget Class.

query_args() :

This function takes widget options related to the WP Query and returns the arguments for creating a WP_Query object.

orderby_list() :

This function returns a list of Order By parameters for the Order By form field used by form() function.

order_list() :

This function returns a list of Order parameters for the Order form field used by form() function.

        /** Query Arguments **/
        protected function query_args( $instance ) {
            return $args = array(
                'post_type' => 'post',
                'posts_per_page' => $instance['no_of_posts'],
                'orderby' => $instance['orderby'],
                'order' => $instance['order'],
            );
        }

        /** Orderby Lists **/
        protected function orderby_list() {
            return array(
                'none' => esc_html__( 'None', 'mystical-themes' ),
                'date' => esc_html__( 'Date', 'mystical-themes' ),
                'rand' => esc_html__( 'Random', 'mystical-themes' ),
                'title' => esc_html__( 'Title', 'mystical-themes' ),
            );
        }
        
        /** Order List **/
        protected function order_list() {
            return array(
                'ASC' => esc_html__( 'Ascending', 'mystical-themes' ),
                'DESC' => esc_html__( 'Descending', 'mystical-themes' )
            );
        }

Final Conclusion

The final code block for creating a recent post widget should something like this:

<?php
    /*
    Plugin Name: Mystical Recent Post Widget
    Plugin URI: https://mysticalthemes.com/
    Description: A plugin that displays recent posts.
    Version: 1.0.0
    Author: bnayawebguy
    Author URI: https://mysticalthemes.com/
    License: GPL2
    */

    // Register the widget
    function mystical_register_widget() {
        register_widget( 'Mystical_Recent_Post_Widget' );
    }
    add_action( 'widget_init', 'mystical_register_widget' );

    class Mystical_Recent_Post_Widget extends WP_Widget {
        // class constructor
        public function __construct() {
            parent::__construct( false, __( 'MT: Post List', 'mystical-themes' ) );
        }

        // output the option form field in admin Widgets screen
        public function form( $instance ) {
            $title = isset( $instance[ 'title' ] ) ? $instance[ 'title' ] : esc_html__( 'New Title', 'mystical-themes' );
            $no_of_posts = isset( $instance[ 'no_of_posts' ] ) ? $instance[ 'no_of_posts' ] : 4;
            $orderby = isset( $instance[ 'orderby' ] ) ? $instance[ 'orderby' ] : 'none';
            $order = isset( $instance[ 'order' ] ) ? $instance[ 'order' ] : 'ASC';
            
            $oderby_list = $this->orderby_list();
            $order_list = $this->order_list();
            ?>
            <p>
                <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title', 'mystical-themes' ); ?></label> 
                <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_html( $title ); ?>" />
            </p>
            <p>
                <label for="<?php echo $this->get_field_id( 'no_of_posts' ); ?>"><?php esc_html_e( 'No. of Posts', 'mystical-themes' ); ?></label> 
                <input class="widefat" id="<?php echo $this->get_field_id( 'no_of_posts' ); ?>" name="<?php echo $this->get_field_name( 'no_of_posts' ); ?>" type="number" value="<?php echo absint( $no_of_posts ); ?>" />
            </p>
            <p>
                <label for="<?php echo $this->get_field_id( 'orderby' ); ?>"><?php esc_html_e( 'Order By', 'mystical-themes' ); ?></label>
                <select class="widefat" id="<?php echo $this->get_field_id( 'orderby' ); ?>" name="<?php echo $this->get_field_name( 'orderby' ); ?>" >
                    <?php foreach( $oderby_list as $key => $label ) : ?>
                        <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $orderby, $key ); ?> ><?php echo esc_html( $label ); ?></option>
                    <?php endforeach; ?>
                </select> 
            </p>
            
            <p>
                <label for="<?php echo $this->get_field_id( 'order' ); ?>"><?php esc_html_e( 'Order', 'mystical-themes' ); ?></label>
                <select class="widefat" id="<?php echo $this->get_field_id( 'order' ); ?>" name="<?php echo $this->get_field_name( 'order' ); ?>" >
                    <?php foreach( $order_list as $key => $label ) : ?>
                        <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $order, $key ); ?> ><?php echo esc_html( $label ); ?></option>
                    <?php endforeach; ?>
                </select> 
            </p>
            <?php
        }

        // output the widget content on the front-end
        public function widget( $args, $instance ) {
            $title = ( isset( $instance['title'] ) ) ? $instance['title'] : '';
            $title = apply_filters( 'widget_title', $title );
 
            // before and after widget arguments are defined by themes
            echo $args['before_widget'];
            if ( ! empty( $title ) ) {
                echo $args['before_title'] . $title . $args['after_title'];
            }
            
            $post_args = $this->query_args( $instance );
            $post_query = new WP_Query( $post_args );
            
            if( $post_query->have_posts() ) :            
                ?>
                <ul class="post-list">
                    <?php while( $post_query->have_posts() ) : $post_query->the_post(); ?>
                        <?php if( has_post_thumbnail() ) : ?>
                            <?php
                                $post_image = wp_get_attachment_image_src( get_post_thumbnail_id(), 'thumbnail' );
                            ?>
                            <li>
                                <div class="post-image">
                                    <a href="<?php the_permalink(); ?>"><img src="<?php echo esc_url( $post_image[0] ); ?>" alt="" /></a>
                                </div>
                                <div class="post-content">
                                    <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
                                    <div class="post-meta">
                                        <?php $this->get_author(); ?>
                                        <?php $this->get_date(); ?>
                                    </div>
                                </div>
                            </li>
                        <?php endif; ?>
                    <?php endwhile; ?>
                </ul>    
                <?php
            wp_reset_postdata();
            endif;
            
            echo $args['after_widget'];
        }

        // save options
        public function update( $new_instance, $old_instance ) {
            $instance = array();
            $instance['title'] = ( !empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
            $instance['no_of_posts'] = ( !empty( $new_instance['no_of_posts'] ) ) ? absint( $new_instance['no_of_posts'] ) : 4;
            $instance['orderby'] = ( !empty( $new_instance['orderby'] ) ) ? sanitize_text_field( $new_instance['orderby'] ) : 'none';
            $instance['order'] = ( !empty( $new_instance['order'] ) ) ? sanitize_text_field( $new_instance['order'] ) : 'ASC';
            
            return $instance;
        }

        /** Query Arguments **/
        protected function query_args( $instance ) {
            return $args = array(
                'post_type' => 'post',
                'posts_per_page' => $instance['no_of_posts'],
                'orderby' => $instance['orderby'],
                'order' => $instance['order'],
            );
        }

        /** Orderby Lists **/
        protected function orderby_list() {
            return array(
                'none' => esc_html__( 'None', 'mystical-themes' ),
                'date' => esc_html__( 'Date', 'mystical-themes' ),
                'rand' => esc_html__( 'Random', 'mystical-themes' ),
                'title' => esc_html__( 'Title', 'mystical-themes' ),
            );
        }
        
        /** Order List **/
        protected function order_list() {
            return array(
                'ASC' => esc_html__( 'Ascending', 'mystical-themes' ),
                'DESC' => esc_html__( 'Descending', 'mystical-themes' )
            );
        }
    }
written by mtheme

One thought on “Creating custom WordPress widget

Leave a Reply

Your email address will not be published. Required fields are marked *