
🔧 Как создать шаблон для кастомного типа записей в WordPress
Вы добавили на сайт кастомный тип записей, скажем, «Акции», «Портфолио» или «Отзывы». Всё работает: записи создаются, мета-поля заполняются. Но когда вы открываете страницу такой записи на фронте, она выглядит как обычный пост блога. Та же вёрстка, те же сайдбары, никакой разницы. А ведь вы заводили кастомный тип именно для того, чтобы контент подавался иначе.
Проблема в том, что WordPress по умолчанию рендерит любой кастомный пост через single.php, универсальный шаблон для всех одиночных записей. Чтобы получить индивидуальную вёрстку под конкретный тип, нужен собственный файл шаблона. И он делается на удивление просто.
Ниже, пошаговый разбор: от иерархии шаблонов до двух способов создания (через файл темы и через фильтр template_include), с живыми примерами кода.
💡 Быстрый обзор:
- Разбираемся, как WordPress ищет шаблон для кастомного типа, приоритет
single-{post_type}.php - Копируем
single.phpтемы, переименовываем под свой CPT и кастомизируем - Рассматриваем альтернативный метод, через фильтр
template_include(удобно, если не хотите трогать тему) - Создаём сам кастомный тип через плагин Custom Post Type UI, быстро и без кода
1. Как WordPress выбирает шаблон для кастомного типа
Иерархия шаблонов WordPress, это цепочка файлов, по которой ядро ищет подходящий шаблон для отображения страницы. Для стандартных записей цепочка длинная: single-post.php → single.php → singular.php → index.php. Для кастомных типов она короче, но логика та же.
Когда посетитель открывает страницу кастомной записи типа aktsii, WordPress проверяет файлы в таком порядке:
single-aktsii.php, шаблон конкретно под этот кастомный типsingle.php, общий шаблон одиночной записиsingular.php, шаблон для любого единичного контента (пост, страница, CPT)index.php, финальный fallback
Первый найденный файл из цепочки и используется для рендеринга. Если single-aktsii.php существует, WordPress возьмёт его и остановится. Не нашёл, идёт дальше до single.php. Именно поэтому у вас кастомный тип выглядит как обычный пост: файла single-aktsii.php нет, и ядро спускается до общего single.php.
Отсюда и решение: создаём single-{post_type}.php, и WordPress подхватывает его автоматически.
2. Создаём файл шаблона: копируем single.php и переименовываем
Самый простой и надёжный способ. Работает с любой классической темой, не требует плагинов и фильтров.
Шаг 1: найдите single.php темы. Он лежит в корне папки темы: /wp-content/themes/ваша-тема/single.php. Если тема использует FSE (Full Site Editing) и построена на блоках, этого файла может не быть, и способ с фильтром (template_include) будет удобнее (раздел 4).
Шаг 2: скопируйте и переименуйте. Копируете single.php и называете по правилу single-{slug}.php: вместо {slug} подставьте ярлык кастомного типа (тот, что указан при регистрации, латиницей). Например, для типа с ярлыком aktsii:
1 single-aktsii.php
Для типа с ярлыком portfolio файл будет называться single-portfolio.php. Для testimonials получится single-testimonials.php. Важно: slug должен совпадать с тем, что указан в параметре 'rewrite' => array('slug' => '...') при регистрации.
Шаг 3: положите файл обратно в папку темы. WordPress сам подхватит его для всех записей этого типа. Никаких дополнительных настроек, иерархия шаблонов делает это автоматически.
Шаг 4: проверьте. Откройте любую запись кастомного типа на фронте. Если видите ту же страницу, что и раньше, проверьте имя файла (регистр, дефисы вместо подчёркиваний) и сбросьте кэш. Если файл создан в дочерней теме, а родительская тоже содержит single-{post_type}.php, дочерняя имеет приоритет.
3. Кастомизируем содержимое шаблона
Скопированный single.php пока ничем не отличается от оригинала, он просто рендерит тот же универсальный макет. Теперь наполняем его контентом, специфичным для кастомного типа.
3.1. Базовая структура: выводим кастомные поля
Предположим, у кастомного типа aktsii есть поля: aktsiya_data_start, aktsiya_data_end и aktsiya_skidka (созданы через ACF, Meta Box или вручную). Вот минимальный шаблон, который их выводит:
1 <?php 2 /** 3 * Шаблон для кастомного типа «Акции» (single-aktsii.php) 4 */ 5 6 get_header(); 7 ?> 8 9 <main id="main" class="site-main" role="main"> 10 11 <?php while ( have_posts() ) : the_post(); ?> 12 13 <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> 14 15 <header class="entry-header"> 16 <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?> 17 </header> 18 19 <?php if ( has_post_thumbnail() ) : ?> 20 <div class="post-thumbnail"> 21 <?php the_post_thumbnail( 'large' ); ?> 22 </div> 23 <?php endif; ?> 24 25 <div class="entry-content"> 26 <?php the_content(); ?> 27 28 <div class="custom-fields"> 29 <?php 30 $data_start = get_post_meta( get_the_ID(), 'aktsiya_data_start', true ); 31 $data_end = get_post_meta( get_the_ID(), 'aktsiya_data_end', true ); 32 $skidka = get_post_meta( get_the_ID(), 'aktsiya_skidka', true ); 33 34 if ( $data_start ) { 35 echo '<p><strong>Дата начала:</strong> ' . esc_html( $data_start ) . '</p>'; 36 } 37 if ( $data_end ) { 38 echo '<p><strong>Дата окончания:</strong> ' . esc_html( $data_end ) . '</p>'; 39 } 40 if ( $skidka ) { 41 echo '<p><strong>Скидка:</strong> ' . esc_html( $skidka ) . '%</p>'; 42 } 43 ?> 44 </div> 45 </div> 46 47 </article> 48 49 <?php 50 if ( comments_open() || get_comments_number() ) : 51 comments_template(); 52 endif; 53 ?> 54 55 <?php endwhile; ?> 56 57 </main> 58 59 <?php 60 get_sidebar(); 61 get_footer();
Это полноценный рабочий шаблон, который можно класть в тему как есть. Он выводит заголовок, миниатюру, контент и три произвольных поля в отдельном блоке.
3.2. Что ещё можно сделать с шаблоном
Дальше, бесконечное поле для кастомизации под конкретную задачу:
- Убрать сайдбар, достаточно убрать вызов
get_sidebar(). - Изменить HTML-обёртку, заменить
<article>на<div>с нужным классом, добавить свою сетку. - Вызвать конкретный template-part, например,
get_template_part( 'template-parts/content', 'aktsii' )и хранить логику в отдельном файле. - Подключить кастомные стили, через
wp_enqueue_style()внутриfunctions.phpс проверкойis_singular( 'aktsii' ).
Важный момент: если тема обновляется, файл в родительской теме может быть затёрт. Поэтому либо работайте в дочерней теме, либо используйте метод с фильтром из следующего раздела.
4. Альтернативный подход: фильтр template_include
Если менять файлы темы не хочется (или тема блочная, без классического single.php), шаблон можно назначить через хук template_include. Он срабатывает до того, как WordPress подключает файл шаблона, и позволяет переопределить путь.
Минус: файл шаблона придётся хранить либо в плагине, либо в дочерней теме, код должен указать корректный путь. Плюс: логика не теряется при обновлении родительской темы.
4.1. Пример: один CPT, один шаблон
Добавьте в functions.php дочерней темы (или в MU-плагин):
1 add_filter( 'template_include', 'techblog_cpt_template', 99 ); 2 3 function techblog_cpt_template( $template ) { 4 if ( is_singular( 'aktsii' ) ) { 5 $custom_template = get_stylesheet_directory() . '/single-aktsii.php'; 6 if ( file_exists( $custom_template ) ) { 7 return $custom_template; 8 } 9 } 10 return $template; 11 }
Код проверяет: открыта ли страница кастомного типа aktsii? Если да, ищет файл single-aktsii.php в папке дочерней темы (get_stylesheet_directory()) и возвращает его. Не нашёл, возвращает стандартный $template без изменений.
4.2. Пример: несколько CPT с одним обработчиком
Если кастомных типов несколько, удобно собрать логику в одном фильтре:
1 add_filter( 'template_include', 'techblog_cpt_templates', 99 ); 2 3 function techblog_cpt_templates( $template ) { 4 $cpt_templates = array( 5 'aktsii' => 'single-aktsii.php', 6 'portfolio' => 'single-portfolio.php', 7 'testimonials' => 'single-testimonials.php', 8 ); 9 10 foreach ( $cpt_templates as $cpt => $template_file ) { 11 if ( is_singular( $cpt ) ) { 12 $custom_template = get_stylesheet_directory() . '/' . $template_file; 13 if ( file_exists( $custom_template ) ) { 14 return $custom_template; 15 } 16 } 17 } 18 19 return $template; 20 }
Теперь каждый CPT получает свой шаблон, а вся логика, в одной функции.
4.3. Хранение шаблонов в плагине
Если вы распространяете кастомный тип как плагин (а не как часть темы), шаблон логично хранить в папке плагина. Тогда путь указывается через plugin_dir_path( __FILE__ ):
1 add_filter( 'template_include', 'myplugin_cpt_template', 99 ); 2 3 function myplugin_cpt_template( $template ) { 4 if ( is_singular( 'aktsii' ) ) { 5 $custom_template = plugin_dir_path( __FILE__ ) . 'templates/single-aktsii.php'; 6 if ( file_exists( $custom_template ) ) { 7 return $custom_template; 8 } 9 } 10 return $template; 11 }
Этот паттерн используют многие популярные плагины (WooCommerce, Easy Digital Downloads, The Events Calendar), они несут шаблоны внутри себя и подключают их через фильтр.
5. Создание кастомного типа записей
Сам шаблон мы сделали, но для полноты картины, пара слов о том, откуда берётся кастомный тип.
5.1. Регистрация через код
Минимальная регистрация CPT в functions.php или MU-плагине:
1 add_action( 'init', 'techblog_register_cpt_aktsii' ); 2 3 function techblog_register_cpt_aktsii() { 4 $labels = array( 5 'name' => 'Акции', 6 'singular_name' => 'Акция', 7 'menu_name' => 'Акции', 8 ); 9 10 $args = array( 11 'labels' => $labels, 12 'public' => true, 13 'has_archive' => true, 14 'supports' => array( 'title', 'editor', 'thumbnail' ), 15 'rewrite' => array( 'slug' => 'aktsii' ), 16 'show_in_rest' => true, 17 ); 18 19 register_post_type( 'aktsii', $args ); 20 }
Здесь важно: параметр 'rewrite' => array( 'slug' => 'aktsii' ) задаёт ярлык, который используется в URL и в имени файла шаблона. Если slug задан как aktsii, файл шаблона должен называться single-aktsii.php.
5.2. Через плагин Custom Post Type UI
Если редактировать functions.php не хочется, CPT создаётся визуально через бесплатный плагин Custom Post Type UI на WordPress.org. После установки:
- Переходите в админке: CPT UI → Add/Edit Post Types
- Заполняете поля: Post Type Slug (латиницей), Plural Label, Singular Label
- На вкладке Settings, отмечаете какие элементы редактора нужны (title, editor, thumbnail)
- Жмёте Add Post Type
Плагин генерирует PHP-код регистрации, который можно экспортировать и вставить в functions.php, удобно, если позже захотите убрать зависимость от плагина. Тот самый slug, который вы указали в CPT UI, и пойдёт в имя файла шаблона.
⁉️🤔 Частые вопросы
Что делать, если шаблон не подхватывается после создания файла?
Сбросьте пермалинки: зайдите в Настройки → Постоянные ссылки и нажмите «Сохранить изменения» (менять ничего не надо). WordPress перестроит правила маршрутизации. Если не помогло, проверьте имя файла: slug кастомного типа и имя файла должны совпадать (регистр, дефисы и подчёркивания). Третий шаг, очистите кэш плагина кэширования, если он установлен. Пермалинки нужно сбрасывать после любой регистрации нового кастомного типа, не только при проблемах с шаблоном. Это стандартная практика: зашли в Настройки → Постоянные ссылки → Сохранить, и новые URL сразу работают.
Можно ли использовать один шаблон для нескольких кастомных типов?
Да, двумя способами. Первый, создать физический файл
single-aktsii.php, а для второго типа (например,portfolio) скопировать его какsingle-portfolio.php. Второй, через фильтрtemplate_include(раздел 4): в массиве$cpt_templatesуказать разным типам один и тот же файл шаблона. Но на практике у разных CPT почти всегда разная вёрстка, копировать и править проще, чем городить условную логику внутри шаблона. Если типов действительно много и логика одинаковая, создайте общийsingle-cpt.php, внутри проверяйтеget_post_type()и подключайте соответствующий template-part черезget_template_part( 'template-parts/content', get_post_type() ). Это чистый и масштабируемый подход.
Нужно ли создавать archive-{post_type}.php** для страницы архива?**
Желательно, но не обязательно. Без него WordPress использует
archive.phpилиindex.php. Если для CPT включён параметр'has_archive' => trueи архив открывается по адресу/aktsii/, а файлаarchive-aktsii.phpнет, будет использован общийarchive.php. Создавать отдельный архивный шаблон стоит тогда, когда сетка постов, состав колонок или сайдбар должны отличаться от блогового архива.
Работает ли это с блочными темами (FSE)?
В блочных темах классические PHP-шаблоны через файлы темы, не основной путь. Однако фильтр
template_include(раздел 4) работает и в FSE: вы можете положить файлsingle-aktsii.phpв дочернюю тему или плагин и подключить через фильтр. Альтернативно, в редакторе сайта (Site Editor) можно создать шаблон для конкретного кастомного типа через интерфейс: Appearance → Editor → Templates → Add New Template → Single Item: Aktsii. WordPress сохранит его как HTML-шаблон в базе данных, и он будет работать без файлов в теме.
Что ставить в 2026: файл или фильтр?
Короткая матрица для выбора подхода:
Ситуация | Что использовать |
|---|---|
Классическая тема, один-два CPT | Файл |
Дочерняя тема, CPT добавляете вы | Файл в дочерней теме - переживает обновления родительской |
Блочная тема (FSE), CPT через плагин | Фильтр |
CPT распространяется как плагин | Фильтр + шаблон внутри папки плагина |
Много CPT с похожей структурой | Фильтр + общий |
На практике мы на techblog.sdstudio.top чаще всего используем комбинацию: CPT регистрируем через код в MU-плагине, а шаблоны кладём файлами в дочернюю тему. Это даёт автоподхват без лишних фильтров, а обновления родительской темы ничего не ломают.
Если вы только начинаете разбираться с кастомными типами, начните с файла single-{post_type}.php. Это пять минут работы и мгновенный результат. А когда почувствуете, что возможностей файлового подхода не хватает (нужно грузить шаблоны из плагина или менять их на лету), переходите на template_include. Оба метода абсолютно легитимны и поддерживаются ядром WordPress.



