Разработка кастомных виджетов WordPress
Виджеты WordPress — блоки контента, размещаемые в зарегистрированных областях сайдбаров и футеров через интерфейс «Внешний вид → Виджеты». Кастомный виджет нужен тогда, когда стандартные виджеты (текст, категории, меню) не покрывают задачу: вывести последние проекты с фильтром, показать форму подписки с конкретной логикой или отрендерить виджет из стороннего API.
Разработка одного виджета занимает 4–8 часов.
Базовая структура класса виджета
Любой кастомный виджет расширяет WP_Widget:
class My_Projects_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'my_projects_widget', // ID виджета
'Последние проекты', // Название в интерфейсе
[
'description' => 'Выводит последние проекты с фильтром по категории',
'customize_selective_refresh' => true,
]
);
}
// Фронтенд: что видит посетитель
public function widget(array $args, array $instance): void {
$count = absint($instance['count'] ?? 3);
$category = sanitize_title($instance['category'] ?? '');
echo $args['before_widget'];
if (!empty($instance['title'])) {
echo $args['before_title'] . apply_filters('widget_title', esc_html($instance['title'])) . $args['after_title'];
}
$query_args = [
'post_type' => 'project',
'posts_per_page' => $count,
'post_status' => 'publish',
];
if ($category) {
$query_args['tax_query'] = [[
'taxonomy' => 'project_category',
'field' => 'slug',
'terms' => $category,
]];
}
$projects = new WP_Query($query_args);
if ($projects->have_posts()) {
echo '<ul class="projects-widget">';
while ($projects->have_posts()) {
$projects->the_post();
printf(
'<li><a href="%s">%s</a></li>',
esc_url(get_permalink()),
esc_html(get_the_title())
);
}
wp_reset_postdata();
echo '</ul>';
}
echo $args['after_widget'];
}
// Форма настроек в /wp-admin
public function form(array $instance): void {
$title = esc_attr($instance['title'] ?? 'Проекты');
$count = absint($instance['count'] ?? 3);
$category = esc_attr($instance['category'] ?? '');
printf(
'<p><label for="%1$s">Заголовок:</label>
<input class="widefat" id="%1$s" name="%2$s" type="text" value="%3$s"></p>',
$this->get_field_id('title'),
$this->get_field_name('title'),
$title
);
printf(
'<p><label for="%1$s">Количество:</label>
<input class="tiny-text" id="%1$s" name="%2$s" type="number" min="1" max="20" value="%3$d"></p>',
$this->get_field_id('count'),
$this->get_field_name('count'),
$count
);
// Выпадающий список категорий проектов
$categories = get_terms(['taxonomy' => 'project_category', 'hide_empty' => false]);
echo '<p><label for="' . $this->get_field_id('category') . '">Категория:</label>';
echo '<select class="widefat" id="' . $this->get_field_id('category') . '" name="' . $this->get_field_name('category') . '">';
echo '<option value="">Все</option>';
foreach ($categories as $cat) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr($cat->slug),
selected($category, $cat->slug, false),
esc_html($cat->name)
);
}
echo '</select></p>';
}
// Сохранение настроек
public function update(array $new_instance, array $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title']),
'count' => absint($new_instance['count']),
'category' => sanitize_title($new_instance['category']),
];
}
}
Регистрация виджета
add_action('widgets_init', function () {
register_widget('My_Projects_Widget');
});
Регистрация области под виджеты (sidebar)
Если тема не имеет нужной области, добавляем через register_sidebar():
add_action('widgets_init', function () {
register_sidebar([
'name' => 'Сайдбар блога',
'id' => 'blog-sidebar',
'description' => 'Виджеты в сайдбаре страниц блога',
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
]);
});
before_widget и after_widget — обёртки, которые получает виджет в $args. Тема контролирует HTML-структуру, не виджет.
Виджет с AJAX-обновлением
Для виджетов, которые должны обновляться без перезагрузки (например, счётчик или живой поиск):
public function widget(array $args, array $instance): void {
$widget_id = $this->id;
echo $args['before_widget'];
echo '<div class="live-counter" data-widget-id="' . esc_attr($widget_id) . '">';
echo $this->render_counter(); // начальный рендер
echo '</div>';
echo $args['after_widget'];
}
document.querySelectorAll('.live-counter').forEach(el => {
setInterval(() => {
fetch(wpData.ajaxUrl + '?action=refresh_counter&widget=' + el.dataset.widgetId)
.then(r => r.json())
.then(data => { el.innerHTML = data.html; });
}, 30000);
});
Виджеты в блочном редакторе
WordPress 5.8+ позволяет управлять виджетами через блочный интерфейс. Классические WP_Widget отображаются как «Legacy Widgets». Для полноценной поддержки Gutenberg-редактора виджетов нужно зарегистрировать блок, который использует те же данные, — это отдельная задача (см. услугу «Разработка кастомных блоков Gutenberg»).
Если сайт использует Classic Widgets (плагин), описанный подход работает без ограничений.







