[WordPress] 自訂內容類型(CPT)與分類法開發教學

本篇文章更新時間:2026/03/03
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持


本系列文參考自 WordPress.org 官方外掛開發文件 - Custom Post TypesTaxonomies 的繁體中文版本,並加入作者實務開發經驗補充。

WordPress 自訂內容類型(Custom Post Type)是擴展 WordPress 內容架構的核心機制。WordPress 預設提供了文章(Post)、頁面(Page)、附件(Attachment)等內容類型,但在實務開發中,我們經常需要建立全新的內容結構來滿足專案需求——例如「產品」、「作品集」、「活動」等。搭配自訂分類法(Custom Taxonomy),開發者能夠為這些自訂內容建立專屬的分類與標籤體系,打造出完整且直覺的內容管理架構。本文將從自訂內容類型與分類法的基礎概念開始,逐步介紹 register_post_type()register_taxonomy() 的完整用法,並分享永久連結處理與實務開發建議。

什麼是自訂內容類型(Custom Post Types)

在 WordPress 中,所有內容都儲存在 wp_posts 資料表中,透過 post_type 欄位區分不同的內容類型。WordPress 核心內建了以下幾種內容類型:

  • post:文章(部落格文章)
  • page:頁面(靜態頁面)
  • attachment:附件(媒體檔案)
  • revision:修訂版本
  • nav_menu_item:選單項目
  • wp_block:可重複使用的區塊
  • wp_template:區塊佈景主題範本
  • wp_template_part:範本組件

自訂內容類型(Custom Post Type,簡稱 CPT)讓開發者可以註冊全新的內容類型,每種 CPT 在後台管理介面中會有自己的選單、編輯畫面和列表頁面。由於所有 CPT 資料都儲存在同一張 wp_posts 資料表中,WordPress 內建的查詢引擎(WP_Query)和 REST API 都可以直接操作這些自訂內容,無需額外建立資料庫結構。

常見的 CPT 應用場景包括:

  • 電商網站的「產品」(WooCommerce 的 product 類型)
  • 作品展示的「作品集」(Portfolio)
  • 活動管理的「活動」(Event)
  • 房產網站的「物件」(Property)
  • 知識庫的「文件」(Documentation)

註冊自訂內容類型 - register_post_type()

使用 register_post_type() 函式來註冊自訂內容類型,此函式必須掛載在 init Hook 上執行。函式接受兩個參數:內容類型名稱(最多 20 個字元,僅限小寫英文與底線)和一個設定陣列。

/**
 * 註冊「書籍」自訂內容類型的完整範例
 */
function myplugin_register_book_post_type() {

    $labels = array(
        'name'                  => '書籍',
        'singular_name'         => '書籍',
        'menu_name'             => '書籍管理',
        'name_admin_bar'        => '書籍',
        'add_new'               => '新增書籍',
        'add_new_item'          => '新增書籍',
        'new_item'              => '新書籍',
        'edit_item'             => '編輯書籍',
        'view_item'             => '檢視書籍',
        'all_items'             => '所有書籍',
        'search_items'          => '搜尋書籍',
        'parent_item_colon'     => '上層書籍:',
        'not_found'             => '找不到書籍',
        'not_found_in_trash'    => '回收桶中找不到書籍',
        'archives'              => '書籍彙整',
        'insert_into_item'      => '插入書籍',
        'uploaded_to_this_item' => '已上傳到這本書籍',
        'filter_items_list'     => '篩選書籍列表',
        'items_list_navigation' => '書籍列表導覽',
        'items_list'            => '書籍列表',
    );

    $args = array(
        // 標籤設定
        'labels'             => $labels,
        'description'        => '書籍內容類型',

        // 公開性設定
        'public'             => true,    // 前台與後台皆可存取
        'publicly_queryable' => true,    // 前台可查詢
        'show_ui'            => true,    // 後台顯示管理介面
        'show_in_menu'       => true,    // 在後台選單中顯示
        'show_in_nav_menus'  => true,    // 可加入導覽選單
        'show_in_admin_bar'  => true,    // 顯示在管理列
        'exclude_from_search'=> false,   // 不排除在搜尋結果之外

        // REST API 與 Gutenberg 支援(重要!)
        'show_in_rest'       => true,    // 啟用 REST API 與區塊編輯器支援

        // 功能設定
        'supports'           => array(
            'title',           // 標題
            'editor',          // 內容編輯器
            'author',          // 作者
            'thumbnail',       // 特色圖片
            'excerpt',         // 摘要
            'comments',        // 留言
            'revisions',       // 修訂版本
            'custom-fields',   // 自訂欄位
        ),

        // 彙整與永久連結
        'has_archive'        => true,    // 啟用彙整頁面(/books/)
        'rewrite'            => array(
            'slug'       => 'books',     // 自訂網址 slug
            'with_front' => false,       // 不加上全域前綴
        ),

        // 階層與選單
        'hierarchical'       => false,   // 非階層式(類似文章)
        'menu_position'      => 5,       // 選單位置(5 = 文章下方)
        'menu_icon'          => 'dashicons-book-alt', // 選單圖示

        // 權限
        'capability_type'    => 'post',  // 使用與文章相同的權限
        'map_meta_cap'       => true,    // 對應 meta capabilities
    );

    register_post_type( 'myplugin_book', $args );
}
add_action( 'init', 'myplugin_register_book_post_type' );

重要參數詳解

labels 陣列:定義後台管理介面中所有顯示文字。雖然只有 namesingular_name 是必填的,但建議完整填寫所有標籤,讓管理介面更加在地化。

supports 陣列:控制內容類型支援哪些編輯功能。常用的值包括:

  • title - 標題欄位
  • editor - 內容編輯器(Gutenberg 或傳統編輯器)
  • author - 作者選擇器
  • thumbnail - 特色圖片
  • excerpt - 摘要
  • comments - 留言功能
  • revisions - 修訂版本追蹤
  • page-attributes - 頁面屬性(排序與上層選擇,需搭配 hierarchical => true

show_in_rest:這是 WordPress 5.0 以後最重要的參數之一。設為 true 時,你的 CPT 將同時支援 Gutenberg 區塊編輯器和 REST API。如果設為 false 或省略,編輯畫面會退回傳統編輯器,且無法透過 REST API 存取此內容類型。

rewrite 陣列:控制永久連結結構。slug 定義了 URL 中的路徑前綴(例如 /books/my-first-book/),with_front 決定是否在 slug 前加上 WordPress 設定中的永久連結前綴。

內容類型命名規範

註冊 CPT 時,命名是一個需要特別注意的細節:

  • 名稱最多 20 個字元
  • 僅使用小寫英文字母、數字和底線
  • 建議加上外掛前綴以避免衝突(例如 myplugin_book 而非 book
  • 不可使用 WordPress 保留名稱,例如 postpageattachmentrevisionactionauthorordertheme

什麼是分類法(Taxonomies)

分類法(Taxonomy)是 WordPress 用來對內容進行分類與分組的機制。「Taxonomy」這個詞源自生物學中的分類學概念——正如生物學家將生物分為界、門、綱、目、科、屬、種,WordPress 的分類法讓你能夠以階層式或扁平式的方式來組織內容。

WordPress 內建了幾種分類法:

  • category(分類):階層式分類法,可以有父子關係
  • post_tag(標籤):非階層式分類法,扁平結構
  • nav_menu:導覽選單分類
  • link_category:連結分類(WordPress 3.5 後已棄用)
  • post_format:文章格式

分類法中的每個項目稱為「分類項目」(Term)。例如「分類」這個分類法可以包含「技術文章」、「生活雜記」等 Term;「標籤」分類法可以包含「WordPress」、「PHP」等 Term。Term 的資料儲存在 wp_termswp_term_taxonomy 資料表中。

階層式 vs 非階層式

自訂分類法最核心的設計決策就是選擇「階層式」或「非階層式」:

特性 階層式(如分類 Category) 非階層式(如標籤 Tag)
父子關係 支援,可建立多層樹狀結構 不支援,所有項目為扁平結構
後台介面 勾選框(Checkbox) 自由輸入標籤框
適用情境 明確的分類體系,如:產品類別 靈活的標記系統,如:技能標籤
hierarchical 參數 true false

註冊自訂分類法 - register_taxonomy()

使用 register_taxonomy() 函式來註冊自訂分類法,同樣需要掛載在 init Hook 上。函式接受三個參數:分類法名稱、要關聯的內容類型(可以是字串或陣列),以及設定陣列。

/**
 * 註冊「書籍類型」階層式分類法(類似分類)
 */
function myplugin_register_genre_taxonomy() {

    $labels = array(
        'name'                       => '書籍類型',
        'singular_name'              => '書籍類型',
        'menu_name'                  => '書籍類型',
        'all_items'                  => '所有書籍類型',
        'parent_item'                => '上層書籍類型',
        'parent_item_colon'          => '上層書籍類型:',
        'new_item_name'              => '新書籍類型名稱',
        'add_new_item'               => '新增書籍類型',
        'edit_item'                  => '編輯書籍類型',
        'update_item'                => '更新書籍類型',
        'view_item'                  => '檢視書籍類型',
        'separate_items_with_commas' => '以逗號分隔書籍類型',
        'add_or_remove_items'        => '新增或移除書籍類型',
        'choose_from_most_used'      => '從最常使用的選取',
        'popular_items'              => '熱門書籍類型',
        'search_items'               => '搜尋書籍類型',
        'not_found'                  => '找不到書籍類型',
        'no_terms'                   => '沒有書籍類型',
        'items_list'                 => '書籍類型列表',
        'items_list_navigation'      => '書籍類型列表導覽',
    );

    $args = array(
        // 標籤設定
        'labels'            => $labels,
        'description'       => '書籍的類型分類',

        // 階層式(true = 類似分類,false = 類似標籤)
        'hierarchical'      => true,

        // 公開性
        'public'            => true,
        'publicly_queryable'=> true,
        'show_ui'           => true,
        'show_in_menu'      => true,
        'show_in_nav_menus' => true,
        'show_tagcloud'     => true,
        'show_admin_column' => true,  // 在文章列表中顯示為欄位

        // REST API 與 Gutenberg 支援
        'show_in_rest'      => true,

        // 永久連結
        'rewrite'           => array(
            'slug'         => 'genre',        // 自訂 URL slug
            'with_front'   => false,
            'hierarchical' => true,           // 支援階層式 URL
        ),

        // 查詢參數
        'query_var'         => true,
    );

    register_taxonomy( 'myplugin_genre', array( 'myplugin_book' ), $args );
}
add_action( 'init', 'myplugin_register_genre_taxonomy' );

以下是另一個非階層式(類似標籤)分類法的範例:

/**
 * 註冊「書籍標籤」非階層式分類法(類似標籤)
 */
function myplugin_register_book_tag_taxonomy() {

    $labels = array(
        'name'                       => '書籍標籤',
        'singular_name'              => '書籍標籤',
        'menu_name'                  => '書籍標籤',
        'all_items'                  => '所有書籍標籤',
        'new_item_name'              => '新書籍標籤名稱',
        'add_new_item'               => '新增書籍標籤',
        'edit_item'                  => '編輯書籍標籤',
        'update_item'                => '更新書籍標籤',
        'search_items'               => '搜尋書籍標籤',
        'not_found'                  => '找不到書籍標籤',
        'separate_items_with_commas' => '以逗號分隔書籍標籤',
        'add_or_remove_items'        => '新增或移除書籍標籤',
        'choose_from_most_used'      => '從最常使用的選取',
        'popular_items'              => '熱門書籍標籤',
    );

    $args = array(
        'labels'            => $labels,
        'hierarchical'      => false,  // 非階層式 = 類似標籤
        'public'            => true,
        'show_ui'           => true,
        'show_admin_column' => true,
        'show_in_rest'      => true,
        'rewrite'           => array(
            'slug' => 'book-tag',
        ),
        'query_var'         => true,
    );

    register_taxonomy( 'myplugin_book_tag', array( 'myplugin_book' ), $args );
}
add_action( 'init', 'myplugin_register_book_tag_taxonomy' );

將分類法關聯到現有內容類型

除了在 register_taxonomy() 的第二個參數指定內容類型外,你也可以使用 register_taxonomy_for_object_type() 將分類法關聯到已存在的內容類型:

/**
 * 將自訂分類法關聯到文章(post)內容類型
 */
function myplugin_connect_taxonomy_to_posts() {
    register_taxonomy_for_object_type( 'myplugin_genre', 'post' );
}
add_action( 'init', 'myplugin_connect_taxonomy_to_posts' );

這在以下情境特別有用:當你想讓自訂分類法同時套用到多種內容類型,或是想把分類法掛載到其他外掛註冊的 CPT 上時。

註冊順序很重要

如果你的分類法要關聯到自訂內容類型,請確保分類法在內容類型之前註冊,或是在 register_post_type()taxonomies 參數中明確指定關聯:

// 方法一:先註冊分類法,再註冊 CPT
add_action( 'init', 'myplugin_register_genre_taxonomy' );
add_action( 'init', 'myplugin_register_book_post_type' );

// 方法二:在 CPT args 中指定 taxonomies
$args = array(
    // ...其他設定
    'taxonomies' => array( 'myplugin_genre', 'myplugin_book_tag' ),
);
register_post_type( 'myplugin_book', $args );

永久連結處理 - flush_rewrite_rules()

註冊 CPT 和自訂分類法後,WordPress 需要重新建立永久連結規則(Rewrite Rules)才能正確解析新的 URL 結構。但切記:絕對不要在 init Hook 中呼叫 flush_rewrite_rules(),因為這個操作非常耗費資源,它會寫入資料庫並影響每次頁面載入的效能。

正確的做法是在外掛啟用時(register_activation_hook)執行一次永久連結重建:

/**
 * 外掛啟用時刷新永久連結規則
 */
function myplugin_activate() {
    // 先註冊 CPT 和分類法,因為啟用時 init hook 可能尚未觸發
    myplugin_register_book_post_type();
    myplugin_register_genre_taxonomy();
    myplugin_register_book_tag_taxonomy();

    // 刷新永久連結規則
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'myplugin_activate' );

/**
 * 外掛停用時也要刷新,移除不再需要的規則
 */
function myplugin_deactivate() {
    // 不需要先註冊 CPT/分類法,因為停用後它們本來就不該存在
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

關於 register_activation_hook()register_deactivation_hook() 的詳細說明,請參考「WordPress 外掛基礎知識篇」中的外掛啟用與停用章節。

開發期間的小技巧:如果你在開發過程中修改了 rewrite 設定但前台顯示 404,可以到後台「設定 → 永久連結」頁面,不需要修改任何設定,直接點擊「儲存設定」就能觸發永久連結重建。這比在程式碼中臨時加入 flush_rewrite_rules() 安全得多。

實務建議

作者實務經驗分享:

1. 何時使用 CPT,何時使用自訂資料表:如果你的資料具有「內容」的特質(有標題、有內文、需要被搜尋和瀏覽),優先使用 CPT,因為你可以直接享受 WordPress 的查詢引擎、快取機制、REST API、SEO 外掛支援等完整生態系。只有在資料結構高度特殊化(例如大量數值型日誌記錄、需要複雜的 JOIN 查詢)或資料量極大(百萬筆以上且需要高效能查詢)時,才考慮自訂資料表。我自己的經驗是:八成以上的需求都可以用 CPT + Post Meta 解決。

2. 務必啟用 show_in_rest:從 WordPress 5.0 開始,show_in_rest 不僅控制 REST API 存取,更直接決定了是否啟用 Gutenberg 區塊編輯器。如果你省略此參數或設為 false,你的 CPT 編輯畫面會退回傳統編輯器。除非你有明確的理由(例如資料安全考量)不公開 REST 端點,否則建議一律設為 true。如果需要細緻的 REST API 權限控制,可以透過 rest_controller_class 參數自訂控制器。

3. 分類法的命名考量:分類法名稱最多 32 個字元,同樣建議加上外掛前綴。另外要注意的是,WordPress 內部會將分類法名稱用於資料庫查詢中的 taxonomy 欄位,所以命名時要避免使用 SQL 保留字和 WordPress 已用的名稱(如 categorypost_tagpost_format)。

4. 善用 show_admin_column:在分類法設定中將 show_admin_column 設為 true,可以在後台文章列表中直接顯示該分類欄位,方便管理者快速檢視與篩選。這是一個小設定,但對使用體驗有很大的提升。

5. 搭配 Hooks 建立完整流程:CPT 與分類法的註冊只是第一步。在實務開發中,你通常還需要搭配 add_meta_boxes 建立自訂欄位介面、使用 save_post_{$post_type} 儲存資料、透過 manage_{$post_type}_posts_columns 自訂後台列表欄位等。如果你對 WordPress Hook 機制還不熟悉,建議先閱讀「WordPress Hooks 完整教學」建立基礎觀念。

6. 永久連結規則只在啟用時刷新:這是我在 code review 中最常看到的錯誤之一——開發者在 init hook 中呼叫 flush_rewrite_rules()。這會導致每次頁面載入都寫入資料庫,嚴重影響效能。正確做法是只在外掛啟用和停用時刷新,開發階段則透過後台的永久連結設定頁面手動觸發。


本文是「WordPress 外掛開發完整指南」系列的第 9 篇。

上一篇:[WordPress] Metadata 與 Custom Meta Box 開發教學

下一篇:[WordPress] 使用者管理與角色權限開發教學


Share:

作者: Chun

WordPress 社群貢獻者、開源社群推廣者。專注於 WordPress 外掛開發、網站效能最佳化、伺服器管理,以及 iDempiere 開源 ERP 導入與客製開發。曾參與 WordCamp Taipei 等社群活動,GitHub Arctic Code Vault Contributor。提供資訊顧問、WordPress 開發教學、主機最佳化與企業 ERP 整合服務。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Apply Filters
Mastodon