WordPress register_post_type:自定义内容类型从注册到前台显示的完整链路
public 与三个细分开关的区别、has_archive 和 rewrite 各管什么、注册后 404 为何必须刷新重写规则,附模板层级与分页控制。
register_post_type() 只是开头。注册完后台菜单出现了,前台访问却 404、归档页不显示、编辑器变成经典样式——这些问题分布在注册参数、重写规则、模板选择三个环节里,得逐层捋。
一、注册的最小可用集
add_action('init', function () {
register_post_type('book', array(
'labels' => array(
'name' => '图书',
'singular_name' => '图书',
'add_new_item' => '新增图书',
),
'public' => true,
'has_archive' => true,
'rewrite' => array('slug' => 'books'),
'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
'show_in_rest' => true,
'menu_icon' => 'dashicons-book',
));
});
六七个参数缺一个就可能出问题,下面逐个说清各自管什么。
二、public 与三个细分开关
'public' => true 是个总闸,它会连带把 publicly_queryable、show_ui、show_in_nav_menus 都设为 true。想让类型「后台可管但前台不可访问」,不要用 public => false(那样后台菜单也没了),而应该:
'public' => true,
'publicly_queryable' => false, // 前台单页 404
反过来,「前台可访问但不想让用户看到后台菜单」就用 show_ui => false。这两个开关独立于 public,经常被一起用错。
三、has_archive 与 rewrite 是两个东西
| 参数 | 控制的地址 | 关掉会怎样 |
|---|---|---|
rewrite.slug | 单页 /books/书名 | 单页变成 ?post_type=book&p=1 |
has_archive | 列表 /books/ | 归档页 404,只剩单页 |
常见误解是以为 rewrite 设了就自动有归档页。实际上要先 has_archive => true,再配合 rewrite.slug 决定路径。两者都设成同一个 slug 时,WordPress 内部有优先级处理,但容易出现列表与单页抢路径,建议单页和归档给不同的 slug 前缀。
四、注册后 404:必须刷新重写规则
这是最高频的坑。register_post_type() 只在 init 跑,而重写规则(rewrite rules)是持久化在数据库 options 里的。新注册的 slug 没进规则表,所以访问 404。
三个解法,按推荐度排序:
- 后台 → 设置 → 固定链接 → 直接点保存(不用改任何东西),规则会重刷
- 代码里判断一次:
flush_rewrite_rules(),但必须只在激活时调用一次 - 改完后 WP-CLI 执行
wp rewrite flush
第 2 条常见错误写法:
// 错:每次 init 都 flush,性能灾难
add_action('init', function () {
register_post_type(...);
flush_rewrite_rules(); // 千万别这样
});
// 对:只在插件激活时执行一次
register_activation_hook(__FILE__, function () {
// 注册逻辑抽成函数,这里调一次再刷
my_register_book();
flush_rewrite_rules();
});
注意 register_activation_hook 里注册的类型要能被 flush 感知,所以得先执行注册函数,再调 flush。顺序反了照样 404。
五、模板层级:前台用哪个文件渲染
自定义类型走的是独立模板链:
单页:single-book.php → single.php → singular.php → index.php
归档:archive-book.php → archive.php → index.php
放进主题根目录即可。子主题里放会优先于父主题。如果两个都不想要、只想改内容不改布局,可以用 filter 拦截:
add_filter('template_include', function ($template) {
if (is_singular('book')) {
return get_stylesheet_directory() . '/templates/book-single.php';
}
return $template;
});
放主题子目录时需要这样指定,因为自动查找只在主题根目录找。
六、has_archive 的列表分页
归档页默认每页条数跟随「设置 → 阅读」里的全站值。想单独控制:
add_action('pre_get_posts', function ($q) {
if (is_admin() || !$q->is_main_query()) {
return;
}
if ($q->is_post_type_archive('book')) {
$q->set('posts_per_page', 12);
}
});
两个 return 必须有:is_admin() 防止影响后台列表,is_main_query() 防止影响页面里嵌套的 WP_Query。少了任何一个都会连带改坏别处的分页。
七、show_in_rest 与区块编辑器
不设 'show_in_rest' => true,该类型会退回经典编辑器,也没有 REST 接口(前后端分离、Gutenberg 自定义区块都要它)。
想要自定义字段出现在编辑器侧栏,还需要 register_post_meta() 并同样声明 show_in_rest,否则字段在后台存不进去。
八、分类法挂上去
内置分类和标签默认不挂自定义类型,得显式声明,或者自己注册分类法:
// 复用内置分类
'taxonomies' => array('category', 'post_tag'),
// 或注册专属分类法后挂上去
register_taxonomy('book_genre', 'book', array(...));
复用内置分类有个副作用:该类型的文章会出现在普通分类归档页里,有时候不是想要的。
小结
注册自定义类型的四步:init 里注册 → 激活时 flush 一次规则 → 建对应模板文件 → pre_get_posts 调分页。80% 的「注册了却 404」都卡在第二步。