Skip to content

Все для WordPress, веб-розробки — і не тільки

🔧 Як створити шаблон для кастомного типу записів у WordPress

🔧 Як створити шаблон для кастомного типу записів у 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.phpsingle.phpsingular.phpindex.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:

1single-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
6get_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
60get_sidebar();
61get_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-плагін):

1add_filter( 'template_include', 'techblog_cpt_template', 99 );
2
3function 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 з одним обробником

Якщо кастомних типів кілька, зручно зібрати логіку в одному фільтрі:

1add_filter( 'template_include', 'techblog_cpt_templates', 99 );
2
3function 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__ ):

1add_filter( 'template_include', 'myplugin_cpt_template', 99 );
2
3function 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-плагіні:

1add_action( 'init', 'techblog_register_cpt_aktsii' );
2
3function 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

Файл single-{post_type}.php - мінімум коду, автопідхоплення

Дочірня тема, CPT додаєте ви

Файл у дочірній темі - переживає оновлення батьківської

Блочна тема (FSE), CPT через плагін

Фільтр template_include - не залежить від структури теми

CPT поширюється як плагін

Фільтр + шаблон всередині папки плагіна

Багато CPT зі схожою структурою

Фільтр + загальний single-cpt.php + template-parts

На практиці ми на techblog.sdstudio.top найчастіше використовуємо комбінацію: CPT реєструємо через код у MU-плагіні, а шаблони кладемо файлами в дочірню тему. Це дає автопідхоплення без зайвих фільтрів, а оновлення батьківської теми нічого не ламають.

Якщо ви тільки починаєте розбиратися з кастомними типами, почніть із файлу single-{post_type}.php. Це п'ять хвилин роботи і миттєвий результат. А коли відчуєте, що можливостей файлового підходу не вистачає (потрібно вантажити шаблони з плагіна або змінювати їх на льоту), переходьте на template_include. Обидва методи абсолютно легітимні й підтримуються ядром WordPress.