This guide explains how to create custom widgets in WordPress. Custom widgets allow you to extend the functionality of widget areas like sidebars and footers, enabling you to display specific content.
Creating a Widget
To create a custom widget, you need to define a PHP class that extends the WP_Widget base class and register it using the register_widget() function. This function must be called on the widgets_init hook.
Here is a basic example that creates a widget to display random posts:
class Random_Posts_Widget extends WP_Widget {
function __construct() {
parent::__construct(
'random_posts',
__('Random Posts', 'text_domain'),
array('description' => __('Displays a list of random posts', 'text_domain'))
);
}
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'];
}
$query_args = array(
'posts_per_page' => !empty($instance['number']) ? absint($instance['number']) : 5,
'orderby' => 'rand'
);
$random_posts = new WP_Query($query_args);
if ($random_posts->have_posts()) {
echo '';
while ($random_posts->have_posts()) {
$random_posts->the_post();
echo '- ' . get_the_title() . '
';
}
echo '
';
wp_reset_postdata();
} else {
echo 'No posts found.
';
}
echo $args['after_widget'];
}
}
function register_random_posts_widget() {
register_widget('Random_Posts_Widget');
}
add_action('widgets_init', 'register_random_posts_widget');
Add this code to your theme's functions.php file or a custom plugin. Afterward, you will see a widget named "Random Posts" in the WordPress admin under Appearance → Widgets, which you can drag into widget areas.
Adding Widget Settings
To allow users to customize the widget (e.g., title, number of posts), implement the form() and update() methods in your widget class.
form(): Generates the settings form in the widget admin.update(): Processes and saves the submitted form data.
Here is the enhanced example with settings:
class Random_Posts_Widget extends WP_Widget {
function __construct() {
parent::__construct(
'random_posts',
__('Random Posts', 'text_domain'),
array('description' => __('Displays a list of random posts', 'text_domain'))
);
}
function widget($args, $instance) {
$title = apply_filters('widget_title', !empty($instance['title']) ? $instance['title'] : __('Random Posts', 'text_domain'));
$number = !empty($instance['number']) ? absint($instance['number']) : 5;
echo $args['before_widget'];
if (!empty($title)) {
echo $args['before_title'] . $title . $args['after_title'];
}
$query_args = array('posts_per_page' => $number, 'orderby' => 'rand');
$random_posts = new WP_Query($query_args);
if ($random_posts->have_posts()) {
echo '';
while ($random_posts->have_posts()) {
$random_posts->the_post();
echo '- ' . get_the_title() . '
';
}
echo '
';
wp_reset_postdata();
} else {
echo 'No posts found.
';
}
echo $args['after_widget'];
}
function form($instance) {
$instance = wp_parse_args((array) $instance, array('title' => '', 'number' => 5));
$title = sanitize_text_field($instance['title']);
$number = absint($instance['number']);
?>
Now, when you expand the "Random Posts" widget in the admin, you will see "Title" and "Number of posts" settings. Changes saved here will update the front‑end display.
Best Practices & Considerations
- Use WP_Query, not query_posts: Always use
WP_Queryfor custom queries. Thequery_posts()function alters the main loop and can cause issues. - Escape Output: Always escape dynamic data with WordPress functions like
esc_url(),esc_attr(), andesc_html()for security. - Reset Post Data: After a custom
WP_Query, callwp_reset_postdata()to restore the main loop. - Text Domain for Internationalization: Replace
'text_domain'in__()and_e()with your theme or plugin's actual text domain. - Code Placement: Package your widget code in a standalone plugin rather than placing it directly in
functions.php. This ensures the widget remains available when you change themes.
By following these steps, you can create fully functional WordPress widgets with custom settings. You can adapt this pattern—modifying query parameters and output HTML—to build widgets for recent comments, posts from specific categories, social media links, and more.