Rejestracja CPT w motywie
Artykuł z 21 kwietnia, 2026
Jak rejestrować Custom Post Types w motywie, bez użycia wtyczki
Custom Post Type (CPT) to jeden z fundamentów każdego niestandardowego projektu w WordPressie. Jeśli klient potrzebuje czegoś więcej niż posty i strony – np. Oferty pracy, Portfolio, Członkowie zespołu czy Opinie – właśnie po to sięgamy po CPT.
W tym wpisie zawarta jest instrukcja, jak rejestrować CPT w sposób czysty i spójny z konwencjami przyjętymi w projekcie.
Zanim jednak przejdziesz dalej, warto zapoznać się też z oficjalną dokumentacją WordPressa – znajdziesz tam pełną listę wszystkich dostępnych parametrów funkcji register_post_type(): developer.wordpress.org/reference/functions/register_post_type
Gdzie umieścić kod?
Nie wrzucamy rejestracji CPT bezpośrednio do functions.php. Zamiast tego tworzymy osobny plik w folderze includes motywu:
kadence-child/
├── functions.php
└── includes/
└── custom-post-type-projects.phpNastępnie w functions.php podpinamy go jedną linijką:
require_once get_template_directory() . '/includes/custom-post-type-projects.php';Każdy CPT rejestrujemy w tej samej konwencji – jako oddzielne pliki w folderze includes.
Zasada: slug po angielsku, URL po polsku (bądź w innych językach)
Slug używany przy rejestracji CPT – ten podawany jako pierwszy argument register_post_type() – zawsze piszemy po angielsku.
Dlaczego? Bo ten identyfikator pojawia się wszędzie w kodzie: w zapytaniach WP_Query, w funkcjach pomocniczych, w warunkach is_singular(), w nazwach hooków. Poza tym angielski jest standardowym językiem pisania kodu – nazwy zmiennych, funkcji, klas i identyfikatorów piszemy po angielsku niezależnie od tego, w jakim języku jest strona. Angielski slug jest neutralny, czytelny dla każdego dewelopera i nie sprawia problemów z kodowaniem znaków.
// ✅ Dobrze
register_post_type( 'projects', $args );
register_post_type( 'team-members', $args );
register_post_type( 'job-offers', $args );
// ❌ Źle – polskie znaki i język ojczysty w identyfikatorze
register_post_type( 'projekty', $args );
register_post_type( 'członkowie-zespołu', $args );To samo dotyczy nazwy funkcji – register_post_type_projects(), nie register_post_type_projekty().
Jeśli jednak zależy Ci, żeby adres URL na stronie wyglądał po polsku (np. /projekty/nazwa-projektu/ zamiast /projects/nazwa-projektu/), możesz to ustawić niezależnie przez parametr rewrite. Slug rejestracji i slug w URL to dwie osobne rzeczy – nie musisz wybierać jednego z nich.
Ważne: ustawienie polskiego slugu na sztywno w rewrite ma sens tylko na stronach jednojęzycznych. Jeśli strona jest wielojęzyczna (WPML, Polylang itp.), każda wersja językowa potrzebuje własnego slugu – i każda z tych wtyczek ma do tego dedykowany interfejs. W takim przypadku w rewrite zostawiasz domyślny angielski slug (lub pomijasz ten parametr), a tłumaczenia slugów konfigurujesz po stronie wtyczki językowej.
Anatomia funkcji rejestrującej CPT
Poniżej pełny przykład dla fikcyjnego CPT „Projekty”. Przeanalizuj każdy parametr – komentarze wyjaśniają, co robi i kiedy go zmieniać.
if ( ! function_exists( 'register_post_type_projects' ) ) {
function register_post_type_projects() {
$labels = array(
// Nazwa w liczbie mnogiej – widoczna np. w bocznym menu WP
'name' => 'Projekty',
// Nazwa w liczbie pojedynczej – pojawia się np. w "Edytuj Projekt"
'singular_name' => 'Projekt',
// Etykieta w bocznym menu admina
'menu_name' => 'Projekty',
// Nazwa na górnym pasku administracyjnym (Admin Bar)
'name_admin_bar' => 'Projekt',
// Tekst przycisku "Dodaj nowy" na liście wpisów
'add_new' => 'Dodaj nowy',
// Tytuł strony edytora przy tworzeniu nowego wpisu
'add_new_item' => 'Dodaj nowy projekt',
// Etykieta przy tworzeniu nowego wpisu
'new_item' => 'Nowy projekt',
// Tytuł strony edytora przy edycji istniejącego wpisu
'edit_item' => 'Edytuj projekt',
// Tekst linku "Zobacz" prowadzącego na frontend
'view_item' => 'Zobacz projekt',
// Etykieta linku "Wszystkie..." w podmenu
'all_items' => 'Wszystkie projekty',
// Placeholder pola wyszukiwania w panelu admina
'search_items' => 'Szukaj projektów',
// Etykieta nadrzędnego wpisu (używana tylko gdy hierarchical => true)
'parent_item_colon' => 'Nadrzędny projekt:',
// Komunikat wyświetlany gdy lista wpisów jest pusta
'not_found' => 'Nie znaleziono projektów',
// Komunikat wyświetlany gdy kosz jest pusty
'not_found_in_trash' => 'Nie znaleziono projektów w koszu',
);
$args = array(
'labels' => $labels,
// Czy CPT jest widoczny publicznie – na frontendzie i w adminie.
// Ustawiając false ukrywasz CPT całkowicie (np. dane wewnętrzne).
'public' => true,
// Czy wpisy można odpytywać przez URL (?post_type=projects).
// Zazwyczaj identyczne z 'public'.
'publicly_queryable' => true,
// Czy CPT pojawia się w panelu administracyjnym.
'show_ui' => true,
// Czy CPT jest dostępny w opcjach tworzenia menu (Wygląd → Menu).
'show_in_nav_menus' => true,
// Czy CPT pojawia się jako pozycja w bocznym menu admina.
// Możesz też podać slug istniejącego menu – wtedy CPT staje się jego podpozycją,
// np. 'tools.php' umieszcza go pod "Narzędzia".
'show_in_menu' => true,
// Czy CPT jest dostępny przez WP REST API.
// ZAWSZE ustawiaj true jeśli używasz edytora blokowego (Gutenberg)!
'show_in_rest' => true,
// Ikona w bocznym menu admina.
// Pełna lista: https://developer.wordpress.org/resource/dashicons/
// Możesz też podać URL do własnej ikony (SVG lub obraz 20x20 px).
'menu_icon' => 'dashicons-portfolio',
// Pozycja w menu bocznym. null = domyślna (poniżej Komentarzy).
// Inne przydatne wartości: 5 (po Wpisach), 20 (po Stronach), 25 (po Komentarzach).
'menu_position' => null,
// Czy CPT ma własną zmienną zapytania (?projects=slug).
'query_var' => true,
// Model uprawnień. 'post' dziedziczy uprawnienia standardowych wpisów.
// Zmień na 'page' jeśli CPT ma być hierarchiczny.
'capability_type' => 'post',
// false = CPT płaski (jak posty).
// true = CPT hierarchiczny (jak strony) – wpisy mogą mieć dzieci.
'hierarchical' => false,
// Czy WordPress ma tworzyć stronę archiwum (/projects/).
// Możesz też podać string – stanie się on slugiem archiwum.
'has_archive' => false,
// true = wpisy CPT są WYKLUCZONE z wyników wyszukiwania WP.
// false = wpisy pojawiają się w wynikach wyszukiwania.
'exclude_from_search'=> false,
// Czy wpisy CPT można eksportować przez Narzędzia → Eksportuj.
'can_export' => true,
// Czy wpisy CPT mają być usuwane razem z ich autorem.
'delete_with_user' => false,
// Które pola i sekcje mają być dostępne w edytorze.
// Dostępne wartości:
// 'title' – pole tytułu
// 'editor' – edytor treści (Gutenberg / TinyMCE)
// 'thumbnail' – obrazek wyróżniający
// 'excerpt' – pole skrótu / opisu
// 'author' – pole autora wpisu
// 'comments' – sekcja komentarzy
// 'custom-fields' – klasyczna metabox z polami niestandardowymi
// 'revisions' – historia wersji
// 'page-attributes' – pola "Kolejność" i "Rodzic"
'supports' => array(
'title',
'editor',
'thumbnail',
'custom-fields',
'revisions',
'page-attributes',
),
// Taksonomie przypisane do CPT.
// Możesz użyć wbudowanych ('category', 'post_tag')
// lub własnych zarejestrowanych przez register_taxonomy().
// WAŻNE: niestandardową taksonomię musisz najpierw zarejestrować
// w register_taxonomy(), a następnie tu do niej się odwołać.
'taxonomies' => array( 'project-category' ),
// Konfiguracja przepisywania adresów URL (permalinków).
//
// Domyślnie WordPress użyje slugu rejestracji jako bazy URL,
// więc adresy wyglądałyby tak: /projects/nazwa-projektu/
//
// Parametr 'rewrite' pozwala to zmienić niezależnie od slugu rejestracji.
// Dzięki temu slug w kodzie pozostaje angielski, a URL na stronie – polski.
//
// 'slug' => 'projekty' – baza URL: /projekty/nazwa-projektu/
// 'with_front' => true – czy dodawać prefix z ustawień permalinków
// (np. /blog/ jeśli masz ustawiony taki prefix).
// Zazwyczaj false dla CPT, żeby nie duplikować prefixu.
// 'feeds' => false – czy generować feed RSS dla tego CPT
// 'pages' => true – czy obsługiwać paginację (/projekty/page/2/)
//
// Możesz też ustawić rewrite => false, żeby całkowicie wyłączyć permalinki
// (np. dla CPT używanego wyłącznie wewnętrznie, bez stron na frontendzie).
//
// ⚠️ UWAGA – strony wielojęzyczne (WPML, Polylang i podobne):
// Jeśli strona działa w więcej niż jednym języku, nie wpisuj tu
// slugu na sztywno. Każda wtyczka do wielojęzyczności ma własny
// mechanizm tłumaczenia slugów CPT – ustaw go tam, a w 'rewrite'
// zostaw domyślny angielski slug (lub całkowicie pomiń ten parametr).
// Wpisanie polskiego slugu na sztywno przy wielojęzycznej stronie
// spowoduje, że tylko jedna wersja językowa będzie działać poprawnie.
'rewrite' => array(
'slug' => 'projekty',
'with_front' => false,
'feeds' => false,
'pages' => true,
),
);
// Pierwszy argument to unikalny slug CPT.
// Zasady: tylko małe litery, cyfry i myślniki, max 20 znaków.
// Unikaj nazw zarezerwowanych przez WP: post, page, attachment,
// revision, nav_menu_item, custom_css i kilku innych.
register_post_type( 'projects', $args );
}
// Rejestrujemy CPT na hooku 'init' z priorytetem 5 (domyślny to 10).
// Priorytet 5 sprawia, że CPT rejestruje się wcześniej – taksonomie
// i inne elementy zależne mogą się wtedy do niego bezpiecznie podpiąć.
add_action( 'init', 'register_post_type_projects', 5 );
}Szablon do skopiowania
Gdy potrzebujesz kolejnego CPT, skopiuj ten blok, zamień wszystkie wystąpienia SLUG oraz etykiety i gotowe:
if ( ! function_exists( 'register_post_type_SLUG' ) ) {
function register_post_type_SLUG() {
$labels = array(
'name' => 'NAZWA MNOGA',
'singular_name' => 'NAZWA POJEDYNCZA',
'menu_name' => 'NAZWA W MENU',
'name_admin_bar' => 'NAZWA POJEDYNCZA',
'add_new' => 'Dodaj nowy',
'add_new_item' => 'Dodaj nowy ELEMENT',
'new_item' => 'Nowy ELEMENT',
'edit_item' => 'Edytuj ELEMENT',
'view_item' => 'Zobacz ELEMENT',
'all_items' => 'Wszystkie ELEMENTY',
'search_items' => 'Szukaj ELEMENTÓW',
'parent_item_colon' => 'Nadrzędny ELEMENT:',
'not_found' => 'Nie znaleziono ELEMENTÓW',
'not_found_in_trash' => 'Nie znaleziono ELEMENTÓW w koszu',
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_nav_menus' => true,
'show_in_menu' => true,
'show_in_rest' => true,
'menu_icon' => 'dashicons-IKONA',
'menu_position' => null,
'query_var' => true,
'capability_type' => 'post',
'hierarchical' => false,
'has_archive' => false,
'exclude_from_search'=> false,
'can_export' => true,
'delete_with_user' => false,
'supports' => array(
'title',
'editor',
'thumbnail',
),
'taxonomies' => array(),
'rewrite' => array(
'slug' => 'SLUG-PO-POLSKU',
'with_front' => false,
'feeds' => false,
'pages' => true,
),
);
register_post_type( 'SLUG', $args );
}
add_action( 'init', 'register_post_type_SLUG', 5 );
}Checklista po dodaniu nowego CPT
Zanim zamkniesz zadanie, przejdź przez tę listę: