Create a Simple Sidebar Widget

panel

Create a simple sidebar widget for your WordPress site. Learn how to show a list of posts in this tutorial for non-coders. We’ll keep all the code in a single PHP file so you can use it either in a custom child theme or in a custom plugin. The widget will show a list of recent posts and we’ll be able to…

  • Choose a post type. So you could show posts, pages, products, or a custom post type.
  • Optionally filter posts by taxonomy/term.
image-10-2857699
A simple sidebar widget

In WordPress, we interact with widgets in 2 areas. We set their properties in the admin area, and render them in the front-end. Because we need to be able to set some widget options before we can render it, we’ll make the back-end admin bits first.

We’re going to do some object oriented programming to make a PHP class definition. The definition will extend the WP_Widget class. When we’ve defined our class, we’ll tell WordPress about it so we can create widget instances in our website.

infoIf you just want the code for the widget then scroll to the end – it’s all there and you can copy/paste it into your project.

Let’s Write some Widget Code

In your custom child theme folder, create a sub-folder called “widgets” (if you haven’t already got one), then create a file called custom-post-list-widget.php in the new widgets sub-folder. This file will hold all the code for our custom widget. Let’s start by setting up the main functions, like this…

<?php

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

/**
 * A widget to show a list of posts.
 */
class Custom_Post_List_Widget extends WP_Widget {

	private $widget_args = null;
	private $widget_instance = null;
	private $this_id = '';

	function __construct() {
		parent::__construct(
			'Custom_Post_List_Widget',
			__('Custom Post List', 'headwall'),
			array('description' => __('Shows a customisable list of posts.', 'headwall'))
		);
	}

	/**
	 * Display the widget form options in the back-end.
	 */
	public function form($instance) {
		$this->init_widget_instance(null, $instance);
	}

	/**
	 * Process our widget's options.
	 */
	private function init_widget_instance($args, $instance) {
		$this->widget_args = $args;
		$this->widget_instance = $instance;

		if (is_array($args)) {
			$this->this_id = $args['widget_id'];
		}
	}

	/**
	 * Render the widget in the front-end.
	 */
	public function widget($args, $instance) {
		$this->init_widget_instance($args, $instance);

		echo '<p>Widget goes here...</p>';
	}
}
register_widget('Custom_Post_List_Widget');

…then add something like this to your custom child theme’s functions.php file to bring the new code into the fold.

// Load our custom post list widget.
require_once 'widgets/custom-post-list-widget.php';

This is the bare-bones of any widget and is just about enough to get it noticed by WordPress. Save the file, go the Admin area of your website then go to Appearance > Widgets. You’ll see your new Widget in the list of widgets you can add to your site.

image-6-1343612
Our custom widget

Add the widget to your side bar and then go to a page on your site that shows the side bar. If everything’s worked properly you’ll see “Widget goes here…” acting as a sort-of placeholder.

We’re making progress.

Let’s go back to the code and fill in some blanks so we can set some properties for our widget. Here’s a new version of custom-post-list-widget.php

<?php

/**
 * A widget to show a list of posts.
 */

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

/**
 * A widget to show a list of posts.
 */
class Custom_Post_List_Widget extends WP_Widget {

	private $widget_args = null;
	private $widget_instance = null;
	private $this_id = '';

	private $title = null;
	private $is_title_visible = true;
	private $post_type = null;
	private $taxonomy = null;
	private $term = null;
	private $number_of_posts = -1;

	function __construct() {
		parent::__construct(
			'Custom_Post_List_Widget',
			__('Custom Post List', 'headwall'),
			array('description' => __('Shows a customisable list of posts.', 'headwall'))
		);
	}

	/**
	 * Display the widget form options in the back-end.
	 */
	public function form($instance) {
		$this->init_widget_instance(null, $instance);

		// The title for our widget (text/string).
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('title'),
			__('Title:')
		);
		printf('<input class="widefat" type="text" id="%s" name="%s" value="%s" />',
			$this->get_field_id('title'),
			$this->get_field_name('title'),
			esc_attr($this->title)
		);
		echo '</p>';

		// Hide/show the title (boolean/checkbox).
		echo '<p>';
		printf('<input class="checkbox" type="checkbox" id="%s" name="%s" %s />',
			$this->get_field_id('is_title_visible'),
			$this->get_field_name('is_title_visible'),
			$this->is_title_visible ? 'checked' : ''
		);
		printf('<label for="%s">%s</label>',
			$this->get_field_id('is_title_visible'),
			__('Show the Widget Title?', 'headwall')
		);
		echo '</p>';

		// The post type we want to query (drop-down list).
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('post_type'),
			__('Post Type:')
		);
		printf('<select id="%s" name="%s" class="widefat">',
			$this->get_field_id('post_type'),
			$this->get_field_name('post_type')
		);
		if (is_array($post_types = get_post_types(null, 'objects'))) {
			foreach ($post_types as $post_type) {
				if ($post_type->public) {
					$props = '';

					if ($post_type->name == $this->post_type) {
						$props .= 'selected';
					}

					printf('<option value="%s" %s>%s</option>',
						esc_attr($post_type->name),
						$props,
						esc_html($post_type->label)
					);
				}
			}
		}
		echo '</select>';
		echo '</p>';

		// The taxonomy we want to query (drop-down list).
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('taxonomy'),
			__('Taxonomy (Optional):')
		);
		printf('<select id="%s" name="%s" class="widefat">',
			$this->get_field_id('taxonomy'),
			$this->get_field_name('taxonomy')
		);
		if (is_array($taxonomies = get_taxonomies(null, 'objects'))) {
			printf('<option value="" %s>&lt;%s&gt;</option>',
				empty($this->taxonomy) ? 'selected' : '',
				esc_html__('None')
			);

			foreach ($taxonomies as $taxonomy) {
				if ($taxonomy->public) {
					$props = '';

					if ($taxonomy->name == $this->taxonomy) {
						$props .= 'selected';
					}

					printf('<option value="%s" %s>%s (%s)</option>',
						esc_attr($taxonomy->name),
						$props,
						esc_html($taxonomy->label),
						esc_html($taxonomy->name)
					);
				}
			}
		}
		echo '</select>';
		echo '</p>';

		// The taxonomy term we want to filter against.
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('term'),
			__('Term (slug):')
		);
		printf('<input class="widefat" type="text" id="%s" name="%s" value="%s" />',
			$this->get_field_id('term'),
			$this->get_field_name('term'),
			esc_attr($this->term)
		);
		echo '</p>';

		// Number of posts to show.
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('number_of_posts'),
			__('Number of posts:')
		);
		printf('<input type="number" id="%s" name="%s" value="%s" />',
			$this->get_field_id('number_of_posts'),
			$this->get_field_name('number_of_posts'),
			$this->number_of_posts
		);
		echo '</p>';
	}

	/**
	 * Process our widget's options.
	 */
	private function init_widget_instance($args, $instance) {
		$this->widget_args = $args;
		$this->widget_instance = $instance;

		if (is_array($args)) {
			$this->this_id = $args['widget_id'];
		}

		// Widget title text.
		if (!isset($this->widget_instance['title'])) {
			$this->title = __('Custom Post List', 'headwall');
		} else {
			$this->title = $this->widget_instance['title'];
		}

		// Is the widget title visible?
		$this->is_title_visible = true;
		if (isset($this->widget_instance['is_title_visible'])) {
			$this->is_title_visible = boolval($this->widget_instance['is_title_visible']);
		}

		// The post type we want to query.
		$this->post_type = 'post';
		if (isset($this->widget_instance['post_type'])) {
			$this->post_type = $this->widget_instance['post_type'];
		}

		// The taxonomy we want. Optional.
		$this->taxonomy = '';
		if (isset($this->widget_instance['taxonomy'])) {
			$this->taxonomy = $this->widget_instance['taxonomy'];
		}

		// The taxonomy we want. Optional.
		$this->term = '';
		if (isset($this->widget_instance['term'])) {
			$this->term = $this->widget_instance['term'];
		}

		// The number of posts to show.
		$this->number_of_posts = 5;
		if (isset($this->widget_instance['number_of_posts'])) {
			$this->number_of_posts = intval($this->widget_instance['number_of_posts']);
		}
	}

	/**
	 * Render the widget in the front-end.
	 */
	public function widget($args, $instance) {
		$this->init_widget_instance($args, $instance);

		echo '<p>Widget goes here...</p>';
	}
}
register_widget('Custom_Post_List_Widget');

There’s just a couple of interesting code chunks in here. We’ve got a function called form() and a function called init_widget_instance(). WordPress expects us to implement the form() function to layout the options in Appearance > Widgets. The code in there shows how to layout a textbox, a checkbox, a drop-down-list, and a number field. The HTML in here needs to be simple because there’s not a lot of onscreen space to work with. Just make sure you give the HTML form controls the correct names and IDs by using the get_field_id() and get_field_name() functions.

The other function we’ve created is init_widget_instance(). Technically, we don’t need this function – we could’ve just put all that code at the top of the form() function, but… putting the code in a separate little function makes the form() function really easy to read, and… we’re going to call init_widget_instance() again when we write the front-end code.

Check it out!

custom-post-list-widget-options-7063117
Our custom sidebar widget’s properties

Test that you can save each of the options properly. If you try to save an option but it doesn’t actually save, check the form controls have got the correct names/IDs and that every control you’re drawing in the form() function has an equivalent bit of code in init_widget_instance() to extract the values from $instance.

Display Our New Sidebar Widget

Now let’s do the fun bit – actually show some posts in the sidebar. To do this, we just have to add a new function called widget(). Here’s an updated version of custom-post-list-widget.php, complete with the new widget() function to render the front-end.

<?php

/**
 * A widget to show a list of posts.
 *
 * Include this file from your child theme' functions.php file:
 *
 * require_once 'widgets/custom-post-list-widget.php';
 */

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

/**
 * A widget to show a list of posts.
 */
class Custom_Post_List_Widget extends WP_Widget {

	private $widget_args = null;
	private $widget_instance = null;
	private $this_id = '';

	private $title = null;
	private $is_title_visible = true;
	private $post_type = null;
	private $taxonomy = null;
	private $term = null;
	private $number_of_posts = -1;

	function __construct() {
		parent::__construct(
			'Custom_Post_List_Widget',
			__('Custom Post List', 'headwall'),
			array('description' => __('Shows a customisable list of posts.', 'headwall'))
		);
	}

	/**
	 * Display the widget form options in the back-end.
	 */
	public function form($instance) {
		$this->init_widget_instance(null, $instance);

		// The title for our widget (text/string).
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('title'),
			__('Title:')
		);
		printf('<input class="widefat" type="text" id="%s" name="%s" value="%s" />',
			$this->get_field_id('title'),
			$this->get_field_name('title'),
			esc_attr($this->title)
		);
		echo '</p>';

		// Hide/show the title (boolean/checkbox).
		echo '<p>';
		printf('<input class="checkbox" type="checkbox" id="%s" name="%s" %s />',
			$this->get_field_id('is_title_visible'),
			$this->get_field_name('is_title_visible'),
			$this->is_title_visible ? 'checked' : ''
		);
		printf('<label for="%s">%s</label>',
			$this->get_field_id('is_title_visible'),
			__('Show the Widget Title?', 'headwall')
		);
		echo '</p>';

		// The post type we want to query (drop-down list).
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('post_type'),
			__('Post Type:')
		);
		printf('<select id="%s" name="%s" class="widefat">',
			$this->get_field_id('post_type'),
			$this->get_field_name('post_type')
		);
		if (is_array($post_types = get_post_types(null, 'objects'))) {
			foreach ($post_types as $post_type) {
				if ($post_type->public) {
					$props = '';

					if ($post_type->name == $this->post_type) {
						$props .= 'selected';
					}

					printf('<option value="%s" %s>%s</option>',
						esc_attr($post_type->name),
						$props,
						esc_html($post_type->label)
					);
				}
			}
		}
		echo '</select>';
		echo '</p>';

		// The taxonomy we want to query (drop-down list).
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('taxonomy'),
			__('Taxonomy (Optional):')
		);
		printf('<select id="%s" name="%s" class="widefat">',
			$this->get_field_id('taxonomy'),
			$this->get_field_name('taxonomy')
		);
		if (is_array($taxonomies = get_taxonomies(null, 'objects'))) {
			printf('<option value="" %s>&lt;%s&gt;</option>',
				empty($this->taxonomy) ? 'selected' : '',
				esc_html__('None')
			);

			foreach ($taxonomies as $taxonomy) {
				if ($taxonomy->public) {
					$props = '';

					if ($taxonomy->name == $this->taxonomy) {
						$props .= 'selected';
					}

					printf('<option value="%s" %s>%s (%s)</option>',
						esc_attr($taxonomy->name),
						$props,
						esc_html($taxonomy->label),
						esc_html($taxonomy->name)
					);
				}
			}
		}
		echo '</select>';
		echo '</p>';

		// The taxonomy term we want to filter against.
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('term'),
			__('Term (slug):')
		);
		printf('<input class="widefat" type="text" id="%s" name="%s" value="%s" />',
			$this->get_field_id('term'),
			$this->get_field_name('term'),
			esc_attr($this->term)
		);
		echo '</p>';

		// Number of posts to show.
		echo '<p>';
		printf('<label for="%s">%s</label>',
			$this->get_field_id('number_of_posts'),
			__('Number of posts:')
		);
		printf('<input type="number" id="%s" name="%s" value="%s" />',
			$this->get_field_id('number_of_posts'),
			$this->get_field_name('number_of_posts'),
			$this->number_of_posts
		);
		echo '</p>';
	}

	/**
	 * Process our widget's options.
	 */
	private function init_widget_instance($args, $instance) {
		$this->widget_args = $args;
		$this->widget_instance = $instance;

		if (is_array($args)) {
			$this->this_id = $args['widget_id'];
		}

		// Widget title text.
		if (!isset($this->widget_instance['title'])) {
			$this->title = __('Custom Post List', 'headwall');
		} else {
			$this->title = $this->widget_instance['title'];
		}

		// Is the widget title visible?
		$this->is_title_visible = true;
		if (isset($this->widget_instance['is_title_visible'])) {
			$this->is_title_visible = boolval($this->widget_instance['is_title_visible']);
		}

		// The post type we want to query.
		$this->post_type = 'post';
		if (isset($this->widget_instance['post_type'])) {
			$this->post_type = $this->widget_instance['post_type'];
		}

		// The taxonomy we want. Optional.
		$this->taxonomy = '';
		if (isset($this->widget_instance['taxonomy'])) {
			$this->taxonomy = $this->widget_instance['taxonomy'];
		}

		// The taxonomy we want. Optional.
		$this->term = '';
		if (isset($this->widget_instance['term'])) {
			$this->term = $this->widget_instance['term'];
		}

		// The number of posts to show.
		$this->number_of_posts = 5;
		if (isset($this->widget_instance['number_of_posts'])) {
			$this->number_of_posts = intval($this->widget_instance['number_of_posts']);
		}
	}

	/**
	 * Render the widget in the front-end.
	 */
	public function widget($args, $instance) {
		$this->init_widget_instance($args, $instance);

		// Open the HTML for our widget.
		echo $this->widget_args['before_widget'];

		if ($this->is_title_visible && !empty($this->title)) {
			echo $args['before_title'];
			esc_html_e($this->title);
			echo $args['after_title'];
		}

		if (($this->number_of_posts) > 0) {
			$query_args = array(
				'posts_per_page' => $this->number_of_posts,
				'post_type' => $this->post_type,
				'page' => 1,
				'orderby' => 'date',
				'order' => 'DESC',
			);

			// If we have a taxonomy and a term, we can filter for
			// these with a tax_query.
			if (!empty($this->taxonomy) && !empty($this->term)) {
				$query_args['tax_query'] = array(
					array(
						'taxonomy' => $this->taxonomy,
						'field' => 'slug',
						'terms' => $this->term,
					),
				);
			}

			$query = new WP_Query($query_args);
			if ($query->have_posts()) {
				echo '<ul>';
				while ($query->have_posts()) {
					$query->the_post();

					echo '<li>';
					echo '<div class="widget-post">';
					printf('<a href="%s">%s</a>',
						esc_url(get_the_permalink()),
						esc_html(get_the_title())
					);
					echo '</div>';
					echo '</li>';
				}
				echo '</ul>';

				wp_reset_postdata();
			}
		}

		// Close the HTML for our widget.
		echo $this->widget_args['after_widget'];
	}
}
register_widget('Custom_Post_List_Widget');

This is the entire widget PHP file, with it’s three main functions:

  • init_widget_instance()
    • Process our widget’s parameters.
  • form()
    • Display the HTML form in the back-end so we can configure each instance of our widget.
  • widget()
    • Render our widget’s front-end HTML .

The logic in widget() is quite straightforward and centres around WP_Query and a WordPress loop. We’ll cover these in another tutorial, but all we do here is output each post as a list item in an unordered list. You can easily extend this part of the loop to display your post summaries however you want.

This widget can now be used as-is, or you can use it as the basis for something more complex. Hack it about – there’s enough in there to get you started. Have fun! 🙂

Like This Tutorial?

Let us know

WordPress plugins for developers

Leave a comment