Виджеты WordPress на примере: блок или свой класс WP_Widget
Нужен список свежих записей из одной рубрики, который редактор настраивает сам. Разбираем три способа в WordPress 6.x: готовый блок, свой класс WP_Widget и вывод в шаблоне, и считаем, чем платит каждый.

Задача: в сайдбаре нужен список из пяти свежих записей одной рубрики, и рубрику должен менять редактор, не трогая код. С WordPress 5.8 экран «Внешний вид → Виджеты» стал блочным, поэтому часть старых инструкций описывает интерфейс, которого вы уже не увидите. Ниже три способа решить задачу, цена каждого и рабочий код для самого гибкого из них.
Три способа и их цена
- Готовый блок «Последние записи» в области виджетов. Кода ноль, число записей и фильтр по рубрике настраиваются в панели блока (наличие фильтра проверьте в своей версии). Платите ограниченной разметкой: нестандартный вывод получится только через CSS.
- Свой класс на WP_Widget в плагине. Любая логика, любая вёрстка, своя форма настроек. Платите PHP, который вы пишете, экранируете и поддерживаете сами.
- WP_Query прямо в шаблоне темы. Быстро, но рубрика зашита в код, редактор ничего не поменяет, а при смене темы список исчезнет.
| Подход | Правит редактор | Переживает смену темы | Свой вывод |
|---|---|---|---|
| Блок «Последние записи» | Да | Да | Только CSS |
| Класс WP_Widget | Да, через вашу форму | Да, пока активен плагин | Любой |
| Код в шаблоне | Нет | Нет | Любой |
Где живёт виджет
Виджет состоит из двух частей: данных в базе и кода, который их показывает. Размещение виджетов по областям хранится в опции sidebars_widgets, а настройки экземпляров1 лежат в опции widget_ плюс идентификатор виджета. Для нашего примера это widget_myblog_category_posts.
Виджет в WordPress: это запись в wp_options плюс класс, который умеет её показать. Всё остальное интерфейс.
Из этого следует практический вывод: если плагин с классом отключить, настройки останутся в базе, но на сайте виджет пропадёт.
Подход 2 на примере
Код ниже рассчитан на WordPress 6.x и PHP 7.4 и выше. Положите его в файл wp-content/plugins/myblog-category-posts/myblog-category-posts.php и активируйте плагин. Область виджетов2 должна быть зарегистрирована темой через register_sidebar и выведена через dynamic_sidebar.
<?php
/**
* Plugin Name: Свежее из рубрики
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Myblog_Category_Posts_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'myblog_category_posts',
'Свежее из рубрики',
array( 'customize_selective_refresh' => true )
);
}
// Вывод на сайте
public function widget( $args, $instance ) {
$cat_id = isset( $instance['cat_id'] ) ? absint( $instance['cat_id'] ) : 0;
if ( ! $cat_id ) {
return;
}
// no_found_rows убирает лишний COUNT-запрос: пагинация не нужна
$query = new WP_Query( array(
'cat' => $cat_id,
'posts_per_page' => 5,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
) );
if ( ! $query->have_posts() ) {
return;
}
echo $args['before_widget'];
if ( ! empty( $instance['title'] ) ) {
$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
}
echo '<ul>';
while ( $query->have_posts() ) {
$query->the_post();
printf( '<li><a href=\"%s\">%s</a></li>', esc_url( get_permalink() ), esc_html( get_the_title() ) );
}
echo '</ul>';
wp_reset_postdata();
echo $args['after_widget'];
}
// Форма в админке
public function form( $instance ) {
$title = isset( $instance['title'] ) ? $instance['title'] : '';
$cat_id = isset( $instance['cat_id'] ) ? absint( $instance['cat_id'] ) : 0;
?>
<p>
<label for='<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>'>Заголовок</label>
<input class='widefat' type='text'
id='<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>'
name='<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>'
value='<?php echo esc_attr( $title ); ?>'>
</p>
<p>
<label for='<?php echo esc_attr( $this->get_field_id( 'cat_id' ) ); ?>'>Рубрика</label>
<?php
wp_dropdown_categories( array(
'show_option_none' => 'Выберите рубрику',
'option_none_value' => 0,
'name' => $this->get_field_name( 'cat_id' ),
'id' => $this->get_field_id( 'cat_id' ),
'selected' => $cat_id,
'class' => 'widefat',
) );
?>
</p>
<?php
}
// Очистка данных перед сохранением
public function update( $new_instance, $old_instance ) {
return array(
'title' => sanitize_text_field( $new_instance['title'] ),
'cat_id' => absint( $new_instance['cat_id'] ),
);
}
}
add_action( 'widgets_init', function () {
register_widget( 'Myblog_Category_Posts_Widget' );
} );
Проверка: на экране «Виджеты» в списке блоков найдите «Свежее из рубрики» (в блочном экране классический виджет добавляется через блок Legacy Widget), выберите рубрику, сохраните и откройте сайт. В сайдбаре должны появиться пять ссылок.
Три места, где этот подход отличается от блока. Имена полей берите только из get_field_name: по ним WordPress находит данные в $new_instance. Всё, что пришло от пользователя, чистит update, а при выводе данные экранируются ещё раз. Изменение схемы (например, добавить число записей) требует правки и формы, и update, и widget.
Когда что выбирать
- Нужен стандартный список с рубрикой и числом записей: берите блок. Поддерживать нечего, обновления ядра закрывают его сами.
- Нужны мета-поля, особая вёрстка, нестандартная выборка или один и тот же виджет на нескольких сайтах: пишите класс.
- Код в шаблоне оправдан только для фиксированного элемента темы, который редактор менять не будет. Даже тогда сначала проверьте, не закрывает ли задачу блок.
Что ломается
- Виджета нет в списке. Плагин не активирован,
register_widgetвызван не наwidgets_initили в файле фатальная ошибка. ВключитеWP_DEBUGи смотрите журнал. - Настройки не сохраняются. Поля названы вручную, а не через
get_field_name, либоupdateне возвращает массив. - Предупреждение «Undefined array key» в PHP 8. Код обращается к ключу экземпляра, которого ещё нет у нового виджета. В PHP 7.4 это было уведомление «Undefined index». Лечится проверкой
isset, как в примере. - Виджеты ушли в «Неактивные» после смены темы. Области принадлежат теме. Новая тема регистрирует свои идентификаторы, и WordPress не всегда может сопоставить их со старыми.
- Блочная тема. В темах на полном редактировании сайта (например, Twenty Twenty-Three) области виджетов по умолчанию нет, сайдбар собирается из блоков в редакторе сайта. Проверьте, регистрирует ли ваша тема области вообще.
- Старый экран нужен любой ценой. Блочный экран отключается фильтром
use_widgets_block_editorсо значениемfalse. Это временная мера, а не стратегия.
Что делать дальше
- Поставьте Query Monitor и сравните число запросов к базе на странице с виджетом и без него.
- Если виджетов с запросами несколько, а полностраничного кеша нет, кешируйте результат. Это отдельная тема, здесь она не разбирается.
- Прочитайте справку по классу WP_Widget для вашей версии WordPress, там перечислены актуальные параметры конструктора.
1 Экземпляр (instance): набор сохранённых настроек одного виджета. Один класс может стоять в сайдбаре несколько раз, у каждого раза свой экземпляр.
2 Область виджетов (sidebar): зарегистрированное темой место вывода. Название вводит в заблуждение: область не обязана быть боковой колонкой, это может быть и подвал.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: WordPress Developer Resources: класс WP_Widget, WordPress Theme Handbook: Widgets
Частые вопросы
У меня WordPress ниже 5.8 или классический экран виджетов. Код сработает?
Виджет не появился в списке или настройки не сохраняются. С чего начать?
Как вернуть старый экран виджетов?
Не замедлит ли такой виджет сайт в проде?
Работает ли классический виджет в блочной теме?
Что будет с настройками, если отключить плагин с виджетом?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.