Widgets are among the most useful features of WordPress, allowing you to add dynamic content and functionality to different areas of your site (sidebars, footers, and custom widget areas). If you install WordPress, it comes with several default widgets (categories, recent posts, search, etc.), but you may need or want custom widgets or custom widget areas for better control of the design of your site.

Creating custom widgets in WordPress allows you to display unique content in your sidebars, footers, or other widget areas. We’ll create a simple Custom Image Link Widget that shows an image with an optional title and link, and also explain how to register a custom sidebar.
How to Add a Custom Widget and Widget Sidebar in WordPress Widget Area?
We need to use register_sidebar() of WordPress to register a custom sidebar in the widgets area, and to register a custom widget, we need to use register_widget().
We register this inside functions.php of the theme.
Code:
// Register a custom sidebar
add_action('widgets_init', 'my_custom_sidebar');
function my_custom_sidebar()
{
register_sidebar([
'name' => __('My Custom Sidebar', 'mc-theme'),
'id' => 'my-custom-sidebar',
'description' => __('A special sidebar for custom widgets', 'mc-theme'),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
]);
}
// Register widget
add_action('widgets_init', function () {
register_widget('Mc_Theme_Custom_Widget');
});
class Mc_Theme_Custom_Widget extends WP_Widget
{
public function __construct()
{
parent::__construct(
'mc_theme_custom_widget',
__('Custom Profile Widget', 'mc-theme'),
['description' => __('Displays an image with a title and link.', 'mc-theme')]
);
add_action('admin_enqueue_scripts', [$this, 'enqueue_media']);
add_action('admin_footer-widgets.php', [$this, 'media_picker_js']);
}
public function enqueue_media($hook)
{
if (in_array($hook, ['widgets.php', 'customize.php'], true)) {
wp_enqueue_media();
wp_enqueue_script('jquery');
}
}
public function media_picker_js()
{
?>
<script>
jQuery(document).ready(function($) {
function initMediaButtons(container) {
// Select Image
container.find('.mc-theme-custom-widget-button').off('click').on('click', function(e) {
e.preventDefault();
var btn = $(this),
input = container.find('.mc-theme-custom-widget-id');
var frame = wp.media({
title: '<?php echo esc_js(__('Select Image', 'mc-theme')); ?>',
button: {
text: '<?php echo esc_js(__('Use Image', 'mc-theme')); ?>'
},
multiple: false,
library: {
type: ['image']
}
});
frame.on('select', function() {
var attachment = frame.state().get('selection').first().toJSON();
input.val(attachment.id).trigger('change');
btn.after('<img class="mc-theme-custom-widget-preview" src="' + attachment.url.replace(/"/g, '"') + '" style="max-width:150px;margin-top:5px;display:block;" alt="<?php echo esc_js(__('Image preview', 'mc-theme')); ?>">');
btn.after('<button class="button mc-theme-custom-widget-remove" type="button" style="margin-top:5px;"><?php echo esc_js(__('Remove Image', 'mc-theme')); ?></button>');
btn.remove();
initMediaButtons(container);
});
frame.open();
});
// Remove Image
container.find('.mc-theme-custom-widget-remove').off('click').on('click', function(e) {
e.preventDefault();
var btn = $(this),
input = container.find('.mc-theme-custom-widget-id');
input.val('').trigger('change');
container.find('img.mc-theme-custom-widget-preview, .mc-theme-custom-widget-remove').remove();
if (!container.find('.mc-theme-custom-widget-button').length) {
input.after('<button class="button mc-theme-custom-widget-button" type="button" style="margin-top:5px;"><?php echo esc_js(__('Select Image', 'mc-theme')); ?></button>');
initMediaButtons(container);
}
});
}
$('.mc-theme-custom-widget-container').each(function() {
initMediaButtons($(this));
});
$(document).on('widget-added widget-updated', function(e, widget) {
$(widget).find('.mc-theme-custom-widget-container').each(function() {
initMediaButtons($(this));
});
});
});
</script>
<?php
}
public function widget($args, $instance)
{
echo $args['before_widget'];
$title = !empty($instance['title']) ? esc_html($instance['title']) : '';
$link = !empty($instance['link_url']) ? esc_url($instance['link_url']) : '';
$image_id = !empty($instance['image_id']) ? intval($instance['image_id']) : 0;
if ($title) {
echo $args['before_title'] . $title . $args['after_title'];
}
if ($image_id && $img_src = wp_get_attachment_url($image_id)) {
$img_alt = esc_attr($title ?: __('Image link', 'mc-theme'));
$img_tag = '<img src="' . $img_src . '" style="max-width:100%;" alt="' . $img_alt . '">';
echo $link ? '<a href="' . $link . '" target="_blank" style="display: flex; justify-content: center;">' . $img_tag . '</a>' : $img_tag;
}
echo $args['after_widget'];
}
public function form($instance)
{
$title = !empty($instance['title']) ? esc_attr($instance['title']) : '';
$link = !empty($instance['link_url']) ? esc_attr($instance['link_url']) : '';
$image_id = !empty($instance['image_id']) ? intval($instance['image_id']) : 0;
$field_id = fn($name) => esc_attr($this->get_field_id($name));
$field_name = fn($name) => esc_attr($this->get_field_name($name));
?>
<p>
<label for="<?php echo $field_id('title'); ?>"><?php _e('Title:', 'mc-theme'); ?></label>
<input class="widefat" id="<?php echo $field_id('title'); ?>" name="<?php echo $field_name('title'); ?>" type="text" value="<?php echo $title; ?>" aria-describedby="<?php echo $field_id('title'); ?>-desc">
<small id="<?php echo $field_id('title'); ?>-desc" class="form-text text-muted"><?php _e('Optional title for the widget.', 'mc-theme'); ?></small>
</p>
<p>
<label for="<?php echo $field_id('link_url'); ?>"><?php _e('Link URL:', 'mc-theme'); ?></label>
<input class="widefat" id="<?php echo $field_id('link_url'); ?>" name="<?php echo $field_name('link_url'); ?>" type="url" value="<?php echo $link; ?>" aria-describedby="<?php echo $field_id('link_url'); ?>-desc">
<small id="<?php echo $field_id('link_url'); ?>-desc" class="form-text text-muted"><?php _e('Optional URL for the image link.', 'mc-theme'); ?></small>
</p>
<p>
<label for="<?php echo $field_id('image_id'); ?>"><?php _e('Image:', 'mc-theme'); ?></label>
<div class="mc-theme-custom-widget-container">
<input class="widefat mc-theme-custom-widget-id" id="<?php echo $field_id('image_id'); ?>" name="<?php echo $field_name('image_id'); ?>" type="hidden" value="<?php echo esc_attr($image_id); ?>">
<?php if ($image_id): ?>
<?php echo wp_get_attachment_image($image_id, [150, 150], false, ['class' => 'mc-theme-custom-widget-preview', 'style' => 'max-width:150px;margin-top:5px;display:block;']); ?>
<button class="button mc-theme-custom-widget-remove" type="button" style="margin-top:5px;"><?php _e('Remove Image', 'mc-theme'); ?></button>
<?php else: ?>
<button class="button mc-theme-custom-widget-button" type="button" style="margin-top:5px;"><?php _e('Select Image', 'mc-theme'); ?></button>
<?php endif; ?>
</div>
<small id="<?php echo $field_id('image_id'); ?>-desc" class="form-text text-muted"><?php _e('Select an image to display.', 'mc-theme'); ?></small>
</p>
<?php
}
public function update($new_instance, $old_instance)
{
return [
'title' => !empty($new_instance['title']) ? sanitize_text_field($new_instance['title']) : '',
'link_url' => !empty($new_instance['link_url']) ? esc_url_raw($new_instance['link_url']) : '',
'image_id' => !empty($new_instance['image_id']) ? intval($new_instance['image_id']) : 0
];
}
}
For convenience, the widget class, media script, and sidebar registration are all included in the theme’s functions.php; you can create separate files for better management.
Output:
Go to Appearance→Widgets area, where you can find the custom widget.

You can add details like title, link, and content image.

You can also find the registered sidebar in Appearance→Widgets area. It’s currently empty, but you can insert any widget here. You can use this sidebar in any theme template to show content.

Frontend display of a sidebar registered in the theme, where our custom-created widget is used.

Final Thoughts
Adding a custom widget and custom sidebar in WordPress gives you flexibility to extend your site’s functionality. You can use this method to add custom ads, special announcements, or any other feature without editing core theme files.
FAQ
- What is a widget in WordPress?
A widget is a small block of content or functionality (like recent posts, search, categories, etc.) that you can place in designated areas of your site, such as sidebars and footers.
- What is a widget area in WordPress?
A widget area (or sidebar) is an area in your theme that allows you to add widgets, such as a blog sidebar or a footer area.
- What are WordPress widgets used for?
Widgets are used to easily add items, like search bars, recent posts, links to social media profiles, or other custom content to specific areas of a WordPress site without changing code.
- Can I add multiple custom sidebars to my WordPress site?
Yes, you can register as many sidebars (widget areas) as you want by continuously repeating the register_sidebar() function using different IDs and names.
- Where can I place widgets on my WordPress site?
You can put widgets in any widget-ready area like sidebars, headers, footers, or any other custom widget area your theme or plugin has provided.