Custom Post Type (CPT) cho phép tạo content type ngoài Post và Page mặc định — Service, Câu chuyện khách, Product, Event. Function register_post_type() nhận hơn 25 tham số chia 4 nhóm, chọn sai capability hoặc rewrite gây bug 404 khó debug.
Bài này hướng dẫn 7 nhóm tham số quan trọng, vì sao show_in_rest bắt buộc cho 2026, capability mapping cho role plugin, pattern flush rewrite an toàn không tốn performance.
register_post_type là gì và khi nào nên tạo CPT thay vì dùng category
register_post_type() là WordPress function declare 1 content type mới với schema riêng biệt với Post và Page. CPT có URL riêng, template riêng, REST endpoint riêng và permissions riêng.
Khác category ở chỗ category là cách phân loại Post còn CPT là entity hoàn toàn khác Post. Quyết định CPT vs category dựa trên 2 câu hỏi: cấu trúc data có khác Post không, có cần URL pattern riêng không.
3 dấu hiệu rõ ràng nên tạo CPT
Không phải mọi nhóm content đều cần CPT — over-engineer dẫn tới admin menu rối và query slow. Ba dấu hiệu khẳng định CPT là lựa chọn đúng.
- Cấu trúc data khác hẳn Post: Service có price, duration, sản phẩm bàn giao. Event có start_date, end_date, location.
- Schema khác Post nhiều — cần CPT để tách meta box rõ ràng.
- URL pattern riêng cần thiết:
/dich-vu/{slug}/hierarchical với pillar và bài con không đạt được với category prefix — CPT cho phép control URL từ slug đến rewrite. - Permissions riêng cho role: Editor edit Post nhưng chỉ Author edit My Project. Cần CPT với capability_type custom để map đúng role.
Khi NÊN dùng category thay vì CPT
Category nhẹ hơn CPT và đủ dùng cho phần lớn site blog. Tránh tạo CPT khi 2 dấu hiệu sau xuất hiện.
- Content cùng schema Post: title + content + featured image, chỉ khác topic — Tin tức, Bài viết SEO, Hướng dẫn đều có thể là Post category, không cần CPT riêng cho mỗi nhóm.
- URL
/category/chấp nhận được: SEO không yêu cầu URL pattern đặc biệt — dùng category đơn giản hơn rồi rewrite slug nếu cần. - Không cần permissions riêng: mọi editor đều có quyền tương đương — không cần tách capability theo nhóm content.
7 nhóm tham số quan trọng — visibility, permalink, capability, UI, REST
Tham khảo signature đầy đủ tại developer.wordpress.org/reference/functions/register_post_type. Hơn 25 tham số chia 7 nhóm theo chức năng để dễ nhớ và pick đúng.
Nhóm Visibility — 5 tham số
Quyết định CPT có hiện ra public hay chỉ dùng internal. Bốn tham số sau là cốt lõi của nhóm visibility.
- public (bool): shortcut set 4 tham số khác cùng lúc — true là visible cả admin và frontend, false là internal only không có URL riêng.
- show_ui (bool): hiện trong admin menu — set false cho CPT internal kiểu sync data từ API external, không cho user edit.
- show_in_menu (bool hoặc string): vị trí menu — true là top-level, string là parent slug để nest dưới CPT khác (vd
edit.php?post_type=service). - publicly_queryable (bool): cho phép query CPT qua query var frontend — false là không truy cập được qua URL
/?post_type=service. - exclude_from_search (bool): loại CPT khỏi search results mặc định — phù hợp CPT internal không cần index.
Nhóm Permalinks — 4 tham số
Control URL pattern của CPT. Sai cấu hình nhóm này dẫn tới URL conflict hoặc 404 khi click link admin.
- rewrite (array): array cấu hình URL —
['slug' => 'dich-vu', 'with_front' => false, 'hierarchical' => true]tạo URL/dich-vu/{post_slug}/. - has_archive (bool hoặc string): tạo archive URL
/dich-vu/liệt kê mọi post của CPT — string là custom archive slug khác post type slug. - hierarchical (bool): CPT có parent và child không — true cho Service (pillar và bài con), false cho Câu chuyện khách (flat list).
- query_var (bool hoặc string): đăng ký query var để filter qua URL — false là không filter được qua
?service=abc.
Nhóm Capabilities — 3 tham số
- capability_type (string hoặc array): string singular như
'service'tự generateedit_service,edit_services,delete_servicevà 7 capability khác. - capabilities (array): override capability map manual khi cần kiểm soát từng quyền — ít dùng vì capability_type tự generate đã đủ.
- map_meta_cap (bool): WordPress auto-map meta cap như
edit_postsang custom cap — bật true để work với role plugin Members và User Role Editor.
Nhóm UI và Editor — 6 tham số
- supports (array): features enable cho editor —
['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'revisions', 'author']. - labels (array): 25 string label admin UI từ singular đến plural đến menu — đầy đủ cho UX rõ ràng với editor không kỹ thuật.
- menu_icon (string): URL ảnh, dashicon (
'dashicons-danh mục dự án') hoặc base64 SVG inline. - menu_position (int): vị trí trong sidebar admin — 5 là dưới Posts, 20 là dưới Pages, 100 là cuối.
- taxonomies (array): attach taxonomy có sẵn vào CPT —
['category', 'post_tag']để CPT dùng category Post mặc định. - template (array): default block template cho CPT — chỉ dùng khi CPT có Block Editor support.
show_in_rest bắt buộc cho Block Editor và REST API 2026
Từ 2026 mặc định mọi CPT mới phải set show_in_rest = true. Tham số này control 3 thứ — không bật là CPT mất khả năng tích hợp hệ sinh thái Block và headless WordPress.
3 lý do show_in_rest bắt buộc
show_in_rest từng là optional nhưng nay thành chuẩn de facto. Ba lý do giải thích vì sao plugin nào cũng phải bật.
- Block Editor load qua REST API: set false thì CPT vẫn hiện admin menu nhưng rơi xuống Classic Editor — mất block, mất pattern, mất template editor.
- Endpoint REST auto-create: WordPress tự tạo
/wp-json/wp/v2/{rest_base}— cho phép headless WordPress, ứng dụng di động, Next.js frontend fetch data. - Block patterns và templates require REST: Site Editor cần REST để render pattern preview — không bật là không insert được CPT vào block pattern.
Cấu hình REST đầy đủ
register_post_type('service', [
'show_in_rest' => true,
'rest_base' => 'services', // override URL: /wp-json/wp/v2/services/
'rest_namespace' => 'wp/v2',
'rest_controller_class' => 'WP_REST_Posts_Controller',
// ... các tham số khác
]);
Permalink với rewrite slug và with_front behavior
Tham số rewrite control URL pattern. Hiểu rõ 3 sub-tham số dưới đây tránh được 90% lỗi URL conflict khi WordPress đã có permalink prefix.
3 sub-tham số trong rewrite array
- slug: prefix URL của CPT —
'dich-vu'tạo URL/dich-vu/{post_slug}/, không có trailing slash thì WordPress tự thêm. - with_front: nếu permalink_structure có prefix (vd
/blog/),with_front = truesẽ thêm prefix vào CPT URL thành/blog/dich-vu/...— set false để giữ URL clean không prefix. - hierarchical: trong rewrite array, true cho phép URL nested kiểu
/dich-vu/parent/child/— khác với hierarchical ở root level (parent post type relationship).
Pattern rewrite cho hierarchical CPT
register_post_type('service', [
'hierarchical' => true,
'rewrite' => [
'slug' => 'dich-vu',
'with_front' => false,
'hierarchical' => true,
],
// ... các tham số khác
]);
// URL kết quả: /dich-vu/thiet-ke-website/gia-re/
Capability mapping với map_meta_cap và custom role
Mặc định mọi CPT inherit capability từ Post (capability_type bằng 'post') — Editor edit được Post thì edit được CPT. Khi cần permissions riêng cho CPT, dùng custom capability.
Custom capability_type cho CPT
register_post_type('service', [
'capability_type' => 'service',
'map_meta_cap' => true,
// WordPress auto-generate 10 cap:
// edit_service, edit_services, edit_others_services,
// publish_services, read_private_services, delete_service,
// delete_services, delete_others_services,
// edit_published_services, delete_published_services
]);
// Assign capability cho role qua code:
$role = get_role('editor');
$role->add_cap('edit_services');
$role->add_cap('publish_services');
// ... 8 cap còn lại
3 lưu ý khi setup custom capability
Custom capability quyền lực nhưng dễ tạo bug “user không vào admin được”. Ba lưu ý sau giảm rủi ro xuống tối thiểu.
- Bật map_meta_cap để tương thích role plugin: Members và User Role Editor expect cơ chế map — không bật là 2 plugin này không hiển thị custom cap.
- Assign cap khi activate plugin: dùng register_activation_hook để add cap cho role Administrator — KHÔNG đợi user tự assign qua plugin role.
- Remove cap khi uninstall: uninstall.php phải gỡ cap đã add — không thì role giữ cap rác sau khi user delete plugin.
Meta box riêng với Carbon Fields và ACF — chọn cái nào
CPT cần custom field cho data specific — Service có price, duration, sản phẩm bàn giao. WordPress core có meta box API nhưng UX bare-bones, đa số dev dùng plugin field framework.
Native meta box từ core
Dùng add_meta_box() với custom HTML form. Hoàn toàn không dependency, control 100% markup.
Effort cao — 50-100 dòng cho 1 meta box phức tạp có 5 field.
Phù hợp plugin distribute trên Plugin Directory vì không thể require dependency Carbon Fields hay ACF. Tự code đảm bảo plugin chạy độc lập trên mọi site.
Carbon Fields — free và code-based
Free, code-based (không có UI plugin), cài qua Composer. Pattern: declare field array trong PHP — phù hợp theme custom với Git workflow team.
Web22 dùng Carbon Fields cho 9 CPT meta tab vì lý do version control friendly — declare field trong code là source of truth, không phụ thuộc DB state.
ACF — UI builder và JSON sync
Free + Pro, UI builder trong admin với JSON sync. Phù hợp dev không quen code hoặc client cần edit field structure mà không cần dev support.
Trade-off: dependency plugin trong production, Pro feature (Repeater, Flexible Content, Gallery) yêu cầu license — phải tính chi phí maintain dài hạn.
Flush rewrite rules — pattern an toàn không tốn performance
WordPress cache rewrite rules trong option('rewrite_rules') để tránh re-compute mỗi request. Khi register CPT mới, rules cũ chưa có CPT — URL CPT trả 404 cho tới khi flush.
3 trigger cần flush rewrite
- Đăng ký CPT mới lần đầu: rules cũ không biết về CPT — flush để rebuild cache rules.
- Đổi rewrite slug: đổi từ
'dich-vu'sang'services'bắt buộc flush để URL mới hoạt động. - Đổi has_archive hoặc hierarchical: 2 tham số này thay đổi cấu trúc URL — rules phải rebuild theo.
Pattern flush an toàn chỉ trong activation hook
register_activation_hook(__FILE__, 'w22_activate');
function w22_activate() {
w22_register_service_cpt(); // gọi register_post_type
flush_rewrite_rules(); // flush rules cache
}
function w22_register_service_cpt() {
register_post_type('service', [/* tham số */]);
}
// Init hook luôn register, KHÔNG flush
add_action('init', 'w22_register_service_cpt');
KHÔNG flush trong init — tốn 50-200ms mỗi request, ghi đè rules option mỗi page load. Chỉ flush khi activate plugin hoặc switch theme có CPT mới.
Câu hỏi thường gặp
CPT slug đã có post — đổi slug thì link cũ có chết không?
Có. Đổi rewrite['slug'] nghĩa là mọi URL cũ trả 404.
WordPress không tự redirect URL cũ sang URL mới — phải setup redirect 301 thủ công.
Dùng plugin Redirection hoặc Rank Math để tạo redirect rule từ URL cũ sang URL mới — chi tiết workflow redirect trong các bài về SEO migration của Web22.
register_post_type vào hook nào tốt nhất?
Hook init với priority 0-10. Chạy sau init core nhưng trước query parsing — đảm bảo CPT có sẵn khi WordPress parse URL request.
Tham khảo note chính thức tại developer.wordpress.org/reference/functions/register_post_type — đặc biệt warning về register quá sớm hoặc quá muộn.
Có giới hạn số lượng CPT trên nhiều site không?
WordPress không có hard limit. Practical limit 5-10 CPT mỗi site — quá nhiều là admin menu rối, query slow khi join nhiều CPT trong query phức tạp.
Pattern Web22 thường: 2-3 CPT chính (service, case_study, danh mục dự án), nest sub-content qua hierarchical hoặc taxonomy thay vì tạo thêm CPT.
CPT có thể có sub-CPT không?
Không native. Pattern thay thế: dùng hierarchical = true cho CPT và set parent_id để nest post bên trong CPT đó — kiểu pillar và bài con trong cùng 1 CPT.
Hoặc dùng taxonomy phân loại nội bộ — vd CPT case_study có taxonomy industry để phân nhóm, không cần tạo CPT case_study_fb riêng cho ngành F&B.
Block Editor không hiện custom field metabox cho CPT — fix sao?
Block Editor disable custom field metabox mặc định. Kiểm tra 'supports' => [...] phải có 'custom-fields' trong array — thiếu là metabox bị hide.
Hoặc chuyển sang Block Editor sidebar plugin (Carbon Fields hoặc ACF Pro) thay metabox — UX đẹp hơn và tích hợp Block Editor tốt hơn metabox cổ điển. Tham khảo register_taxonomy WordPress — hierarchical vs flat + term meta 2026 để hiểu term meta tương tự post meta.
Tài nguyên và bước tiếp theo
Nắm CPT là nền — taxonomy, hook chain và plugin structure là các topic chuyên sâu cần học song song để có stack đầy đủ cho project WordPress production.
- register_taxonomy WordPress — hierarchical vs flat + term meta 2026 — phân loại CPT bằng taxonomy custom với term meta.
- Tạo plugin WordPress từ đầu — 7 bước build chuẩn 2026 — đăng ký CPT trong plugin riêng thay vì functions.php.
- Hook, action và filter WordPress — concept fundamental 2026 — hook init là điểm vào của register_post_type.
- Plugin boilerplate WordPress 2026 — WPPB vs Underscores vs custom — boilerplate scaffold CPT sẵn theo OOP pattern.
- Dịch vụ thiết kế website WordPress chuyên nghiệp — tham khảo gói thi công CPT custom với meta box trọn gói.
Cần Web22 setup CPT chuẩn với Block Editor compatibility, capability custom và REST endpoint cho headless? Tư vấn migrate Classic theme lên Block theme FSE — bàn giao kèm capability test, flush rewrite an toàn và document REST schema đầy đủ.


