Skip to content

Всё для WordPress, веб-разработки — и не только

🛠 Эксклюзивные таксономии в WordPress: как заменить чекбоксы на радиокнопки

🛠 Эксклюзивные таксономии в WordPress: как заменить чекбоксы на радиокнопки

По умолчанию WordPress разрешает привязать к посту сколько угодно терминов одной таксономии. Чекбоксы в сайдбаре, и всё, порядок. Но что если заказчику нужно, чтобы редактор выбрал ровно один вариант? Например, «Тип проекта», кейс, лендинг или интернет-магазин. Три позиции, и две из них вместе не имеют смысла.

Штатного переключателя «сделать таксономию эксклюзивной» в WordPress нет. Заявка в Trac висит с 2010 года без движения. Но задача решается кодом, без плагинов и лишних зависимостей.

К концу статьи вы превратите стандартный метабокс с чекбоксами в панель с радиокнопками: ровно один термин на пост, меньше ошибок редактора.

💡 Быстрый обзор:

  • Регистрируем таксономию с параметром meta_box_cb, стандартный метабокс не появится.
  • На хуке add_meta_boxes создаем собственный блок с радиокнопками.
  • Через save_post записываем выбранный термин в базу.
  • Полный код копируется в functions.php дочерней темы и работает сразу.

Что такое таксономии и зачем нужна эксклюзивность

Таксономия в WordPress, механизм группировки записей. Стандартные «Категории» и «Метки», тоже таксономии. Кастомная таксономия создается под конкретную задачу: «Тип проекта» для портфолио, «Регион» для каталога филиалов, «Вид услуги» для прайс-листа.

Встроенные категории работают как эксклюзивная таксономия: пост принадлежит ровно одной категории, если не использовать плагины-расширения. Иерархия «родитель → дочерняя» подсказывает редактору логику выбора.

А вот любая зарегистрированная через register_taxonomy() кастомная таксономия по умолчанию неэксклюзивна. Интерфейс, чекбоксы или поле с автодополнением. Для тегов и характеристик товара это правильно. Но когда каждый пост обязан иметь ровно один термин, чекбоксы становятся источником ошибок.

Решение: скрыть стандартный метабокс и отрисовать свой, с радиокнопками и строгим контролем. Это не обходной путь, а документированный API WordPress, просто растянутый на несколько шагов.

Шаг 1: регистрируем кастомную таксономию

База, функция register_taxonomy(). Код добавляется в functions.php дочерней темы или через плагин Code Snippets. Создадим таксономию project_type для стандартных записей:

1function sd_register_project_type_taxonomy() {
2 register_taxonomy(
3 'project_type',
4 'post',
5 array(
6 'label' => __( 'Тип проекта', 'textdomain' ),
7 'public' => true,
8 'show_in_rest' => true,
9 'hierarchical' => true,
10 'show_in_quick_edit' => false,
11 'meta_box_cb' => false,
12 )
13 );
14}
15add_action( 'init', 'sd_register_project_type_taxonomy' );

Ключевые параметры:

  • hierarchical => true включает древовидную структуру, как у категорий. Термины выстраиваются в иерархию: «Дизайн → Лендинг», «Разработка → Интернет-магазин».
  • show_in_rest => true делает таксономию доступной в REST API и редакторе блоков. Без этого Gutenberg не увидит метабокс.
  • meta_box_cb => false и show_in_quick_edit => false полностью убирают стандартный интерфейс выбора терминов.

После сохранения кода таксономия project_type появилась в меню «Записи». Термины добавляются через «Записи → Тип проекта», интерфейс стандартный, как для рубрик.

Шаг 2: скрываем стандартный метабокс

Фактически этот шаг уже сделан параметрами meta_box_cb и show_in_quick_edit на шаге 1. Закрепим:

  • meta_box_cb => false убирает метабокс со страницы редактирования записи.
  • show_in_quick_edit => false прячет таксономию из панели быстрого и массового редактирования.

Без них WordPress добавляет интерфейс по умолчанию: для иерархических таксономий, чекбоксы категорий, для неиерархических, поле тегов с автодополнением.

Параметры meta_box_cb и show_in_quick_edit в register_taxonomy

Термины заполняются через отдельную страницу управления:

Страница управления терминами кастомной таксономии в админке WordPress

Шаг 3: создаем кастомный метабокс с радиокнопками

Регистрируем собственный метабокс через хук add_meta_boxes. Добавьте в functions.php:

1add_action( 'add_meta_boxes', 'sd_add_project_type_meta_box' );
2
3function sd_add_project_type_meta_box() {
4 add_meta_box(
5 'project_type_box',
6 __( 'Тип проекта', 'textdomain' ),
7 'sd_render_project_type_meta_box',
8 'post',
9 'side',
10 'default'
11 );
12}

Параметры add_meta_box():

  • project_type_box, внутренний ID (произвольный, но уникальный).
  • 'Тип проекта', заголовок в админке.
  • sd_render_project_type_meta_box, функция отрисовки.
  • 'post', тип записи; можно массив для нескольких CPT.
  • 'side', боковая панель. Альтернативы: 'normal', 'advanced'.

Функция, отрисовывающая радиокнопки:

1function sd_render_project_type_meta_box( $post ) {
2 $terms = get_terms( array(
3 'taxonomy' => 'project_type',
4 'hide_empty' => false,
5 ) );
6
7 if ( empty( $terms ) || is_wp_error( $terms ) ) {
8 echo '<p>Сначала добавьте термины на странице «Тип проекта».</p>';
9 return;
10 }
11
12 $current_terms = get_the_terms( $post->ID, 'project_type' );
13 $current_id = ( ! empty( $current_terms ) && ! is_wp_error( $current_terms ) )
14 ? $current_terms[0]->term_id
15 : 0;
16
17 foreach ( $terms as $term ) : ?>
18 <label style="display:block;margin-bottom:4px;">
19 <input type="radio"
20 name="project_type_term"
21 value="<?php echo esc_attr( $term->term_id ); ?>"
22 <?php checked( $current_id, $term->term_id ); ?>>
23 <?php echo esc_html( $term->name ); ?>
24 </label>
25 <?php endforeach;
26}

Что здесь важно:

  • get_terms() с hide_empty => false возвращает все термины, включая неиспользованные.
  • get_the_terms() возвращает массив, берем первый элемент [0], так как логика гарантирует не более одного термина.
  • checked(), встроенная функция WordPress, выводит checked="checked" при совпадении.
  • esc_attr() и esc_html(), обязательное экранирование.

Результат, чистый блок с радиокнопками:

Кастомный метабокс с радиокнопками для выбора одного термина таксономии

Никаких чекбоксов, никакой возможности выбрать два варианта.

Шаг 4: сохраняем термин при сохранении поста

Без этого шага метабокс чисто декоративный. Используем хук save_post:

1add_action( 'save_post', 'sd_save_project_type_term' );
2
3function sd_save_project_type_term( $post_id ) {
4 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
5 return;
6 }
7
8 if ( ! current_user_can( 'edit_post', $post_id ) ) {
9 return;
10 }
11
12 if ( isset( $_POST['project_type_term'] ) ) {
13 $term_id = absint( $_POST['project_type_term'] );
14 wp_set_object_terms( $post_id, $term_id, 'project_type' );
15 }
16}

Детали:

  • DOING_AUTOSAVE, пропускаем автосохранения. Без этой проверки термин перезаписывается в фоне каждые 60 секунд.
  • current_user_can( 'edit_post', $post_id ), базовый контроль прав.
  • absint() приводит значение к положительному целому, безопаснее чем (int) sanitize_text_field().
  • wp_set_object_terms() с одиночным ID (не массивом) записывает ровно один термин, удаляя предыдущие связи.

Готово. Сохраните пост, радиокнопка зафиксируется. При следующем открытии выбранный термин подсвечен.

Полный код для копирования

Все четыре шага в одном фрагменте. Добавьте в functions.php дочерней темы или через Code Snippets:

1/**
2 * Эксклюзивная таксономия «Тип проекта» — один термин на пост.
3 * Добавить в functions.php дочерней темы.
4 */
5function sd_register_project_type_taxonomy() {
6 register_taxonomy(
7 'project_type',
8 'post',
9 array(
10 'label' => __( 'Тип проекта', 'textdomain' ),
11 'public' => true,
12 'show_in_rest' => true,
13 'hierarchical' => true,
14 'show_in_quick_edit' => false,
15 'meta_box_cb' => false,
16 )
17 );
18}
19add_action( 'init', 'sd_register_project_type_taxonomy' );
20
21add_action( 'add_meta_boxes', 'sd_add_project_type_meta_box' );
22
23function sd_add_project_type_meta_box() {
24 add_meta_box(
25 'project_type_box',
26 __( 'Тип проекта', 'textdomain' ),
27 'sd_render_project_type_meta_box',
28 'post',
29 'side',
30 'default'
31 );
32}
33
34function sd_render_project_type_meta_box( $post ) {
35 $terms = get_terms( array(
36 'taxonomy' => 'project_type',
37 'hide_empty' => false,
38 ) );
39
40 if ( empty( $terms ) || is_wp_error( $terms ) ) {
41 echo '<p>Сначала добавьте термины на странице «Тип проекта».</p>';
42 return;
43 }
44
45 $current_terms = get_the_terms( $post->ID, 'project_type' );
46 $current_id = ( ! empty( $current_terms ) && ! is_wp_error( $current_terms ) )
47 ? $current_terms[0]->term_id
48 : 0;
49
50 foreach ( $terms as $term ) : ?>
51 <label style="display:block;margin-bottom:4px;">
52 <input type="radio"
53 name="project_type_term"
54 value="<?php echo esc_attr( $term->term_id ); ?>"
55 <?php checked( $current_id, $term->term_id ); ?>>
56 <?php echo esc_html( $term->name ); ?>
57 </label>
58 <?php endforeach;
59}
60
61add_action( 'save_post', 'sd_save_project_type_term' );
62
63function sd_save_project_type_term( $post_id ) {
64 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
65 return;
66 }
67
68 if ( ! current_user_can( 'edit_post', $post_id ) ) {
69 return;
70 }
71
72 if ( isset( $_POST['project_type_term'] ) ) {
73 $term_id = absint( $_POST['project_type_term'] );
74 wp_set_object_terms( $post_id, $term_id, 'project_type' );
75 }
76}

⚠️ Перед вставкой сделайте полный бэкап сайта. Код проверен на актуальной версии WordPress с классическим редактором. В Gutenberg кастомный метабокс отображается в боковой панели без изменений.

На практике мы ставили этот сниппет на три проекта, везде взлетело без правок. Если у вас иерархический тип записи или несколько ролей редакторов, замените 'post' на свой slug и сверьте права в save_post.


WordPress Trac с 2010 года висит заявка #14877 на нативную поддержку эксклюзивных таксономий. Появится параметр 'exclusive' => true в ядре, вся конструкция сожмется до одной строчки. Пока заявка без движения, подход с радиокнопками остается основным.

А ниже, короткое видео по теме, чтобы увидеть процесс глазами:

⁉️🤔 Частые вопросы

Работает ли это в Gutenberg?

Да. Параметр show_in_rest => true при регистрации таксономии включает совместимость с редактором блоков. Кастомный метабокс появляется в боковой панели документа, радиокнопки отображаются и сохраняются корректно.

Можно ли применить подход к произвольным типам записей?

Да. Замените 'post' в register_taxonomy() и add_meta_box() на slug вашего CPT. Остальной код, название таксономии, label, параметры, не меняется. Подход работает с любым зарегистрированным типом записи, включая созданные через ACF или Custom Post Type UI.

Что произойдет, если редактор не выберет ни один термин?

Пост сохранится без привязанного термина, save_post просто не вызовет wp_set_object_terms(). Если обязательный выбор критичен, добавьте JavaScript-валидацию в админке или проверку через хук pre_post_update.

Почему не использовать плагин Radio Buttons for Taxonomies?

Можно. Плагин решает задачу без кода, 100 000+ активных установок. Минус, еще одна зависимость. Для одного-двух сайтов плагин оправдан. Для агентств и мультисайтов код в теме даёт полный контроль без лишних обновлений и конфликтов.

Как добавить иерархический выбор (родитель → дочерняя)?

Иерархические таксономии (hierarchical => true) получают структуру родитель-потомок автоматически. Чтобы отобразить иерархию в кастомном метабоксе, замените get_terms() на wp_dropdown_categories() с параметром 'taxonomy' => 'project_type', функция выведет select с отступами.

Что в итоге: когда стоит заморачиваться с кодом

Эксклюзивная таксономия нужна не каждому проекту. Если редакторы понимают логику сайта и не ошибаются при выборе, стандартных чекбоксов хватает. Но когда цена ошибки высока (лендинг в портфолио ошибочно отмечен и как «интернет-магазин», и как «кейс»), полчаса на код окупаются чистотой контента.

  • Если у вас один сайт и нет разработчика, поставьте Radio Buttons for Taxonomies. Работает без кода.
  • Если агентство или мультисайт, скопируйте код выше в базовую тему. Меньше плагинов, меньше точек отказа при обновлениях.
  • Если сайт на чистом Gutenberg, оцените, нужна ли таксономия вообще. Возможно, хватит поля ACF с радиокнопками: меньше сущностей, быстрее админка.

Начните с тестового сайта. Зарегистрируйте таксономию, добавьте три термина и создайте пост, интерфейс заработает сразу. Напишите в комментариях, для какой задачи вы использовали эксклюзивную таксономию.