Розробка користувацьких типів записів WordPress
WordPress з коробки працює з двома типами контенту: "Записи" та "Сторінки". Коли сайту потребуються Проекти, Вакансії, Нерухомість, Відгуки чи Товари — для кожного створюється власний тип записів (CPT). CPT дає окремий розділ в /wp-admin, власні URL-адреси, архівні сторінки та повний контроль над структурою даних. Реєстрація CPT з базовими налаштуваннями займає кілька годин; повна настройка з URL-адресами, користувацькими колонками та можливостями пошуку — 1–2 дні.
Реєстрація через register_post_type
add_action('init', function () {
register_post_type('project', [
'labels' => [
'name' => 'Проекти',
'singular_name' => 'Проект',
'add_new' => 'Додати проект',
'add_new_item' => 'Новий проект',
'edit_item' => 'Редагувати проект',
'new_item' => 'Новий проект',
'view_item' => 'Переглянути проект',
'search_items' => 'Пошук проектів',
'not_found' => 'Проекти не знайдені',
'not_found_in_trash' => 'Кошик порожній',
'menu_name' => 'Проекти',
],
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => ['slug' => 'projects', 'with_front' => false],
'capability_type' => 'post',
'has_archive' => 'projects',
'hierarchical' => false,
'menu_position' => 5,
'menu_icon' => 'dashicons-portfolio',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
'show_in_rest' => true, // обов'язково для Gutenberg
'rest_base' => 'projects',
]);
});
has_archive => 'projects' створює сторінку архіву за адресою /projects/ — туди попадають усі записи типу. show_in_rest => true робить тип доступним через REST API та в редакторі Gutenberg.
Після реєстрації нового CPT потрібно оновити правила URL: в /wp-admin → Налаштування → Постійні посилання натисніть "Зберегти" або програмно:
register_activation_hook(__FILE__, function () {
add_action('init', 'register_project_cpt');
flush_rewrite_rules();
});
Користувацькі колонки у списку записів
За замовчуванням, список проектів показує лише заголовок і дату. Додайте потрібні колонки:
// Реєструємо колонки
add_filter('manage_project_posts_columns', function (array $columns): array {
$new = [];
foreach ($columns as $key => $title) {
$new[$key] = $title;
if ($key === 'title') {
$new['client'] = 'Клієнт';
$new['year'] = 'Рік';
$new['featured'] = 'На головній';
}
}
unset($new['comments']);
return $new;
});
// Виводимо дані колонок
add_action('manage_project_posts_custom_column', function (string $column, int $post_id): void {
switch ($column) {
case 'client':
echo esc_html(get_post_meta($post_id, '_project_client', true) ?: '—');
break;
case 'year':
echo esc_html(get_post_meta($post_id, '_project_year', true) ?: '—');
break;
case 'featured':
$is_featured = get_post_meta($post_id, '_project_featured', true);
echo $is_featured ? '⭐' : '—';
break;
}
}, 10, 2);
// Робимо колонку сортованою
add_filter('manage_edit-project_sortable_columns', function (array $columns): array {
$columns['year'] = 'year';
return $columns;
});
add_action('pre_get_posts', function (WP_Query $query): void {
if (!is_admin() || !$query->is_main_query()) return;
if ($query->get('orderby') === 'year') {
$query->set('meta_key', '_project_year');
$query->set('orderby', 'meta_value_num');
}
});
Фільтр за таксономією у списку
add_action('restrict_manage_posts', function (string $post_type): void {
if ($post_type !== 'project') return;
$taxonomy = get_taxonomy('project_category');
wp_dropdown_categories([
'taxonomy' => 'project_category',
'name' => 'project_category',
'show_option_all' => 'Усі категорії',
'selected' => $_GET['project_category'] ?? 0,
'value_field' => 'slug',
'hierarchical' => true,
]);
});
Включення в пошук WordPress
За замовчуванням, стандартний пошук WordPress не включає користувацькі CPT:
add_action('pre_get_posts', function (WP_Query $query): void {
if ($query->is_search() && !is_admin() && $query->is_main_query()) {
$post_types = $query->get('post_type') ?: ['post'];
if (!is_array($post_types)) {
$post_types = [$post_types];
}
$query->set('post_type', array_merge($post_types, ['project', 'vacancy']));
}
});
Пов'язані записи між CPT
Стандартний спосіб пов'язати два записи — зберігати ID у post meta:
// Збереження зв'язків
update_post_meta($project_id, '_related_cases', array_map('absint', $case_ids));
// Отримання пов'язаних кейсів
$case_ids = get_post_meta($project_id, '_related_cases', true);
if (!empty($case_ids)) {
$cases = get_posts([
'post_type' => 'case',
'post__in' => $case_ids,
'orderby' => 'post__in',
'posts_per_page' => -1,
]);
}
Для складних двосторонніх зв'язків (багато-до-багатьох) використовуйте плагін Posts 2 Posts або власну проміжну таблицю.
CPT vs таксономія vs користувацькі поля
Частої помилки — робити CPT там, де потребується інший інструмент:
- CPT потребується, коли сутність має власні сторінки, архів, редактор контенту
- Таксономія — коли потрібна група/фільтрація (категорія, тег)
- Користувацькі поля — коли потрібно зберігати атрибут існуючого запису (ціна, рік, адреса)
Реєстрація CPT з нульовими шаблонами і без повноцінного контенту — порожня трата структури. Якщо "тип" не потребується на фронтенді — це, ймовірно, просто таксономія до вже існуючого CPT.







