Метаполя в WordPress: четыре подхода и цена каждого
Десяток характеристик у карточки, фильтр по двум из них — и meta_query начинает тянуть за собой JOIN за JOIN. Разбираем нативные метабоксы, ACF и аналоги, таксономии и свою таблицу: что выигрываете и чем платите.

Задача звучит просто: у карточки объекта нужно хранить цену, город, площадь и ещё десяток характеристик, а потом фильтровать выдачу по двум-трём из них. Очевидный путь — поставить ACF, накидать поля, отдать фильтр в meta_query. На сотне записей это летает. На двадцати тысячах каждое условие фильтра превращается в отдельный JOIN к wp_postmeta, где индексы есть по post_id и meta_key, но не по значению. Выбор подхода к метаполям — это выбор, который дешевле сделать до запуска, а не после. Всё ниже — про WordPress 6.x.
Что именно вы выбираете
Под словом «подход» прячутся три независимых решения, и их полезно разделить.
- Интерфейс редактора. Кто и как вводит значение: метабокс, написанный руками, панель плагина, боковая панель блочного редактора.
- Способ чтения.
get_post_meta(), API плагина, REST, свой запрос. - Способ фильтрации. Вот здесь и живёт основная цена. Три из четырёх подходов ниже пишут в одну и ту же
wp_postmeta, поэтому на скорость выборки влияют одинаково.
Путаница обычно в том, что люди сравнивают ACF с таксономиями как «плагин против ядра», хотя сравнивать надо схемы хранения. ACF — это интерфейс над теми же метаполями.
Четыре подхода
1. Нативные метаполя: register_post_meta и свой метабокс
Регистрируете поле в ядре, рисуете метабокс или отдаёте поле в REST и работаете с ним из блочного редактора. Ноль зависимостей, предсказуемые ключи, поле сразу живёт в API.
<?php
// WordPress 6.x. Регистрируем метаполе на init.
add_action( 'init', 'blog_register_reading_time' );
function blog_register_reading_time() {
register_post_meta( 'post', 'reading_time', array(
'type' => 'integer',
'single' => true,
'default' => 0,
// Без show_in_rest поля не будет ни в REST, ни в боковой панели редактора
'show_in_rest' => true,
'sanitize_callback' => 'absint',
'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
return current_user_can( 'edit_post', $post_id );
},
) );
}
Чем платите: весь UI на вас. Нонс, проверка прав, санитизация, вывод ошибок, повторяемые группы полей — это руками. Для одного-двух полей разумно, для тридцати — отдельный мини-проект, который потом некому поддерживать.
2. ACF, Meta Box, Carbon Fields
Плагин даёт интерфейс, типы полей, условную логику и валидацию. ACF 6.x пишет значение в обычный meta_key, а рядом кладёт служебную строку с префиксом подчёркивания — ссылку на ключ поля. То есть строк в wp_postmeta примерно в два раза больше, чем полей. Carbon Fields по умолчанию префиксует сами ключи подчёркиванием, Meta Box пишет в ключ как есть.
<?php
// ACF 6.x: два способа прочитать одно и то же значение
$price_via_acf = get_field( 'price', $post_id ); // с форматированием типа поля
$price_via_core = get_post_meta( $post_id, 'price', true ); // сырое значение из wp_postmeta
// Для простых типов (текст, число) значения совпадают.
// Для relation, image, date форматирование делает плагин — подменить вызов не выйдет.
Чем платите: зависимостью, и она не только техническая. В октябре 2024 года плагин ACF в каталоге WordPress.org был форкнут и переименован в Secure Custom Fields, а обновления ACF Pro приходят с сайта разработчика. Если у вас сотни сайтов на ACF, это уже не абстрактный риск вендор-лока, а вопрос того, откуда прилетает апдейт. Репитеры и гибкий контент — платная Pro-версия; текущие условия лицензии сверяйте на сайте продукта, они меняются.
Отдельно: вызовы get_field() по всем шаблонам означают, что уход с плагина — это правка каждого шаблона. Хранить конфигурацию полей в репозитории стоит с первого дня: ACF умеет выгружать группы в JSON, Carbon Fields описывается только кодом и в этом смысле безопаснее.
3. Таксономия вместо метаполя
Если по полю фильтруют и значений конечное число — город, бренд, состояние, тип жилья, — это не метаполе, а таксономия. Разница видна в SQL: таксономии фильтруются через wp_term_relationships, где связь лежит в двух индексированных целочисленных колонках, а не в longtext.
<?php
// Фильтр по метаполям: отдельное условие — отдельный JOIN к wp_postmeta,
// причём meta_value не индексирован
$by_meta = new WP_Query( array(
'post_type' => 'estate',
'meta_query' => array(
'relation' => 'AND',
array( 'key' => 'city', 'value' => 'moscow' ),
array( 'key' => 'price', 'value' => 5000000, 'type' => 'NUMERIC', 'compare' => '<=' ),
),
) );
// Тот же город через таксономию: выборка по term_taxonomy_id,
// числовой диапазон по-прежнему остаётся метаполем
$by_tax = new WP_Query( array(
'post_type' => 'estate',
'tax_query' => array(
array( 'taxonomy' => 'city', 'field' => 'slug', 'terms' => 'moscow' ),
),
'meta_query' => array(
array( 'key' => 'price', 'value' => 5000000, 'type' => 'NUMERIC', 'compare' => '<=' ),
),
) );
Чем платите: таксономия плохо подходит для уникальных значений — цена, площадь, дата. Термины на каждое число раздувают wp_terms и ничего не ускоряют. Диапазоны и сортировка остаются за метаполями или за внешним индексом.
4. Своя таблица
Отдельная таблица с колонкой на каждую характеристику и индексами там, где нужно. Подход для каталогов и маркетплейсов, где фильтр — основная функция сайта, а не украшение.
Чем платите: вы уходите из экосистемы. WP_Query не знает о вашей таблице, подмешивать её придётся фильтрами posts_join и posts_where или писать выборку на $wpdb целиком. Экспорт, импорт, мультисайт, ревизии, плагины SEO и кеширования про эти данные тоже не знают. Миграции схемы — ваша забота, включая откат.
Метаполя в WordPress — это EAV-хранилище, которому поручили роль фильтруемого каталога. Оно справляется, пока вы не просите его о том, для чего нужны колонки и индексы.
Сравнение
| Подход | Интерфейс редактора | Фильтрация | Зависимости | Миграция данных |
|---|---|---|---|---|
| register_post_meta + свой метабокс | Пишете сами | meta_query, JOIN на условие | Нет | Ключи свои, переносится просто |
| ACF / Meta Box / Carbon Fields | Готовый, богатый | meta_query, то же самое | Плагин, у части — платная версия | Значения в wp_postmeta, но служебные ключи и форматирование привязаны к плагину |
| Таксономия | Готовый, из ядра | tax_query по индексированным связям | Нет | Переносится штатным экспортом |
| Своя таблица | Пишете сами | Свои индексы, своя скорость | Нет, но и интеграции нет | Свои миграции и бэкапы |
Когда что выбирать
- Поле только выводится в шаблоне, фильтра нет. Любой подход годится. Берите тот, в котором удобно редактору: чаще это плагин.
- По полю фильтруют, значений конечный набор. Таксономия. Даже если поле уже сделано метаполем, перенос обычно дешевле, чем попытки ускорить
meta_query. - По полю фильтруют диапазоном и записей меньше нескольких тысяч. Метаполя. Одно-два условия на запрос ядро выдерживает, сильно не усложняйте.
- Фильтр по трём и более числовым полям, десятки тысяч записей. Своя таблица или внешний поиск. Критерий грубый, но рабочий: если план запроса показывает два и более JOIN к
wp_postmetaс сортировкой по третьему ключу — вы уже за границей применимости. - Поле нужно в REST или в блочном редакторе. Любое метаполе придётся регистрировать с
show_in_rest, а в ACF — включить соответствующую настройку группы полей. Данные в своей таблице в REST сами не появятся.
Что ломается чаще всего
- Поле не сохраняется из кода. У
register_post_metaестьauth_callback, и для ключей с подчёркиванием по умолчанию права строже. Если запись через REST молча не проходит, смотрите сначала туда, а не на фронтенд. - Чтение в цикле.
get_post_meta()поднимает все метаполя записи одним запросом и кладёт в кеш объекта, поэтому десять вызовов для одной записи не страшны. Страшно другое:'update_post_meta_cache' => falseв запросе, после которого каждое обращение идёт в базу отдельно. - Запись в цикле.
update_post_meta()на тысячу записей — это тысяча отдельных транзакций плюс инвалидация кеша. Массовые импорты делайте пакетно и с отключёнными лишними хуками. - Ревизии и автосейвы. Нативные метаполя в ревизии по умолчанию не попадают, и редактор, откатившийся на старую версию, обнаружит, что характеристики не вернулись. Плагины решают это по-разному — проверяйте на своём наборе полей.
- Индекс на meta_value. Соблазнительно и почти всегда бесполезно: колонка
longtext, индексировать придётся префикс, а числа в ней хранятся строками, и сравнение идёт с приведением типа.
Что делать дальше
Снимите с продакшена реальный план: включите SAVEQUERIES на стейдже или поставьте Query Monitor и посмотрите на запрос страницы фильтра — сколько JOIN к wp_postmeta, какая сортировка, есть ли Using temporary; Using filesort в EXPLAIN. Дальше посчитайте строки: SELECT COUNT(*) FROM wp_postmeta и топ по meta_key показывают, какие поля реально растут. Это две цифры, которые решают спор о подходе быстрее любых общих рассуждений.
Отдельно стоит завести правило: конфигурация полей живёт в репозитории, а не только в админке. Инвалидация кеша выборок и внешний поиск по каталогу — отдельные темы, здесь мы разбирали только схему хранения и фильтрацию.
EAV (entity-attribute-value) — схема, в которой атрибуты объекта хранятся строками в одной таблице: идентификатор объекта, имя свойства, значение. wp_postmeta устроена именно так: post_id, meta_key, meta_value. Плюс — любые поля без изменения схемы. Минус — каждое условие фильтра требует отдельного обращения к той же таблице, а значение нельзя нормально проиндексировать.
Карточки для маркетплейсов: Таблица товаров и папка фотографий на входе — комплект слайдов и описаний на выходе, по требованиям Wildberries и Ozon.
Спросить за 5 ₽Источники: register_post_meta() — WordPress Developer Resources, WP_Query — WordPress Developer Resources
Частые вопросы
Если я читаю поля через get_post_meta, смогу ли я отказаться от ACF без правки шаблонов?
У меня WordPress 5.x или другая версия ACF — примеры заработают?
Метаполе не появляется в REST или не сохраняется — с чего начать?
Сколько метаполей на одну запись — это много?
Можно ли ускорить meta_query индексом, не меняя подход?
Что проверить в проде после перехода на новый подход?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту. Instagram, Facebook и WhatsApp принадлежат компании Meta Platforms Inc., признанной в России экстремистской организацией; её деятельность на территории Российской Федерации запрещена.