Wordpress ~ 21 мин

Создание своего типа записи (Custom Post Type) с нуля: пошаговое руководство

WordPress из коробки умеет работать с записями (Posts) и страницами (Pages). Но что, если на вашем сайте должны быть каталог услуг, портфолио проектов, база знаний или список сотрудников? Для этого в WordPress существует механизм Custom Post Type (CPT) — собственный тип записи, у которого есть свой раздел в админке, свои шаблоны вывода и при необходимости — своя таксономия. В этой статье — подробный разбор создания CPT с нуля: функция register_post_type(), все аргументы с примерами, где размещать код и типичные ошибки.

Зачем нужен Custom Post Type

Допустим, вы делаете сайт агентства. У агентства есть:

  • блог — Posts (записи), выводятся в обратном хронологическом порядке;
  • страницы «О компании», «Контакты» — Pages, иерархия и статичный контент;
  • база знаний — Knowledge Base, нужны свой архив, шаблон статьи и набор полей (версия ПО, категория);
  • услуги — Services, нужны карточки с иконками и ценами.

Запихивать кейсы в стандартные Posts — значит получить хаос в админке и невозможность сделать отдельные шаблоны. Создав для них отдельный CPT, вы получаете:

  • свой пункт меню в админке;
  • свои поля (title, editor, thumbnail и т.д.);
  • свою таксономию (например, «Раздел» или «Метки»);
  • свои URL-адреса (/kb/, /kb/article-slug/);
  • свои шаблоны в теме (single-knowledge_base.php, archive-knowledge_base.php).

Шаг 1. Функция register_post_type()

Регистрация CPT — это один вызов register_post_type(), помещённый внутрь хука init. Хук init срабатывает после загрузки WordPress, до того, как начинается вывод страницы. Это правильное место: к этому моменту уже известны все таксономии и установленные плагины, но маршрутизация ещё не началась.

/**
 * Регистрация Custom Post Type "База знаний".
 * Код размещается в functions.php темы ИЛИ в отдельном плагине.
 *
 * @see https://developer.wordpress.org/reference/functions/register_post_type/
 */
add_action('init', function () {

    register_post_type('knowledge_base', array(
        'labels'       => /* массив лейблов */,
        'public'       => true,
        'has_archive'  => true,
        'rewrite'      => array('slug' => 'kb'),
        'supports'     => array('title', 'editor', 'thumbnail'),
    ));

});

Аргумент labels

Labels — это массив строк, которые WordPress подставляет в интерфейс админки: заголовки страниц, подписи кнопок, уведомления. Всего их больше двадцати, но обязательных — три. Остальные WordPress сгенерирует автоматически, взяв за основу значение singular_name.

'labels' => array(
    /* ОБЯЗАТЕЛЬНЫЕ */
    'name'          => 'База знаний',          // множественное число — в меню, заголовки архива
    'singular_name' => 'Статья базы знаний',            // единственное число — на странице редактирования

    /* ОСНОВНЫЕ ДОПОЛНИТЕЛЬНЫЕ */
    'add_new'               => 'Добавить',         // текст ссылки «Добавить» в меню
    'add_new_item'          => 'Добавить статью',   // заголовок страницы создания
    'edit_item'             => 'Редактировать статью', // заголовок страницы редактирования
    'new_item'              => 'Новая статья',      // заголовок после создания
    'view_item'             => 'Смотреть статью',   // кнопка «Просмотреть» в админке
    'view_items'            => 'Смотреть статьи',   // для экрана архива
    'search_items'          => 'Искать в базе знаний',    // текст в поле поиска админки
    'not_found'             => 'Статьи не найдены',      // пустой архив
    'not_found_in_trash'    => 'В корзине пусто', // пустая корзина

    /* МЕНЮ */
    'all_items'             => 'Все статьи',      // подпункт меню
    'archives'              => 'Архив базы знаний',    // подпункт меню
    'attributes'            => 'Атрибуты статьи',  // подпункт меню (появляется если есть supports 'page-attributes')

    /* ПРОЧИЕ */
    'featured_image'        => 'Обложка статьи',        // в настройках экрана
    'set_featured_image'    => 'Установить обложку',
    'remove_featured_image' => 'Удалить обложку',
    'use_featured_image'    => 'Использовать как обложку',

),
Не переводите слова, которые являются техническими идентификаторами. Например, name, singular_name, add_new — это ключи массива, они всегда на английском. Переводить нужно только строковые значения справа.

Аргумент public

public — главный выключатель CPT. Он определяет:

  • видимость записей в админке (показывать в меню или скрыть);
  • видимость на сайте (доступны ли по прямому URL);
  • нужен ли отдельный шаблон в теме;
  • участвует ли тип в поиске.
// Полная публичность — записи видны везде
'public' => true,

// CPT существует, но скрыт из админского меню и недоступен по прямым URL.
// Используется, когда записи создаются только программно (например, демо-контент).
'public' => false,

// Ярлык (true/false) — URL останется рабочим, но в админке пункт не появится.
'publicly_queryable' => true, // отвечать на ?post_type=projects
'show_ui'            => true, // показывать интерфейс в админке
'show_in_nav_menus'  => false, // не показывать в навигации
Частая ситуация. CPT с 'public' => false, но 'show_ui' => true и 'show_in_menu' => true — это «скрытый» тип, который нужен только для админки. Например, виджеты или импортированные данные.

Аргумент has_archive

Если true — WordPress создаст страницу архива по адресу /kb/. На ней будут выводиться все записи этого CPT в обратном хронологическом порядке, как на странице блога.

// Архив будет доступен по адресу /kb/
'has_archive' => true,

// Архив будет доступен по адресу /portfolio/ (кастомный ярлык)
'has_archive' => 'portfolio',

// Без архива — например, для «лидогенераторов» или виджетов
'has_archive' => false,

Аргумент rewrite

rewrite определяет, по каким URL будут доступны одиночная запись и архив.

'rewrite' => array(
    'slug'       => 'kb',         // префикс URL: /kb/%postname%
    'with_front' => false,              // не добавлять перед ярлык префикс темы (/blog/kb/)
    'feeds'      => true,               // включать CPT в RSS-ленту сайта
    'pages'      => true,               // поддержка нумерации страниц (/kb/page/2/)
    'ep_mask'    => EP_PERMALINK,      // маска «конечной точки» (Endpoint mask). EP_PERMALINK означает,
                                        // что правило сработает на том же уровне, что и обычные записи.
),

После изменения slug нужно перейти в Настройки → Постоянные ссылки и нажать «Сохранить изменения» — это сбросит правила перезаписи URL.

Аргумент supports

supports — массив фич, которые будут доступны на странице редактирования записи. Это то, что вы отмечаете галочками в настройках экрана.

'supports' => array(
    'title',        // заголовок записи (обязательно — без него WP не даст сохранить)
    'editor',       // визуальный редактор (TinyMCE или Гутенберг)
    'thumbnail',    // миниатюра записи (если тема поддерживает post-thumbnails)
    'excerpt',      // цитата / отрывок
    'author',       // выбор автора
    'trackbacks',   // устаревший pingbacks — лучше отключить
    'custom-fields', // произвольные поля (CMB2 / ACF их заменяют)
    'comments',     // комментарии
    'revisions',    // хранить ревизии (версии) записи
    'page-attributes', // атрибуты страницы: родитель и порядок (menu_order).
                       // Примечание: поле «Порядок» показывается ТОЛЬКО если
                       // 'page-attributes' добавлен в supports И 'hierarchical' => true.
                       // Для CPT с иерархией страниц это нормально, для постов — избыточно.
    'post-formats', // форматы записи (aside, gallery, quote, etc.)
),
Экономьте на supports. Каждая фича — это лишний запрос к базе при загрузке страницы редактирования. Если вам нужен только заголовок и текст — используйте ['title', 'editor']. Миниатюру добавляйте только если тема её выводит.

Аргумент menu_icon и menu_position

'menu_icon' => 'dashicons-portfolio', // иконка из набора Dashicons
                              // Полный список: https://developer.wordpress.org/resource/dashicons/
                              // Примеры: dashicons-book-alt, dashicons-building, dashicons-star-filled

// Все доступные позиции меню (числа примерные):
// 2   — Dashboard
// 4   — Separator
// 5   — Записи
// 10  — Медиафайлы
// 20  — Страницы
// 25  — Separator
// 59  — Комментарии
// 60  — Separator
// 65  — Плагины
// 70  — Пользователи
// 75  — Инструменты
// 80  — Настройки
// 99  — Separator

// Ставим после «Страницы» (20) и до комментариев (59)
'menu_position' => 21,

Аргумент capabilities

По умолчанию CPT наследует capabilities стандартной записи (edit_post, delete_post и т.д.). Это значит, что редактор сайта может редактировать ваш CPT «из коробки». Но если нужно разделить права — например, чтобы контент-менеджер видел статьи базы знаний, но не мог их удалять — используйте кастомные capabilities.

'capability_type' => 'kb_article',       // базовое имя для роли (edit_kb_article, delete_kb_article, etc.)
// ИЛИ более гибкий вариант — задаём каждую capability отдельно:
'capabilities' => array(
    'edit_post'          => 'edit_kb_article',
    'read_post'          => 'read_kb_article',
    'delete_post'        => 'delete_kb_article',
    'edit_posts'         => 'edit_kb_articles',
    'edit_others_posts'  => 'edit_others_kb_articles',
    'publish_posts'      => 'publish_kb_articles',
    'read_private_posts' => 'read_private_kb_articles',
    // 'delete_posts', 'delete_others_posts', 'delete_private_posts' —
    // WordPress сгенерирует автоматически по аналогии с edit_post
),

После добавления capabilities нужно добавить их к роли Редактор через add_cap(). Лучше делать это в хуке admin_init с проверкой, чтобы не добавлять повторно при каждой загрузке страницы.

add_action('admin_init', function () {
    $role = get_role('editor');
    if ($role && !$role->has_cap('edit_kb_articles')) {
        $role->add_cap('edit_kb_articles');
        $role->add_cap('edit_others_kb_articles');
        $role->add_cap('publish_kb_articles');
        $role->add_cap('delete_kb_articles');
        $role->add_cap('delete_others_kb_articles');
    }
});

Аргумент taxonomies

Сюда передаётся массив ярлыков таксономий, которые должны быть привязаны к CPT. Это не регистрирует таксономии — они уже должны быть зарегистрированы раньше (на том же или более раннем хуке init).

'taxonomies' => array('kb_section', 'kb_tag'),

Если таксономия ещё не существует, WordPress просто проигнорирует ярлык. Привязка «на лету» во время регистрации CPT — это просто удобная короткая запись. То же самое можно сделать после регистрации:

// Привязать таксономию к CPT после регистрации CPT
add_action('init', function () {
    register_taxonomy_for_object_type('kb_section', 'knowledge_base');
}, 20); // приоритет 20 — после регистрации таксономии и CPT

Аргумент show_in_rest и REST API

Начиная с WordPress 5.0, REST API включён по умолчанию. Чтобы CPT был доступен через /wp-json/wp/v2/kb, нужно:

'show_in_rest' => true,  // добавить эндпоинты в REST API (обязательно для Гутенберга!)
'rest_base'    => 'kb', // базовый ярлык в URL: /wp/v2/kb
'rest_namespace' => 'wp/v2', // неймспейс. wp/v2 — стандартный; свой — для кастомных эндпоинтов
show_in_rest делает CPT видимым в Гутенберге. Если отключить его на уже существующем CPT, Гутенберг перестанет открываться для записей этого типа и предложит Classic Editor.

Шаг 2. Полный рабочий пример

Ниже — законченный код регистрации CPT «База знаний» с комментариями. Его можно вставить в functions.php дочерней темы или в свой плагин.

/**
 * Регистрация Custom Post Type «База знаний» (knowledge_base).
 *
 * Код должен быть подключён на хуке init.
 * Все строки лейблов — на русском, ключи массива — на английском.
 */
add_action('init', function () {

    register_post_type('knowledge_base', array(

        /* === ЛЕЙБЛЫ === */
        'labels' => array(
            'name'               => 'База знаний',
            'singular_name'      => 'Статья базы знаний',
            'add_new'            => 'Добавить',
            'add_new_item'       => 'Новая статья',
            'edit_item'          => 'Редактировать статью',
            'new_item'           => 'Новая статья',
            'view_item'          => 'Смотреть статью',
            'search_items'       => 'Искать в базе знаний',
            'not_found'          => 'Статьи не найдены',
            'not_found_in_trash' => 'В корзине пусто',
            'all_items'          => 'Все статьи',
            'archives'           => 'Архив базы знаний',
        ),

        /* === ПУБЛИЧНОСТЬ === */
        // public — главный выключатель: видимость в админке, доступность по URL,
        // участие в поиске, генерация шаблонов темы.
        'public' => true,

        // Показывать ли пункт в навигационном меню сайта (wp_nav_menu()).
        // true — CPT появится в настройках меню как «База знаний».
        'show_in_nav_menus' => true,

        // Показывать ли в меню админки (список «Записи», «Страницы» и т.д.).
        // По умолчанию равно значению public, если не задано явно.
        'show_ui' => true,

        // Позволять ли обращаться к CPT через ?post_type=knowledge_base в URL.
        // Отключение = CPT недоступен по прямой ссылке, но виден в админке.
        'publicly_queryable' => true,

        // Исключить из результатов поиска сайта (если false — CPT не ищется).
        'exclude_from_search' => false,

        /* === АДРЕСА === */
        // Архив: /kb/
        'has_archive' => true,

        // Ярлык в URL: /kb/nastrojka-pochty/
        'rewrite' => array(
            'slug'       => 'kb',
            // with_front: не добавлять префикс темы (/blog/kb/) —
            // актуально если для записей настроен префикс /blog/
            'with_front' => false,
            // feeds: включить RSS для архива CPT
            'feeds'      => true,
            // pages: поддержка /kb/page/2/
            'pages'      => true,
        ),

        /* === ИНТЕРФЕЙС РЕДАКТИРОВАНИЯ === */
        // Фичи, доступные на странице редактирования записи.
        // Минимум — ['title'], иначе WP не даст сохранить запись.
        'supports' => array(
            'title',       // заголовок (обязательно)
            'editor',      // текстовый редактор
            'thumbnail',   // миниатюра
            'excerpt',    // цитата
            'author',     // выбор автора
            // 'page-attributes' — нужен только если hierarchical => true (иерархия как у страниц)
            // 'revisions' — хранить ревизии (полезно для длинных статей)
        ),

        // Иконка в меню админки — из набора Dashicons.
        // Полный список: https://developer.wordpress.org/resource/dashicons/
        'menu_icon' => 'dashicons-book-alt',

        // Позиция в меню. 21 = после «Страницы» (20).
        // Варианты: 5 — после «Консоли», 10 — после «Медиа», 20 — после «Страниц», 59 — после «Комментариев», 65 — после «Плагинов»
        'menu_position' => 21,

        /* === REST API === */
        // show_in_rest = true делает CPT видимым в Гутенберге и в /wp-json/wp/v2/kb
        'show_in_rest' => true,

        // База в URL REST: /wp/v2/kb
        'rest_base' => 'kb',

        /* === ИЕРАРХИЯ === */
        // hierarchical = false — как Записи (Post): без вложенности, дата вместо меню.
        // hierarchical = true  — как Страницы (Page): можно выбрать родителя.
        // Важно: если hierarchical => true, в supports полезно добавить 'page-attributes',
        // чтобы появилось поле «Родитель» и «Порядок».
        'hierarchical' => false,

        /* === ЯЗЫК === */
        // Позволяет плагинам локализации (Polylang, WPML) корректно переводить CPT.
        // Для Polylang это обязательно.
        'language' => 'ru_RU',

    ));

}, 5); // приоритет 5 — регистрируем рано, чтобы другие таксономии успели подключиться

Шаг 3. Где размещать код

Есть три варианта:

1. functions.php темы

Самый простой способ. Код добавляется прямо в functions.php активной темы. Если тема простая и не обновляется — этого достаточно.

Вариант А: код напрямую

// functions.php темы
add_action('init', function () {
    register_post_type('projects', array(
        // ...
    ));
});

Вариант Б: подключение через include

Если код большой или вы хотите держать его в отдельном файле (удобнее для редактирования, можно версионировать), создайте файл и подключите его:

// В functions.php темы — одна строка:
require_once get_template_directory() . '/inc/cpt-knowledge-base.php';

// Создайте файл: /wp-content/themes/ВАША_ТЕМА/inc/cpt-knowledge-base.php
// В нём — только регистрация CPT и таксономий, без add_action wrapper:
<?php
if (!defined('ABSPATH')) { exit; }

/**
 * CPT «База знаний» и таксономия «Раздел»
 */
add_action('init', 'dk_register_kb_cpt', 5);
function dk_register_kb_cpt() {
    register_post_type('knowledge_base', array(
        // ...
    ));
}

Нюансы: когда это безопасно, а когда — нет

Ситуация Рекомендация
Тема из репозитория WordPress.org (бесплатная) Никогда не добавляйте код напрямую. При обновлении темы из админки всё потеряется. Используйте дочернюю тему.
Коммерческая тема (ThemeForest, TemplateMonster и т.д.) Обновляется через панель хостинга или личный кабинет. При обновлении — код в functions.php потеряется. Дочерняя тема — обязательно.
Своя кастомная тема, которую никто не обновляет Можно добавлять напрямую. Но если проект перейдёт другому разработчику — он может не знать о ваших изменениях. Лучше сразу структурировать.
Сайт-визитка клиента, без планов на развитие Допустимо, если вы уверены, что тема не обновится. Но предупредите клиента, что при обновлении темы код нужно будет вернуть.
Главное правило: функции темы и плагины — это разные вещи. Код в functions.php тесно связан с темой. Если тема обновляется — ваш код исчезает. Если тема меняется — ваш код перестаёт работать. Для любого проекта, который должен жить больше месяца, используйте дочернюю тему или плагин.

2. Дочерняя тема (рекомендуется для клиентских сайтов)

Создайте дочернюю тему и добавьте код в её functions.php. При обновлении родительской темы ваши изменения сохранятся.

// functions.php дочерней темы
add_action('init', 'dk_register_kb_cpt');

function dk_register_kb_cpt() {
    register_post_type('knowledge_base', array(
        // ... массив аргументов
    ));
}

3. Собственный плагин (рекомендуется для переиспользуемого кода)

Если CPT логически не привязан к теме (например, вы разрабатываете плагин базы знаний, который будет установлен на разные сайты), создайте плагин:

<?php
/**
 * Plugin Name: DK Knowledge Base
 * Description: Регистрирует CPT «База знаний» и таксономию «Раздел»
 * Version:     1.0.0
 * Author:      Денис К.
 * License:     GPL v2
 */

// Защита от прямого доступа
if (!defined('ABSPATH')) { exit; }

add_action('init', 'dk_register_kb_cpt', 5);

function dk_register_kb_cpt() {
    register_post_type('knowledge_base', array(
        // ...
    ));
}

Шаг 4. Регистрация таксономии для CPT

Таксономия — это способ группировать записи внутри CPT. Различают иерархическую (как рубрики: «Настройка сервера», «Ошибки», «Интеграции», с возможностью вложенности) и неиерархическую (как метки: «Ubuntu», «PHP 8», «Битрикс24»).

add_action('init', function () {

    /* ИЕРАРХИЧЕСКАЯ ТАКСОНОМИЯ — рубрики */
    register_taxonomy('kb_section', 'knowledge_base', array(
        'labels' => array(
            'name'              => 'Разделы',
            'singular_name'     => 'Раздел',
            'add_new_item'      => 'Добавить раздел',
            'edit_item'         => 'Редактировать раздел',
            'search_items'       => 'Искать разделы',
            'not_found'         => 'Разделы не найдены',
        ),
        // Иерархическая: как рубрики, с вложенностью
        'hierarchical' => true,

        // Показывать в REST API
        'show_in_rest' => true,

        // Ярлык в URL: /section/nastrojka-servera/
        'rewrite' => array('slug' => 'section'),

        // CPT, к которым привязана таксономия. Можно указать несколько: array('knowledge_base', 'services')
        'object_type' => array('knowledge_base'),
    ));

    /* НЕИЕРАРХИЧЕСКАЯ ТАКСОНОМИЯ — метки */
    register_taxonomy('kb_tag', 'knowledge_base', array(
        'labels' => array(
            'name'          => 'Метки',
            'singular_name' => 'Метка',
        ),
        // Неиерархическая: как метки, без вложенности
        'hierarchical' => false,

        'show_in_rest' => true,
        'rewrite' => array('slug' => 'kb-tag'),
        'object_type' => array('knowledge_base'),
    ));

}, 10); // приоритет 10 — после CPT (CPT зарегистрирован с 5)

Шаг 5. Шаблоны темы

WordPress автоматически ищет шаблоны для CPT по цепочке:

  1. single-{post_type}.php — одиночная запись. Например, single-knowledge_base.php.
  2. single.php — общий шаблон одиночной записи.
  3. singular.php — любой единичный тип.

Для архива:

  1. archive-{post_type}.php — архив. Например, archive-knowledge_base.php.
  2. archive.php — общий архив.
  3. index.php — главная.

Пример минимального single-knowledge_base.php:

<?php
/**
 * Шаблон одиночной записи CPT «knowledge_base».
 * Файл: single-knowledge_base.php
 */

get_header();

while (have_posts()) {
    the_post();

    // Получаем данные
    $version = get_post_meta(get_the_ID(), 'kb_version', true);
    $section = get_the_terms(get_the_ID(), 'kb_section');
    ?>

    <article class="kb-article">
        <header class="kb-article__header">
            <?php the_post_thumbnail('large', array('class' => 'kb-article__thumb')); ?>
            <h1><?php the_title(); ?></h1>
        </header>

        <div class="kb-article__meta">
            <?php if ($version) : ?>
                <span>Версия: <?php echo esc_html($version); ?></span>
            <?php endif; ?>
            <?php if ($section && !is_wp_error($section)) : ?>
                <span>Раздел: <?php echo esc_html($section[0]->name); ?></span>
            <?php endif; ?>
        </div>

        <div class="kb-article__content">
            <?php the_content(); ?>
        </div>

        <?php
        // Выводим метки
        $tags = get_the_terms(get_the_ID(), 'kb_tag');
        if ($tags && !is_wp_error($tags)) {
            echo '<ul class="kb-article__tags">';
            foreach ($tags as $tag) {
                echo '<li><a href="' . esc_url(get_term_link($tag)) . '">'
                     . esc_html($tag->name) . '</a></li>';
            }
            echo '</ul>';
        }
        ?>
    </article>

    <?php
}

get_footer();

Шаг 6. Вывод CPT в запросе (WP_Query)

Чтобы вывести записи CPT на странице темы (не в шаблоне архива, а в произвольном месте), используйте WP_Query:

/**
 * Вывод 6 последних статей базы знаний в сетке.
 */
$args = array(
    'post_type'      => 'knowledge_base',           // наш CPT
    'posts_per_page' => 6,                    // сколько показать (-1 = все)
    'orderby'        => 'date',              // по дате
    'order'          => 'DESC',              // от новых к старым

    // Альтернативные варианты сортировки:
    // 'orderby' => 'title',   'order' => 'ASC'   — по алфавиту
    // 'orderby' => 'meta_value_num', 'meta_key' => 'kb_version', 'order' => 'DESC' — по числовому мета-полю
    // 'orderby' => 'rand'                           — случайный порядок

    // Получить только статьи из раздела «Настройка сервера»
    // 'tax_query' => array(
    //     array(
    //         'taxonomy' => 'kb_section',
    //         'field'    => 'slug',
    //         'terms'    => 'nastrojka-servera',
    //     ),
    // ),
);

$query = new WP_Query($args);

if ($query->have_posts()) {
    echo '<div class="kb-grid">';

    while ($query->have_posts()) {
        $query->the_post();
        ?>
        <article class="kb-card">
            <a href="<?php the_permalink(); ?>">
                <?php the_post_thumbnail('medium', array('class' => 'kb-card__img')); ?>
                <h3><?php the_title(); ?></h3>
            </a>
            <?php the_excerpt(); ?>
        </article>
        <?php
    }

    echo '</div>';

    // Пагинация (для архивных страниц)
    echo paginate_links(array(
        'total'   => $query->max_num_pages,
        'current' => max(1, get_query_var('paged')),
    ));
} else {
    echo '<p>Статьи скоро появятся.</p>';
}

// Обязательно восстановить оригинальный запрос!
wp_reset_postdata();

Типичные ошибки

Ошибка 1. CPT не появляется в меню

Причина — public установлен в false или show_ui в false. Проверьте:

'public'  => true,
'show_ui'  => true,

Ошибка 2. CPT не открывается по URL

Причина — не сброшены постоянные ссылки. Перейдите в Настройки → Постоянные ссылки и нажмите «Сохранить».

Ошибка 3. After registration, taxonomy not attached

Таксономия регистрируется после CPT — в итоге taxonomies в CPT не находит её ярлык. Решение: регистрируйте таксономию на более позднем хуке, либо вызывайте register_taxonomy_for_object_type() после регистрации обоих.

// Правильный порядок: таксономия — раньше (ниже приоритет), CPT — позже
add_action('init', 'dk_register_kb_section_tax', 3);  // приоритет 3
add_action('init', 'dk_register_kb_cpt',            5); // приоритет 5

Ошибка 4. Имя CPT совпадает с зарезервированным

WordPress резервирует ряд ярлыков: post, page, attachment, revision, nav_menu_item, custom_css, customize_changeset, action, author, order, theme. Использование любого из них вызовет ошибку.

Ошибка 5. CPT виден, но шаблон не подключается

Убедитесь, что имя файла точно соответствует ярлыку CPT: single-knowledge_base.php, не single-kb.php. Также проверьте, что файл лежит в корне темы (не в подпапке).

Шаг 7. Расширенные возможности

CPT с иерархией (как страницы)

'hierarchical' => true,           // можно выбирать родителя
'supports'     => array('title', 'editor', 'page-attributes', 'thumbnail'),
// page-attributes даёт:
// 1) «Родитель» — выпадающий список всех записей этого CPT
// 2) «Порядок»  — числовое поле menu_order (удобно для ручной сортировки без плагинов)

CPT только для REST (без публичной части)

'public'             => false,
'show_ui'            => true,
'show_in_menu'       => true,
'publicly_queryable' => false,    // не доступен по URL
'show_in_rest'       => true,     // но доступен через REST API
// Подходит для хранения данных, которые создаются и редактируются в админке,
 // а на сайт выводятся через API или шорткод.

CPT со своей колонкой в админке

// Добавляем колонку «Версия ПО» в список записей CPT
add_filter('manage_knowledge_base_posts_columns', function ($columns) {
    // Вставляем колонку после «Заголовок»
    $new = array();
    foreach ($columns as $key => $value) {
        $new[$key] = $value;
        if ($key === 'title') {
            $new['kb_version'] = 'Версия';
        }
    }
    return $new;
});

// Заполняем колонку данными
add_action('manage_knowledge_base_posts_custom_column', function ($column, $post_id) {
    if ($column === 'kb_version') {
        $version = get_post_meta($post_id, 'kb_version', true);
        echo $version ? esc_html($version) : '—';
    }
}, 10, 2);

Шаг 8. Rewrite Rules: как работают URL

Когда вы регистрируете CPT с 'rewrite' => array('slug' => 'kb'), WordPress автоматически добавляет правила перезаписи URL. Иногда нужно добавить свои — например, для пагинации вложенных таксономий или для «красивых» URL вида /kb/tag/php8/.

Что добавляет WordPress автоматически

Для CPT knowledge_base с rewrite => array('slug' => 'kb') WordPress создаёт примерно такие правила:

// Архив: /kb/
/kb/?$                    -> index.php?post_type=knowledge_base

// Одиночная запись: /kb/nastrojka-php8/
/kb/([^/]+)/?$           -> index.php?post_type=knowledge_base&name=$matches[1]

// Пагинация архива: /kb/page/2/
/kb/page/([0-9]{1,})/?$  -> index.php?post_type=knowledge_base&paged=$matches[1]

// Фид: /kb/feed/
/kb/feed/(feed|rdf|rss|rss2|atom)/?$ -> index.php?post_type=knowledge_base&feed=$matches[1]
/kb/feed/?$                      -> index.php?post_type=knowledge_base&feed=rss2

Добавление своего правила

Если нужен URL вида /kb/tag/php8/ (таксономия в URL), используйте хук generate_rewrite_rules:

/**
 * Добавляем правило /kb/tag/%term%/ для таксономии kb_tag.
 * URL: /kb/tag/php8/
 */
add_filter('generate_rewrite_rules', function ($wp_rewrite) {

    // Правило для архива таксономии
    $wp_rewrite->rules = array(
        // Наш паттерн: /kb/tag/ИМЯ_МЕТКИ/
        'kb/tag/([^/]+)/?$' => 'index.php?kb_tag=$matches[1]',

        // Пагинация: /kb/tag/php8/page/2/
        'kb/tag/([^/]+)/page/([0-9]{1,})/?$' => 'index.php?kb_tag=$matches[1]&paged=$matches[2]',
    ) + $wp_rewrite->rules;
});
Важно: после добавления своего правила обязательно перейдите в Настройки → Постоянные ссылки и нажмите «Сохранить», чтобы WordPress обновил правила перезаписи.

Проверка правил

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

add_action('init', function () {
    global $wp_rewrite;
    // Выведет все правила в debug-лог
    error_log(print_r($wp_rewrite->rules, true));
}, 999);

Или используйте плагин Rewrite Rules Inspector — он покажет все правила в админке и позволит отфильтровать их по CPT.

Заключение

Custom Post Type — это один из самых мощных инструментов WordPress. Он позволяет выйти за рамки «блог-платформы» и построить на WordPress каталог, портфолио, базу знаний, список вакансий или что угодно ещё.

Ключевые моменты, которые стоит запомнить:

  • Регистрация CPT — одна функция register_post_type() на хуке init с приоритетом 5–10.
  • Обязательные элементы массива аргументов — labels (хотя бы name и singular_name), public и supports.
  • Не забывайте сбрасывать постоянные ссылки после изменения slug или has_archive.
  • Для REST API и Гутенберга обязательно 'show_in_rest' => true.
  • Таксономии регистрируйте раньше CPT (на более низком приоритете хука) или привязывайте после через register_taxonomy_for_object_type().
  • Код CPT лучше держать в дочерней теме или собственном плагине — не в родительской теме.

Полный рабочий код из этой статьи можно взять за основу для любого проекта. Замените ярлыки, slug, supports и таксономии на свои — и через пять минут у вас в админке появится новый раздел, готовый к наполнению.

Комментарии

Оставить комментарий

Имя видно всем. Сообщения проходят модерацию. Правила обсуждения.

  1. Admin администратор
    • вап
    • авпвп
    • вап
    • вапвап
    • вапвап
    • вап
  2. Admin администратор Закреплён

    парарапрар

Обычно отвечаю до 18:00 МСК

Вопрос по статье?

Напишите — разберём на вашем проекте.

Написать в Telegram Без автоответчика · пн–пт

Обсудить задачуОтвет в рабочий день

Написать