Taxonomy là cách WordPress phân loại data — category và tag là 2 taxonomy built-in. Custom taxonomy cho phép tạo schema phân loại riêng cho CPT — Câu chuyện khách có Industry và Stack riêng biệt.
Bài này hướng dẫn 20 tham số của register_taxonomy(), quyết định hierarchical hay flat, attach taxonomy vào nhiều CPT, term meta API, REST endpoint custom field, script migration term từ category cũ sang taxonomy mới.
register_taxonomy là gì và khác biệt với category cùng tag built-in
register_taxonomy() là WordPress function declare 1 taxonomy mới với schema phân loại riêng, attach vào 1 hoặc nhiều post type. Khác category và tag built-in ở 3 điểm: URL pattern riêng, capability riêng, REST endpoint riêng.
Quyết định custom taxonomy hay reuse category dựa trên 2 câu hỏi: taxonomy có gắn vào CPT cụ thể không, có cần URL khác /category/ không.
3 trường hợp cần custom taxonomy
Category đủ dùng cho phần lớn blog. Ba dấu hiệu sau khẳng định custom taxonomy là lựa chọn đúng thay vì reuse category.
- Phân loại CPT theo schema riêng: Câu chuyện khách cần tag theo Industry (F&B, Tech, BĐS) và Stack (React, Vue, WordPress) — 2 schema khác hẳn, không thể nhồi vào 1 category.
- URL pattern riêng mong muốn:
/industry/{slug}/và/stack/{slug}/rõ ràng hơn/category/...— đặc biệt khi site có nhiều schema phân loại song song. - Permissions riêng cho role: Editor manage Industry term nhưng chỉ Admin manage Stack term — cần capability custom mà category built-in không có.
Hierarchical kiểu category với non-hierarchical kiểu tag — quyết định kiến trúc
Đây là quyết định kiến trúc đầu tiên khi thiết kế taxonomy. Sai sẽ phải migration term sau — tốn nhiều giờ và rủi ro mất data.
Hierarchical khi nào chọn
- Term có quan hệ parent và child rõ: Industry phân nhánh F&B rồi xuống Restaurant rồi xuống Cafe — cấu trúc nested 3-4 cấp.
- UI muốn checkbox tree: giống cách category hiện trong Post editor — editor click chọn từ list có sẵn, không gõ tay.
- Term limited list: Industry chỉ có 5-10 ngành, editor thêm có chủ đích — không phải free-form mỗi post.
Non-hierarchical khi nào chọn
- Term flat không nest: Stack chỉ có React, Vue, Angular — không có quan hệ parent child giữa chúng.
- UI text input autocomplete: giống tag trong Post editor — editor gõ và autocomplete gợi ý từ term đã có.
- Term free-form mở rộng tự do: editor thêm term mới mỗi post nếu cần — không cần admin approve trước.
register_taxonomy với 20 tham số đầy đủ
add_action('init', function () {
register_taxonomy('industry', ['case_study'], [
'labels' => [
'name' => 'Industries',
'singular_name' => 'Industry',
'menu_name' => 'Industry',
'all_items' => 'All Industries',
'edit_item' => 'Edit Industry',
'view_item' => 'View Industry',
'add_new_item' => 'Add New Industry',
'search_items' => 'Search Industries',
'parent_item' => 'Parent Industry',
'parent_item_colon' => 'Parent Industry:',
],
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'show_in_rest' => true, // BẮT BUỘC 2026
'rest_base' => 'industries',
'show_tagcloud' => false,
'show_admin_column' => true, // Cột trong list CPT admin
'hierarchical' => true, // category-like
'rewrite' => ['slug' => 'industry', 'with_front' => false, 'hierarchical' => true],
'query_var' => true,
'capabilities' => [
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
],
]);
});
Attach 1 taxonomy vào multiple CPT
Tham số thứ 2 của register_taxonomy là array các CPT slug. Một taxonomy có thể attach nhiều CPT — tận dụng khi taxonomy là cross-cutting concern áp dụng nhiều entity.
Pattern share taxonomy giữa CPT
register_taxonomy('skill', ['case_study', 'service'], [
// 1 taxonomy "Skill" dùng chung cho cả Câu chuyện khách lẫn Service
'show_in_rest' => true,
'hierarchical' => false,
'rewrite' => ['slug' => 'skill', 'with_front' => false],
]);
3 lưu ý khi share taxonomy
Share taxonomy giúp giữ DRY nhưng có rủi ro cần biết trước. Ba điểm sau quan trọng khi thiết kế share schema.
- Term share giữa CPT: edit term ở Câu chuyện khách cũng đổi ở Service — không tách context được nếu cần label khác nhau.
- Query JOIN phức tạp hơn: filter “post có Skill X” sẽ trả về cả Câu chuyện khách và Service trộn lẫn — phải thêm post_type filter nếu chỉ muốn 1 loại.
- Permissions tới term áp dụng tất cả: Editor có quyền manage Skill term ảnh hưởng cả Câu chuyện khách lẫn Service — không tách role per CPT được.
Term meta API — get, update, add, delete term meta
WordPress 4.4 trở lên hỗ trợ meta cho term tương tự post meta. Cho phép gắn data extra vào term — icon URL, color, custom field business logic.
4 function CRUD cho term meta
// Get meta value
$icon_url = get_term_meta($term_id, 'industry_icon', true);
// Update (replace value cũ nếu có)
update_term_meta($term_id, 'industry_icon', 'https://web22.dev/icons/fnb.svg');
// Add (cho phép multiple value cùng key)
add_term_meta($term_id, 'related_skill', 'react');
add_term_meta($term_id, 'related_skill', 'vue');
// Delete
delete_term_meta($term_id, 'industry_icon');
UI cho term meta — pattern add và edit form hook
// Hiện field khi add new term
add_action('industry_add_form_fields', function () {
echo '<div class="form-field">
<label for="industry_icon">Icon URL</label>
<input type="url" name="industry_icon" id="industry_icon">
</div>';
});
// Hiện field khi edit term
add_action('industry_edit_form_fields', function ($term) {
$icon = get_term_meta($term->term_id, 'industry_icon', true);
echo '<tr class="form-field">
<th><label>Icon URL</label></th>
<td><input type="url" name="industry_icon" value="' . esc_attr($icon) . '"></td>
</tr>';
});
// Save khi create hoặc update term
add_action('created_industry', 'w22_save_industry_meta');
add_action('edited_industry', 'w22_save_industry_meta');
function w22_save_industry_meta($term_id) {
if (isset($_POST['industry_icon'])) {
update_term_meta($term_id, 'industry_icon', esc_url_raw($_POST['industry_icon']));
}
}
Custom REST endpoint cho taxonomy với show_in_rest
Set show_in_rest bằng true là WordPress auto-create endpoint /wp-json/wp/v2/{rest_base}. Bật REST mở khả năng headless WordPress, ứng dụng di động fetch term, frontend Next.js render list term.
Cấu hình REST endpoint đầy đủ
register_taxonomy('industry', ['case_study'], [
'show_in_rest' => true,
'rest_base' => 'industries', // /wp-json/wp/v2/industries/
'rest_namespace' => 'wp/v2',
'rest_controller_class' => 'WP_REST_Terms_Controller',
]);
Add custom field vào REST response
add_action('rest_api_init', function () {
register_rest_field('industry', 'icon', [
'get_callback' => function ($term) {
return get_term_meta($term['id'], 'industry_icon', true);
},
'schema' => ['type' => 'string', 'description' => 'Industry icon URL'],
]);
});
// Response bao gồm: { "id": 5, "name": "F&B", "icon": "https://..." }
3 lưu ý khi expose term meta qua REST
REST endpoint public bất kỳ ai cũng fetch được. Ba lưu ý sau tránh leak data nội bộ hoặc tạo bottleneck performance.
- Không expose meta nhạy cảm: internal note, draft data, API key không bao giờ register_rest_field — chỉ expose data đã sẵn sàng public.
- Cache response cho endpoint nặng: term có 100 meta key trả response chậm — cache 5-10 phút qua transient hoặc object cache để giảm DB query.
- Schema rõ ràng cho client: trường schema phải có type và description — Swagger và OpenAPI doc generate từ schema này, mơ hồ là client misuse.
Migration term từ taxonomy cũ sang taxonomy mới
Use case phổ biến: site cũ dùng category phân loại Post, refactor thành CPT case_study với custom taxonomy industry. Cần script migrate term và post-term relationship.
Script migration chạy 1 lần qua WP-CLI
// scripts/_migrate-category-to-industry.php
require_once 'wp-load.php';
$mapping = [
'restaurant' => 'F&B',
'saas' => 'Tech',
'real-estate' => 'BĐS',
];
foreach ($mapping as $old_slug => $new_name) {
$old_cat = get_term_by('slug', $old_slug, 'category');
if (!$old_cat) continue;
// Tạo term mới trong taxonomy industry
$new_term = wp_insert_term($new_name, 'industry');
if (is_wp_error($new_term)) continue;
// Lấy mọi post trong category cũ
$posts = get_posts([
'category' => $old_cat->term_id,
'posts_per_page' => -1,
'post_type' => 'case_study',
]);
foreach ($posts as $post) {
wp_set_object_terms($post->ID, [$new_term['term_id']], 'industry', true);
}
echo "Migrated " . count($posts) . " posts từ {$old_cat->name} sang {$new_name}n";
}
Lưu ý quan trọng về tham số append của wp_set_object_terms
Tham số thứ 4 của wp_set_object_terms là append. true là thêm term mới giữ term cũ, false là replace toàn bộ term cũ. Cẩn thận với false trong bulk update — có thể xoá nhầm term không liên quan.
Flush rewrite và cache invalidation sau register
Tương tự CPT, register taxonomy mới cần flush rewrite để URL /industry/{slug}/ hoạt động. Pattern và lý do giống mục flush của CPT trong bài register_post_type WordPress — 25 tham số + show_in_rest 2026.
Pattern flush an toàn chỉ trong activation
register_activation_hook(__FILE__, function () {
w22_register_industry_taxonomy();
flush_rewrite_rules();
});
add_action('init', 'w22_register_industry_taxonomy');
Term cache invalidation
WordPress cache term (kết quả get_terms) trong object cache để tránh DB query lặp. Khi update term meta, cache cũ vẫn dùng giá trị stale — force refresh khi cần.
clean_term_cache($term_id, 'industry');
delete_term_meta($term_id, '_w22_cache_marker');
3 lưu ý quan trọng về caching term
Object cache layer của WordPress hoạt động ngầm — dev thường không nhận ra cho tới khi gặp bug “đổi data mà UI vẫn hiện cũ”. Ba lưu ý sau giúp debug nhanh.
- Cache phạm vi per request mặc định: không có persistent cache (Redis hoặc Memcached) thì cache reset mỗi request — bug chỉ xuất hiện rõ khi site dùng object cache plugin.
- clean_term_cache xoá cả parent chain: term hierarchical clean cache 1 term sẽ xoá cả ancestor — đảm bảo breadcrumb refresh đúng sau update.
- Transient riêng cho query nặng: get_terms với args phức tạp nên cache vào transient riêng — không dựa hoàn toàn vào WP_Object_Cache vì có thể bị flush bất ngờ.
Best practice khi thiết kế taxonomy production
Sau khi nắm cú pháp, đây là tổng hợp best practice từ kinh nghiệm maintain taxonomy production cho site có 50.000 term trở lên. Áp dụng giảm rủi ro phải refactor schema sau khi đã có dữ liệu.
5 nguyên tắc thiết kế taxonomy
- Quyết định hierarchical hay flat trước khi import term: đổi sau khi đã có 1.000 term là tốn nhiều giờ migration — đầu tư 30 phút phân tích schema trước khi register.
- Đặt slug ngắn và stable: slug đi vào URL — đổi slug sau là phải setup 301 redirect cho mọi URL cũ, tốn công và rủi ro mất xếp hạng SEO.
- Limit depth hierarchical 3-4 cấp: sâu hơn dẫn tới breadcrumb dài, query JOIN nhiều bảng — performance giảm khi term tree phình to.
- Document term meta schema: ghi rõ key name, type, mục đích vào README plugin — dev khác hoặc chính bạn 6 tháng sau sẽ cảm ơn.
- Test migration script trên staging trước: wp_set_object_terms với append=false có thể xoá nhầm term — test trên copy DB production rồi mới chạy thật.
Câu hỏi thường gặp
Có giới hạn số term per post không?
Không có hard limit native. Practical limit khoảng 50 term mỗi post — quá nhiều dẫn tới UX rối khi editor scroll danh sách và query slow khi filter.
Web22 câu chuyện khách CPT thường có 3-5 term Industry và 5-10 term Skill — đủ phân loại mà không tạo bottleneck. Vượt 20 term mỗi post là dấu hiệu cần refactor schema.
Hierarchical taxonomy có giới hạn depth không?
Không có giới hạn native. Practical recommended 3-4 cấp — sâu hơn dẫn tới breadcrumb dài lê thê, URL phức tạp khó nhớ, query JOIN slow vì nhiều level.
Ví dụ pattern an toàn: Industry (level 1) — Sub-Industry (level 2) — Niche (level 3). Sâu hơn nữa nên cân nhắc dùng taxonomy thứ 2 thay vì nest tiếp.
register_taxonomy nên hook vào đâu?
Hook init với priority 0-10. Chạy sau init core nhưng trước query parsing — đảm bảo taxonomy có sẵn khi WordPress resolve URL request.
Tham khảo note chính thức tại developer.wordpress.org/reference/functions/register_taxonomy — đặc biệt phần thứ tự register so với register_post_type.
Term meta, post meta hay option — chọn cái nào cho data?
Tuỳ phạm vi của data. Term meta cho data về term (icon, color, description mở rộng).
Post meta cho data về post (price, duration, sản phẩm bàn giao). Option cho data global site-wide (API key, default email, feature toggle).
Đừng nhồi tất cả vào option — option load mỗi request và cache toàn bộ trong autoloaded option. Vượt 1MB autoloaded option là dấu hiệu cần migrate sang post meta hoặc term meta.
get_term_meta so với get_field của ACF khác gì?
get_term_meta là API native của WordPress, không dependency plugin. get_field là API của ACF, hỗ trợ field type phức tạp như image, repeater, flexible content.
Native đủ cho data đơn giản kiểu string hoặc URL. ACF cho UX builder khi field type phức tạp — trade-off là dependency plugin và Pro license cho field nâng cao.
Term có thể có URL custom không?
Có qua filter term_link. Pattern: override default /industry/{slug}/ sang URL custom theo logic riêng.
Code mẫu thay từ /industry/ sang /work/industry/:
add_filter('term_link', function ($url, $term, $tax) {
if ($tax === 'industry') {
return home_url('/work/industry/' . $term->slug . '/');
}
return $url;
}, 10, 3);
Tài nguyên và bước tiếp theo
Taxonomy là partner của CPT — phân loại tốt nâng giá trị CPT lên rõ rệt. Các topic chuyên sâu liên quan giúp hoàn thiện stack WordPress development production-grade.
- register_post_type WordPress — 25 tham số + show_in_rest 2026 — CPT là entity chính, taxonomy là cách phân loại — học song song.
- Tạo plugin WordPress từ đầu — 7 bước build chuẩn 2026 — đóng taxonomy vào plugin để tồn tại qua mọi theme.
- Hook, action và filter WordPress — concept fundamental 2026 — hook init là điểm vào của register_taxonomy.
- Plugin boilerplate WordPress 2026 — WPPB vs Underscores vs custom — boilerplate có sẵn pattern register taxonomy theo OOP.
- Dịch vụ thiết kế website WordPress chuyên nghiệp — tham khảo gói thi công taxonomy custom với term meta trọn gói.
Cần Web22 setup taxonomy chuẩn với term meta, REST endpoint và migration script từ category cũ? Dịch vụ dev theme/plugin WordPress custom tại Web22 — bàn giao kèm script migration test trên staging, REST endpoint custom field và document schema đầy đủ.


