Свои типы записей в WordPress: где регистрировать и чем платить
Категория в блоге не тянет, а register_post_type в functions.php ломается в день смены темы. Разбираем четыре места для регистрации своего типа записи, цену каждого и то, что ломается после.

Задача выглядит безобидно: на сайте нужен раздел «Кейсы» — со своим списком в админке, своей карточкой и фильтром по отрасли. Очевидное решение — завести категорию в обычных записях: пять минут, ноль кода. Через полгода кейсы лезут в ленту блога и в RSS, в карточке не хватает полей, а «Отрасль» приходится изображать тегами. Второй очевидный путь — register_post_type в functions.php — перестаёт работать в день смены темы. Ниже — что советуют делать те, кто потом этот сайт поддерживает. Проверено на WordPress 6.x. Шаблоны карточки и архива — отдельная тема, здесь только регистрация и её цена.
Сначала проверьте, нужен ли новый тип вообще
Свой тип записи — это новое значение в колонке post_type таблицы wp_posts плюс отдельная ветка шаблонов и отдельный пункт меню. Платите вы за него в трёх местах:
- Имя типа фиксируется навсегда. Не больше 20 символов, только строчные латинские буквы, цифры, дефис и подчёркивание. Переименовать после запуска — это
UPDATEпоwp_posts, правка всех запросов, имён шаблонов и сохранённых ссылок. - Часть имён занята ядром.
post,page,attachment,revision,nav_menu_item,wp_block,wp_templateи ещё ряд служебных типов. Префикс проекта в имени спасает и от них, и от коллизии с установленным плагином. Полный список зарезервированных имён есть в документацииregister_post_type. - Каждый публичный тип с архивом добавляет rewrite-правила. Пока вы их не сбросили, единичная запись отдаёт 404.
Критерий: новый тип нужен, когда у сущности свой набор полей, свой список в админке, свои права на редактирование или свой URL-префикс. Если отличие только в том, что «это другой раздел блога» — хватит таксономии на обычных записях.
Четыре места, где можно зарегистрировать тип
Код регистрации всегда одинаковый, цена — разная.
| Где | Переживает смену темы | Уезжает с деплоем | Главный риск |
|---|---|---|---|
functions.php темы | Нет | Да, если тема в git | После смены темы записи остаются в базе, но исчезают из админки и с сайта |
| Отдельный плагин проекта | Да | Да | Можно забыть активировать после переноса |
| mu-plugin | Да, и отключить нельзя | Да | Не видно в списке плагинов, легко потерять при миграции файлов |
| UI-плагин (CPT UI, ACF 6.1+) | Да, пока плагин активен | Нет: определение лежит в базе | На стейдже и в проде расходятся настройки типа |
Рабочий совет большинства команд — отдельный маленький плагин на проект: он переживает смену темы, лежит в репозитории и его видно в списке плагинов. UI-плагины уместны там, где типы заводит не разработчик, а контент-менеджер, и их приходится править без деплоя.
Тип записи — это значение в колонке базы, а не настройка интерфейса. Всё, что его регистрирует, должно ехать вместе с кодом, иначе на стейдже и в проде у вас разные сущности с одинаковыми названиями.
Минимальный набор аргументов, который реально нужен
<?php
/**
* Plugin Name: Acme Content Types
* Description: Типы записей и таксономии проекта.
* Version: 1.0.0
*/
add_action( 'init', 'acme_register_case_post_type' );
function acme_register_case_post_type() {
register_post_type(
'acme_case',
array(
'labels' => array(
'name' => 'Кейсы',
'singular_name' => 'Кейс',
'add_new_item' => 'Добавить кейс',
'edit_item' => 'Редактировать кейс',
'menu_name' => 'Кейсы',
),
'public' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'cases', 'with_front' => false ),
'menu_icon' => 'dashicons-portfolio',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'revisions', 'custom-fields' ),
// Без show_in_rest откроется классический редактор вместо блочного.
'show_in_rest' => true,
'taxonomies' => array( 'acme_industry' ),
)
);
}
// Сбрасываем rewrite-правила один раз при активации, а не на каждом init.
register_activation_hook( __FILE__, 'acme_flush_rewrites_on_activate' );
function acme_flush_rewrites_on_activate() {
acme_register_case_post_type();
flush_rewrite_rules();
}
Что здесь неочевидно. show_in_rest отвечает не только за REST API: без него тип открывается в классическом редакторе. Поддержка custom-fields нужна, чтобы мета-поля отдавались через REST и были доступны блочному редактору. has_archive => true включает архив, который ищет шаблон archive-acme_case.php, а карточка — single-acme_case.php. И главное: flush_rewrite_rules() тяжёлая, её вызывают на активации плагина, а не на каждом запросе.
Чужой тип правят фильтром, а не повторной регистрацией
Если нужно добавить поддержку миниатюр типу из купленной темы или плагина, не регистрируйте его заново — перехватите аргументы фильтром register_post_type_args. Повторная регистрация с тем же именем перебивает оригинал целиком, и вы теряете всё, что в нём было настроено.
Таксономия или мета-поле: чем платите за фильтр
«Отрасль» у кейса можно хранить и как мета-поле, и как таксономию. Разница видна на запросе.
// Фильтр по мета-полю: JOIN к wp_postmeta, индекса по значению там нет
$by_meta = new WP_Query( array(
'post_type' => 'acme_case',
'meta_query' => array(
array( 'key' => 'acme_industry', 'value' => 'retail' ),
),
) );
// Фильтр по таксономии: через term_relationships, плюс готовый архив и ЧПУ
$by_term = new WP_Query( array(
'post_type' => 'acme_case',
'tax_query' => array(
array( 'taxonomy' => 'acme_industry', 'field' => 'slug', 'terms' => 'retail' ),
),
) );
Таблица wp_postmeta индексирована по post_id и meta_key, но не по значению, которое лежит в длинном текстовом поле. На нескольких тысячах записей разница незаметна, на десятках тысяч и двух-трёх условиях подряд — заметна очень. Таксономия вдобавок бесплатно даёт страницу /cases/industry/retail/ и фильтр в админке.
Мета-поля остаются для того, что уникально у каждой записи и по чему вы не фильтруете: бюджет, срок сдачи, ссылка на сайт клиента. Регистрируйте их через register_post_meta на том же хуке init, с sanitize_callback и show_in_rest — иначе поле не увидит блочный редактор.
Когда что выбирать
- Значение повторяется и по нему фильтруют — таксономия.
- Значение уникально для записи и нужно только в карточке — мета-поле.
- Сущность отличается только разделом — оставьте обычные записи и таксономию.
- Нужны свои поля, свой список и свой URL — свой тип записи в отдельном плагине.
- Типы заводит контент-менеджер без деплоя — UI-плагин, но тогда синхронизация стейджа и прода становится вашей ручной работой.
Что ломается
- Карточка отдаёт 404, архив работает. Не сброшены rewrite-правила. Откройте «Настройки → Постоянные ссылки» и сохраните без изменений либо выполните
wp rewrite flush. - По адресу архива открывается страница. Слаг типа совпал со слагом существующей страницы или категории. Выигрывает тот, кто раньше в правилах перезаписи; лечится переименованием слага до запуска.
- Открывается классический редактор. Нет
show_in_rest => true. Если редактор блочный, но мета-поля не читаются — нетcustom-fieldsвsupports. - После смены темы раздел пропал. Регистрация жила в теме. Данные целы: админка покажет их по адресу
/wp-admin/edit.php?post_type=acme_case, как только тип снова зарегистрируют. - Архив тормозит в проде. Обычно это
meta_queryна двух-трёх ключах. Смотрите реальный SQL через Query Monitor и переносите то, по чему фильтруете, в таксономию.
Rewrite-правила — таблица соответствий между красивым URL и внутренним запросом WordPress. Она хранится в опции базы и пересобирается не на каждом запросе, а по команде. Поэтому новый тип записи «появляется» на сайте только после сброса этих правил, хотя код давно на месте.
Что делать дальше
Зафиксируйте имя типа и слаг до первой публикации — это самое дорогое решение во всей истории. Если регистрация сейчас в теме, перенесите её в плагин: по данным ничего не изменится, а риск уйдёт. Добавьте в деплой wp rewrite flush, чтобы правила не зависели от того, зашёл ли кто-то в настройки ссылок. Проверить, что видит сайт, можно командой wp post-type list. Дальше смотрите на две вещи: время запросов на архиве через Query Monitor и права — если кейсы ведёт отдельная группа редакторов, разбираться с capability_type и map_meta_cap придётся раньше, чем кажется.
Аналитик звонков: Разбор записи звонка по этапам сделки: где сорвалось, что упущено, три исправляемые рекомендации с цитатами.
Спросить за 90 ₽Источники: register_post_type() — WordPress Developer Resources, Registering Custom Post Types — Plugin Handbook
Частые вопросы
Можно ли переименовать тип записи после запуска сайта?
Почему страница записи отдаёт 404, хотя тип зарегистрирован?
А если у меня WordPress старее 6.x — код будет работать?
Стоит ли регистрировать типы через CPT UI или ACF вместо кода?
Сколько своих типов записей нормально для одного сайта?
Записи исчезли после смены темы — они удалились?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.