
🛠 Эксклюзивные таксономии в 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 для стандартных записей:
1 function 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 } 15 add_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 добавляет интерфейс по умолчанию: для иерархических таксономий, чекбоксы категорий, для неиерархических, поле тегов с автодополнением.

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

Шаг 3: создаем кастомный метабокс с радиокнопками
Регистрируем собственный метабокс через хук add_meta_boxes. Добавьте в functions.php:
1 add_action( 'add_meta_boxes', 'sd_add_project_type_meta_box' ); 2 3 function 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'.
Функция, отрисовывающая радиокнопки:
1 function 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:
1 add_action( 'save_post', 'sd_save_project_type_term' ); 2 3 function 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 */ 5 function 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 } 19 add_action( 'init', 'sd_register_project_type_taxonomy' ); 20 21 add_action( 'add_meta_boxes', 'sd_add_project_type_meta_box' ); 22 23 function 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 34 function 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 61 add_action( 'save_post', 'sd_save_project_type_term' ); 62 63 function 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 с радиокнопками: меньше сущностей, быстрее админка.
Начните с тестового сайта. Зарегистрируйте таксономию, добавьте три термина и создайте пост, интерфейс заработает сразу. Напишите в комментариях, для какой задачи вы использовали эксклюзивную таксономию.



