Khi một website cần lưu loại nội dung không vừa khít với “Bài viết” hay “Trang” — danh mục dịch vụ, dự án portfolio, sản phẩm, đối tác — thì gom hết vào Bài viết sẽ rối và khó tách giao diện. Lúc này bạn cần một loại nội dung riêng. WordPress cho phép khai báo nó bằng vài chục dòng code, không cần plugin trung gian.
Custom post type là gì và khi nào nên dùng
WordPress mặc định có sẵn vài loại nội dung (post type): post (Bài viết), page (Trang), attachment (tệp đính kèm) và một số loại nội bộ. Khi bạn cần một nhóm nội dung có cấu trúc riêng, URL riêng và giao diện riêng, bạn đăng ký thêm một post type của chính mình.
Một dấu hiệu rõ ràng để biết đã đến lúc tách: nội dung có trường dữ liệu riêng (ví dụ “dự án” cần tên khách, ngành, năm thực hiện), có archive riêng, hoặc cần phân quyền/template khác hẳn bài blog. Trong plugin nội bộ web22-core, Web22 tách hai loại: service (trang dịch vụ) và case_study (dự án đã làm). Nhờ vậy trang dịch vụ có URL /dich-vu/ và portfolio có URL /work/, mỗi loại một template, không lẫn vào blog.
Ngược lại, nếu nội dung chỉ khác nhau ở chủ đề (vd “tin công nghệ” với “tin đời sống”) thì không nên tạo post type mới — dùng taxonomy tuỳ biến để phân loại bài viết sẽ gọn hơn nhiều.
Hàm register_post_type và hook init
Mọi custom post type sinh ra từ một hàm duy nhất: register_post_type( $post_type, $args ). Tham số đầu là tên định danh (tối đa 20 ký tự, chỉ chữ thường, số và gạch dưới). Tham số sau là một mảng cấu hình.
Điểm bắt buộc đầu tiên: hàm này phải chạy trong hook init. Đăng ký quá sớm (trước init) sẽ khiến nhiều cơ chế nội bộ của WordPress chưa sẵn sàng và post type hoạt động sai.
function web22_register_project_cpt() {
$args = array(
'labels' => array(
'name' => 'Dự án',
'singular_name' => 'Dự án',
'add_new_item' => 'Thêm dự án mới',
'edit_item' => 'Sửa dự án',
'all_items' => 'Tất cả dự án',
),
'public' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'du-an', 'with_front' => false ),
'menu_icon' => 'dashicons-portfolio',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'show_in_rest' => true,
);
register_post_type( 'project', $args );
}
add_action( 'init', 'web22_register_project_cpt' );Đoạn trên tạo một post type tên project. Phần labels là chuỗi hiển thị trong wp-admin — khai báo càng đủ thì menu, nút và thông báo càng đúng tiếng Việt. Bốn tham số còn lại là phần đáng để hiểu kỹ.
Nếu bạn không tự code phần này mà muốn nhờ Web22 dựng website, chúng mình đặt sẵn các loại nội dung tuỳ biến trong plugin riêng để chúng bền qua mỗi lần đổi giao diện.

Bốn nhóm tham số quan trọng nhất
public — bật/tắt hàng loạt cùng lúc
public là tham số tổng. Khi đặt true, WordPress tự suy ra bốn tham số con: hiện trong wp-admin (show_ui), cho phép truy vấn ngoài frontend (publicly_queryable), xuất hiện trong menu điều hướng (show_in_nav_menus) và được tìm thấy ở ô tìm kiếm (exclude_from_search tắt). Mặc định của public là false, nên nếu quên đặt thì post type sẽ “vô hình” với cả người dùng lẫn quản trị.
Bạn có thể đặt public => true rồi ghi đè từng tham số con khi cần một kịch bản đặc biệt — ví dụ hiện trong admin nhưng ẩn khỏi tìm kiếm.
has_archive — trang danh sách của post type
has_archive => true tạo một trang archive (trang lưu trữ — liệt kê toàn bộ mục của post type) tại đường dẫn theo slug. Với ví dụ trên, người dùng truy cập /du-an/ sẽ thấy danh sách dự án, dùng template archive-project.php theo thứ tự ưu tiên template của WordPress. Nếu không cần trang danh sách (vd loại nội dung chỉ nhúng nơi khác), để false.
supports — bật các khối tính năng của trình soạn thảo
supports quyết định màn hình sửa bài có những gì. Mặc định chỉ có title và editor. Các giá trị hay dùng:
thumbnail— bật ảnh đại diện (featured image).excerpt— bật ô tóm tắt, hữu ích cho mô tả ngắn và SEO.revisions— lưu lịch sử phiên bản để khôi phục.page-attributes— bật ô chọn bài cha và thứ tự, bắt buộc khi muốn cấu trúc phân cấp.custom-fields— bật khung trường tuỳ chỉnh gốc.
Trong web22-core, post type service khai báo supports gồm title, editor, thumbnail, excerpt, revisions, page-attributes — có page-attributes vì các trang dịch vụ xếp theo mô hình trụ–vệ tinh (trang trụ có các trang con).
rewrite — định hình URL
rewrite điều khiển đường dẫn. Truyền một mảng để tinh chỉnh:
slug— đoạn URL của post type (vddu-ancho/du-an/ten-bai/).with_front => false— bỏ tiền tố permalink chung (thường là/blog) khỏi URL, cho đường dẫn sạch hơn.
Lưu ý quan trọng: mỗi khi đổi rewrite hay tên post type, bạn phải vào Cài đặt → Đường dẫn tĩnh và bấm Lưu một lần để WordPress nạp lại quy tắc URL. Bỏ qua bước này là nguyên nhân phổ biến khiến trang custom post type báo lỗi 404 dù code đã đúng.
show_in_rest — chìa khoá cho Block Editor
Đây là tham số dễ bị quên nhất. Trình soạn thảo khối (Block Editor, tức Gutenberg) giao tiếp với máy chủ qua REST API. Nếu post type không lộ ra REST API, màn hình sửa bài sẽ rớt về trình soạn thảo cổ điển hoặc trống trơn.
Để bật Block Editor cho custom post type, cần đủ hai điều kiện: trong supports có editor, và đặt show_in_rest => true. Khi bật, WordPress tự sinh một route trong namespace wp/v2, mặc định theo tên post type.
'show_in_rest' => true,
'rest_base' => 'projects', // đổi đường dẫn REST thành /wp-json/wp/v2/projectsrest_base không bắt buộc; nó chỉ đổi tên đoạn URL của REST. Web22 đặt rest_base => 'services' cho post type service để endpoint đọc tự nhiên hơn. Bật show_in_rest còn mở đường cho việc lấy dữ liệu ra frontend tách rời (headless) sau này, nên Web22 luôn để mặc định true cho mọi post type công khai.

Vì sao nên dùng plugin thay vì functions.php
Nhiều hướng dẫn đặt register_post_type ngay trong functions.php của theme. Cách đó chạy được, nhưng có một rủi ro: khi đổi theme, post type biến mất khỏi giao diện và toàn bộ bài thuộc loại đó trở nên “mồ côi”. Vì dữ liệu nội dung không nên phụ thuộc vào lớp giao diện, Web22 luôn đặt định nghĩa post type trong một plugin riêng (như web22-core) — đổi hay sửa theme thì service và case_study vẫn còn nguyên. Bạn có thể tham khảo cách dựng khung này trong bài viết plugin WordPress từ đầu và quy ước viết hàm gọn gàng ở cách tổ chức functions.php an toàn.
Câu hỏi thường gặp
Tạo custom post type có cần plugin Custom Post Type UI không?
Không bắt buộc. Plugin UI tiện cho người không code, nhưng bằng register_post_type bạn kiểm soát hoàn toàn từng tham số và đặt được trong plugin riêng, sạch và dễ chuyển giao hơn.
Đăng ký xong mà trang báo 404 thì sao?
Gần như luôn là do chưa nạp lại quy tắc URL. Vào Cài đặt → Đường dẫn tĩnh bấm Lưu một lần. Nếu vẫn lỗi, kiểm tra has_archive và slug có trùng với trang nào sẵn có không.
Vì sao màn hình sửa custom post type không hiện Block Editor?
Kiểm tra hai điểm: supports có chứa editor và show_in_rest đã đặt true chưa. Thiếu một trong hai là Block Editor không bật.
Nếu bạn cần một website WordPress có cấu trúc nội dung tuỳ biến đặt đúng chỗ, bền qua các lần đổi giao diện, dịch vụ phát triển WordPress của Web22 và mảng code plugin tuỳ biến có thể là điểm khởi đầu hợp lý.
