本文约 4,861 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:什么都往”文章”里塞的代价
一个企业站,产品、案例、团队、FAQ、下载资料,全部用”文章”发布,靠分类区分。半年后问题来了:
- 想给产品单独做详情页模板,做不了——所有文章共用
single.php; - 想让案例的 URL 是
/case/xxx/而不是/blog/xxx/,做不了; - 后台文章列表几千条混在一起,编辑找不到东西;
- 想让”产品”不出现在 RSS 和站点搜索里,做不了。
这些问题的正确解法是自定义文章类型(Custom Post Type,简称 CPT)。它是 WordPress 从博客系统变成 CMS 的关键能力。
原理分析:三种类型的本质差异
数据库层面,post 和 page 都存在 wp_posts 表,区别只有一个 post_type 字段:
SELECT post_type, COUNT(*) FROM wp_posts
WHERE post_status = 'publish' GROUP BY post_type;
但语义和行为差别很大:
| 特性 | 文章 post | 页面 page | CPT |
|---|---|---|---|
| 是否有时间属性 | 有(发布时间、归档) | 无 | 可配置 |
| 是否参与 RSS | 是 | 否 | 可配置 |
| 是否有分类/标签 | 有 | 无 | 可配置 |
| 是否有归档页 | 无独立归档(按分类/日期) | 无 | 可配置 has_archive |
| 层级 | 无 | 有(父子页面) | 可配置 hierarchical |
| 模板文件 | single.php |
page.php |
single-{post_type}.php |
判断标准其实很简单:
- 有发布时间、会持续新增、需要按时间倒序和分类聚合 → 文章
- 静态、不常变、不属于时间序列(关于我们、联系我们、服务介绍) → 页面
- 内容结构自成一体、需要独立 URL 前缀和独立模板(产品、案例、课程、FAQ、房产、招聘) → CPT
保姆级实操
场景一:用代码注册一个”案例”CPT
推荐写在子主题的 functions.php 或自建的功能插件里,不要写在父主题(更新会被覆盖)。
add_action( 'init', 'mmf_register_case_cpt' );
function mmf_register_case_cpt() {
$labels = array(
'name' => '客户案例',
'singular_name' => '案例',
'add_new' => '添加案例',
'add_new_item' => '添加新案例',
'edit_item' => '编辑案例',
'all_items' => '全部案例',
'search_items' => '搜索案例',
'not_found' => '暂无案例',
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_rest' => true, // 关键:启用 Gutenberg 和 REST API
'menu_position' => 5,
'menu_icon' => 'dashicons-portfolio',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ),
'has_archive' => true, // 启用 /case/ 归档页
'rewrite' => array( 'slug' => 'case', 'with_front' => false ),
'hierarchical' => false,
'taxonomies' => array( 'case_cat' ),
'capability_type' => 'post',
'map_meta_cap' => true,
);
register_post_type( 'case', $args );
}
配套注册它的分类法:
add_action( 'init', 'mmf_register_case_tax' );
function mmf_register_case_tax() {
register_taxonomy( 'case_cat', 'case', array(
'labels' => array( 'name' => '案例分类', 'singular_name' => '案例分类' ),
'public' => true,
'hierarchical' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'case-cat' ),
) );
}
必须注意:注册完 CPT 后要去 设置 → 固定链接 → 点一次”保存更改”,否则 /case/xxx/ 会 404。这是 CPT 最常见的”注册了打不开”问题。命令行:
wp rewrite flush
场景二:不用代码,用插件
- Custom Post Type UI(CPT UI):最常用,可视化注册 CPT 和分类法,免费版够用;
- Pods:除了 CPT 还能加自定义字段,功能更强;
- ACF + CPT UI 组合:ACF 做字段,CPT UI 做类型,是很多建站公司的标准配置。
CPT UI 注册后同样要刷新固定链接。
场景三:做 CPT 的模板
WordPress 的模板层级(template hierarchy)对 CPT 同样生效:
single-case.php → 单个案例页
single.php → 兜底
archive-case.php → 案例归档页
taxonomy-case_cat.php → 案例分类页
最省事的做法:复制 single.php 改名 single-case.php,再按需要改。
场景四:让 CPT 出现在首页主循环
默认首页 WP_Query 只包含 post。要包含 CPT:
add_action( 'pre_get_posts', function( $query ) {
if ( ! is_admin() && $query->is_main_query() && is_home() ) {
$query->set( 'post_type', array( 'post', 'case' ) );
}
});
场景五:让 CPT 出现在站点搜索和 RSS
// 搜索包含 CPT
add_action( 'pre_get_posts', function( $query ) {
if ( ! is_admin() && $query->is_main_query() && $query->is_search() ) {
$query->set( 'post_type', array( 'post', 'page', 'case' ) );
}
});
// RSS 包含 CPT
add_filter( 'request', function( $qv ) {
if ( isset( $qv['feed'] ) && ! isset( $qv['post_type'] ) ) {
$qv['post_type'] = array( 'post', 'case' );
}
return $qv;
});
场景六:用 WP-CLI 管理 CPT 内容
wp post list --post_type=case --fields=ID,post_title,post_date
wp post create --post_type=case --post_title='某客户案例' --post_status=publish
wp term list case_cat
常见踩坑与失败案例
- 案例一:注册了 CPT,前台
/case/xxx/404,反复检查代码没问题。99% 是没刷新固定链接。注册代码改动后也要重新 flush。 show_in_rest没设 true:后台 CPT 编辑页只有经典编辑器,没有 Gutenberg,REST API 也拿不到数据,部分插件(如 RankMath 的编辑器侧栏)无法工作。with_front忘了设为 false:如果固定链接结构里带/blog/,CPT 的 URL 会变成/blog/case/xxx/,通常不是你想要的。- CPT 别名和页面别名冲突:有个页面别名叫
case,同时 CPT 的 slug 也是case,归档页打不开。改其中一个。 - 案例二:用了
capability_type => 'case'但没给角色分配能力,编辑完全看不到 CPT 菜单。要么改回'post',要么用插件(Members)给角色加edit_cases等能力。 - CPT 归档页 SEO 未配置:
has_archive开启后/case/是个可收录页面,要去 SEO 插件里给它写标题和描述,否则输出的是空的归档标题。 - 停用注册插件后 CPT 内容”消失”:CPT 的定义代码没了,后台菜单消失,但数据还在数据库里。重新注册同样的
post_type就能恢复。删插件前先想清楚,或者把注册代码写进子主题而非插件。
总结
CPT 是 WordPress 从博客走向 CMS 的分水岭。判断什么时候用它的标准很朴素:当某一类内容需要”独立的 URL 前缀 + 独立的模板 + 独立的后台菜单”时,就该用 CPT。注册时三个关键参数不能漏:show_in_rest => true(编辑器与 REST)、has_archive => true(归档页)、rewrite => array('with_front' => false)(干净的 URL)。注册完一定要刷新固定链接。如果不想写代码,CPT UI + ACF 是最稳的组合,但请把注册代码导出到子主题,避免插件停用后内容”消失”。
