Таксономии WordPress: разбор фильтров на живом каталоге
Фильтр каталога на вложенных рубриках и полях ACF ломается на втором признаке. Разбираем на живом проекте, когда нужна своя таксономия, когда мета-поле и чем каждый выбор платит в запросах и в админке.

Каталог оборудования на WordPress 6.x: около четырёх тысяч записей, фильтр по шести признакам — бренд, тип инструмента, назначение, питание, страна сборки, наличие. Первая версия собрана на вложенных рубриках плюс полях ACF, и ровно здесь каталог встал. Фильтр по двум признакам одновременно отдаёт пустую страницу, по трём — наоборот, всё подряд, а контент-менеджер заводит бренд Bosch отдельно внутри «Перфораторов» и отдельно внутри «Шлифмашин», потому что иначе товар не попадает в нужный раздел.
Что именно сломалось в первой версии
Рубрика — это одна ось классификации. Вложенность в ней означает «часть целого», а не «ещё один признак». Как только признаков становится больше одного, иерархия начинает их перемешивать, и дальше ломается всё по очереди.
- Дубли терминов. Бренд существует столько раз, сколько у него разделов. Собрать все записи одного бренда в один архив нечем: у каждого дубля свой term_id и свой URL.
- Потомки в выборке. В
tax_queryпараметрinclude_childrenдля иерархической таксономии по умолчанию включён. Запрос по родительской рубрике честно тянет всё поддерево — поэтому «Перфораторы» и выдают половину каталога. - Фильтр по мета-полям. Признаки из ACF лежат в
wp_postmeta, а там есть индексы поpost_idиmeta_key, поmeta_value— нет. Плюс значения никто не нормализовал: «220В», «220 В» и «220v» для базы три разных строки.
Фасетный поиск с подсчётом количества в каждом чекбоксе — отдельная тема, здесь разбираем только структуру данных и саму выборку.
Четыре способа описать признак записи
Встроенные рубрики и метки
Выигрыш: нулевой код, знакомая админка, рабочие архивы и RSS из коробки. Цена: одна иерархия на весь сайт и общее пространство с блогом — рубрика «Новости» и рубрика «Перфораторы» живут в одном дереве. Метки тут честнее рубрик, но они плоские и не дают ни порядка, ни описания раздела, которое нужно для SEO-текста на странице фильтра.
Своя таксономия под каждую ось
Шесть признаков — шесть таксономий, каждая со своим набором терминов и своим префиксом в URL. Регистрация занимает двадцать строк и делается только на хуке init:
<?php
// WordPress 6.x, PHP 8.1. Файл подключается из плагина или functions.php темы.
add_action( 'init', 'catalog_register_brand_taxonomy' );
function catalog_register_brand_taxonomy(): void {
register_taxonomy(
'catalog_brand', // имя таксономии: латиница, до 32 символов
[ 'equipment' ], // к каким типам записей цепляем
[
'labels' => [
'name' => 'Бренды',
'singular_name' => 'Бренд',
],
'public' => true,
'hierarchical' => false, // бренд не вкладывается в бренд
'show_in_rest' => true, // без этого нет панели в блочном редакторе и REST
'show_admin_column' => true, // колонка в списке записей
'query_var' => true,
'rewrite' => [
'slug' => 'brand',
'with_front' => false,
],
]
);
}
Теперь фильтр по двум осям — это один запрос, где каждый блок tax_query отвечает за свой признак:
// Выдача фильтра: бренд (любой из двух) и тип (строго один, без потомков)
$query = new WP_Query( [
'post_type' => 'equipment',
'posts_per_page' => 24,
'no_found_rows' => true, // пагинацию считаем отдельно, SQL_CALC_FOUND_ROWS не нужен
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => 'catalog_brand',
'field' => 'slug',
'terms' => [ 'bosch', 'makita' ],
'operator' => 'IN',
],
[
'taxonomy' => 'catalog_type',
'field' => 'slug',
'terms' => [ 'perforator' ],
'include_children' => false,
],
],
] );
Цена: каждый блок tax_query добавляет свой JOIN к wp_term_relationships, и на шести осях запрос становится тяжёлым. Если фронтенд уже знает идентификаторы, передавайте 'field' => 'term_taxonomy_id' — тогда WordPress не ходит за терминами по slug и строит на один стык таблиц меньше:
[
'taxonomy' => 'catalog_brand',
'field' => 'term_taxonomy_id',
'terms' => [ 42, 57 ],
]
Мета-поле вместо таксономии
Выигрыш: значение хранится как значение, а не как ссылка на термин. Для числа это принципиально: мощность 1500 Вт можно сравнить с диапазоном, термин «1500 Вт» — нельзя. Цена: нет архива, нет URL, нет описания, а фильтр по meta_query на больших таблицах упирается в отсутствие индекса по meta_value. Компромисс, который работает на живых каталогах: число остаётся в мета-поле для сортировки и диапазонов, а для фильтра создаются термины-ведра («до 800 Вт», «800–1500 Вт»), которые проставляются автоматически при сохранении записи.
Отдельный тип записи плюс таксономия-зеркало
Нужен, когда у признака есть собственная карточка: логотип бренда, условия гарантии, список сервисных центров. Тогда бренд — это запись типа brand, а для фильтрации рядом живёт термин таксономии с тем же slug. Выигрыш: полноценная страница бренда и редактирование силами менеджера. Цена: два объекта на одну сущность и обязательная синхронизация при переименовании. Без неё через полгода у вас «Makita» в карточке и «Makita Corp» в фильтре.
Таксономия отвечает на вопрос «что ещё похоже на эту запись». Мета-поле отвечает на вопрос «что известно про эту запись». Как только вы фильтруете список по мета-полю, вы платите за то, что задали не тот вопрос.
Сравнение подходов
| Подход | Фильтр по нескольким осям | Свой URL и архив | Цена выборки | Админка |
|---|---|---|---|---|
| Рубрики и метки | Плохо: одна иерархия, дубли терминов | Есть, но в общем пространстве с блогом | JOIN на каждый блок, потомки по умолчанию в выборке | Привычная, кода нет |
| Своя таксономия на ось | Нативно: один блок tax_query на признак | Есть, с отдельным префиксом | JOIN на каждую ось, снижается через term_taxonomy_id | Отдельный метабокс и колонка в списке |
| Мета-поле | Только через meta_query, диапазоны и сортировка — да | Нет | Нет индекса по meta_value, деградирует с объёмом | Зависит от плагина полей |
| CPT плюс зеркало | Да, фильтр идёт по таксономии | Полноценная страница сущности | Как у таксономии, плюс запрос за карточкой | Две точки редактирования, нужна синхронизация |
Когда что выбирать
Критерий простой и проверяется до начала работы, а не после релиза.
- Значение повторяется у многих записей и по нему фильтруют или строят отдельную страницу — таксономия.
- Значение уникально для записи: артикул, цена, вес, дата поставки — мета-поле.
- Нужны диапазоны и сортировка по числу — мета-поле, а для чекбоксов фильтра дублируйте в термины-ведра.
- Признаков несколько и они независимы — отдельная таксономия на каждый, а не вложенность в одну.
- У признака есть описание, картинка и свои поля — отдельный тип записи плюс таксономия для выборки.
Что ломается на практике
- Архив таксономии отдаёт 404. Правила перезаписи кешируются в базе. После регистрации зайдите в «Настройки — Постоянные ссылки» и сохраните их, либо вызовите сброс правил один раз при активации плагина, а не на каждом
init. - Slug таксономии совпал со slug страницы. Если есть страница
/brand/, архив бренда с тем же префиксом начнёт проигрывать ей или выдавать не то. Проверяйте пересечения до запуска. - Регистрация не на init. Вызов
register_taxonomyиз обработчика шаблона или слишком рано приводит к Notice: Function register_taxonomy was called incorrectly и к тому, что таксономии нет в REST. - show_in_rest забыли. Блочный редактор берёт панели таксономий через REST. Без флага панель просто не появится, хотя в классическом редакторе всё работало.
- Тысячи терминов в метабоксе. Чекбоксы для плоской таксономии на несколько тысяч значений грузят экран редактирования целиком. Для таких осей нужен свой метабокс через
meta_box_cbс поиском, а не стандартный. - Терм удалён, записи остались. Удаление термина не трогает записи, оно только разрывает связь. Фильтр после этого молча отдаёт пустоту вместо ошибки, поэтому перед удалением смотрите счётчик записей у термина.
Что делать дальше
Первый шаг — перенос существующих связей. Разовый скрипт под WP-CLI, пачками по двести записей, с true в конце wp_set_object_terms, чтобы добавлять термины, а не затирать уже проставленные:
// Разовая миграция: из рубрики в таксономию бренда. Запускать через WP-CLI.
$post_ids = get_posts( [
'post_type' => 'equipment',
'posts_per_page' => 200,
'fields' => 'ids',
'cat' => $legacy_category_id,
] );
foreach ( $post_ids as $post_id ) {
$term = term_exists( 'Bosch', 'catalog_brand' );
if ( ! $term ) {
$term = wp_insert_term( 'Bosch', 'catalog_brand', [ 'slug' => 'bosch' ] );
}
if ( is_wp_error( $term ) ) {
continue;
}
wp_set_object_terms( $post_id, (int) $term['term_id'], 'catalog_brand', true );
}
Дальше по порядку: настроить 301 с URL старых рубрик на новые архивы и не удалять рубрики, пока редиректы не проверены; поставить Query Monitor и посмотреть, сколько JOIN и миллисекунд даёт реальный запрос фильтра на вашем хостинге; включить объектный кеш, чтобы запросы терминов не ходили в базу на каждый чекбокс. Когда осей станет больше шести, смотрите в сторону внешнего поискового индекса — это уже другая архитектура и отдельный разговор.
term_taxonomy_id — идентификатор связки «термин плюс таксономия» в таблице wp_term_taxonomy. Один и тот же термин может быть привязан к разным таксономиям, поэтому записи в wp_term_relationships ссылаются именно на term_taxonomy_id, а не на term_id. Отсюда и выигрыш при фильтрации по этому полю: лишний стык таблиц не нужен.
wp_postmeta — таблица произвольных полей записи, где каждое значение лежит отдельной строкой в формате «ключ — значение». Именно с ней работают ACF и большинство плагинов полей.
Разбор научной статьи: Сложный текст — понятным языком, с терминами
Спросить за 12 ₽Источники: register_taxonomy() — WordPress Developer Resources, WP_Query: Taxonomy Parameters — WordPress Developer Resources
Частые вопросы
Чем таксономия отличается от мета-поля, если и то и другое можно вывести в фильтре?
Зарегистрировал таксономию, а её архив отдаёт 404. Что проверить?
У меня WordPress 5.x, а не 6.x. Что из этого не сработает?
Сколько терминов выдержит одна таксономия?
Можно ли переехать с рубрик на свою таксономию без потери трафика?
Фильтр по шести осям работает, но медленно. Что смотреть в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.