自定义文章类型(Custom Post Type,CPT)是把”产品、课程、案例、活动”这类结构化内容从普通文章里拆出来的正确方式:它有自己的后台菜单、自己的字段、自己的归档页和模板。最短路径是在 init 钩子上调用 register_post_type(),配上 public => true、has_archive、show_in_rest => true 三个开关,再注册一个自定义分类法做 grouping,最后处理模板层级与重写规则刷新。
什么时候该用 CPT,什么时候不该
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 产品、课程、案例、房源 | CPT + 自定义分类法 | 需要独立菜单、独立模板、可复用 WP 查询与权限 |
| 只是给文章加几个属性 | 自定义字段(meta) | 开一种新内容类型成本远大于加字段 |
| 百万级结构化数据(日志、订单流水) | 自建数据表 | CPT 全塞 wp_posts 会让查询和后台一起变慢 |
| 只是换个排版 | 区块图案 / 模板 | 不涉及数据模型,别动 post type |
官方文档明确提示过:注册 CPT 的代码在每一次站点请求都会执行,哪怕当前页面根本没用到它。所以别为了”好看”注册几十种类型。
最小可用的注册代码
把下面这段放进一个功能插件(不要放进主题,理由见后文),挂在 init 上:
add_action( 'init', function () {
register_post_type( 'course', [
'labels' => [
'name' => '课程',
'singular_name' => '课程',
'add_new_item' => '添加课程',
'edit_item' => '编辑课程',
],
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // 区块编辑器 + REST API 必需
'menu_icon' => 'dashicons-welcome-learn-more',
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt' ],
'rewrite' => [ 'slug' => 'course' ],
] );
} );
show_in_rest => true 在新版本里几乎是必选项:区块编辑器依赖 REST API 存取内容,这个值不打开,CPT 在古腾堡里会出现各种诡异问题。public 不是一个开关,是五个
很多人以为 public => true 就够了,实际上它只是几个独立开关的默认值来源:
show_ui:后台有没有管理界面。publicly_queryable:前台能不能通过?post_type=course之类查询。exclude_from_search:要不要排除出站内搜索结果(默认值是public的反值)。show_in_nav_menus:能不能加进菜单。show_in_admin_bar:后台工具栏是否出现。
想要”后台能管、前台不单独暴露”的内部内容类型,就设 public => false 再单独把 show_ui => true,而不是硬凑。
配套注册自定义分类法
- 用
register_taxonomy()注册分类法,object_type参数直接传 CPT 名称数组,不要用register_taxonomy_for_object_type()后补。 - 需要层级(像分类目录)就设
hierarchical => true,需要扁平标签(像标签)就设false。 - 同样加
show_in_rest => true,否则区块编辑器和 REST 里读不到。 - 在
register_post_type()的taxonomies参数里同时声明,保证parse_query、pre_get_posts这类钩子行为一致。 - 到后台「设置 → 固定链接」点一次保存,触发重写规则刷新。
前台模板怎么接
模板层级对 CPT 同样生效,优先级从高到低:
single-course.php→ 单篇single.php→ 兜底单篇archive-course.php→ 归档archive.php→ 兜底归档index.php→ 最终兜底
只想改归档的查询参数(比如每页显示 24 条、按自定义字段排序),不要写新查询,用 pre_get_posts 拦截主查询:
add_action( 'pre_get_posts', function ( $q ) {
if ( ! is_admin() && $q->is_main_query() && $q->is_post_type_archive( 'course' ) ) {
$q->set( 'posts_per_page', 24 );
$q->set( 'orderby', 'menu_order' );
$q->set( 'order', 'ASC' );
}
} );
三个必须避开的坑
坑一:在 init 之前注册
WP_Post_Type 对象在 init 之前尚未完成初始化,早于 init 注册会导致重写规则、分类法关联等一批行为失效,而且报错信息很不直观。所有注册动作一律挂 init。
坑二:不刷新重写规则,前台 404
注册 CPT 只是把规则算出来,真正写进数据库需要一次刷新。开发期可以去后台「设置 → 固定链接」保存一次;正式发布必须在插件的 register_activation_hook 里调 flush_rewrite_rules(),并且只在这里调——放在 init 或 wp_loaded 上会让每个请求都重写一次规则,是拖慢站点的经典原因。
坑三:把 CPT 写进主题
post type 一旦放进主题,换主题时后台菜单和数据就”消失”了(数据其实还在 wp_posts 里,只是没有注册代码所以读不出来)。正确位置是功能插件或 mu-plugin,保证内容与外观解耦。
CPT 的文章类型名有什么限制?
注册了很多 CPT 会不会拖慢网站?
不想写代码,有没有可视化方案?
CPT 怎么接入 REST API 给外部调用?
show_in_rest => true,默认路由就是 /wp-json/wp/v2/course。可以用 rest_base 改路由名,用 rest_controller_class 换成自定义控制器。需要写入时用应用密码做 Basic 鉴权。相关阅读
- WordPress 钩子(Hooks)怎么用:Action 与 Filter 的区别、优先级与自定义钩子实战
- WordPress 子主题怎么用:不改父主题也能安全定制的完整步骤
- WordPress 全站编辑(FSE)实战:用站点编辑器改版整个网站
- WordPress theme.json 配置手册










评论 (0)