本文约 5,216 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:什么都塞进”文章”,后台迟早会乱
WordPress 默认只有”文章”和”页面”两种内容类型,文章的职能是”按时间倒序列出来的内容”,页面是”独立静态内容”。但真实站点远不止这两种:产品需要参数与价格、案例需要客户与时间、问答需要独立归档、招聘需要有效期。
把它们全塞进”文章”再靠分类区分,短期内能用,长期一定会出问题:归档页混在一起、SEO 结构混乱、后台筛选靠人肉记分类、客户找不到入口。自定义文章类型(Custom Post Type,简称 CPT)就是把这些业务对象各自独立成一张表——它们有自己的菜单、自己的归档页、自己的模板文件、自己的权限。
写 CPT 需要调 register_post_type(),参数有三十多个,手写容易漏。Custom Post Type UI(下称 CPT UI)这个插件把参数做成了表单,还带”生成代码”功能,让你可以先在界面上试,稳定后导出代码进版本控制。
原理分析:几个真正影响行为的参数
CPT UI 界面上的字段直接对应 register_post_type() 的参数,下面这几个最容易踩坑:
| 参数 | 作用 | 常见取值 |
|---|---|---|
public |
总开关,影响前台可见与后台 UI | true |
publicly_queryable |
是否允许前台 URL 访问 | true(关闭后前台 404) |
has_archive |
是否有归档页 /product/ |
true / 指定 slug |
rewrite |
固定链接规则,含 slug 与 with_front |
array( 'slug' => 'product' ) |
show_in_rest |
是否在古腾堡与 REST API 中可用 | true(不开就没有区块编辑器) |
supports |
编辑器支持的功能模块 | title,editor,thumbnail,excerpt |
taxonomies |
挂载的分类法 | array( 'product_cat' ) |
capability_type |
权限模型 | post(按文章逻辑)/ page |
menu_icon |
后台菜单图标 | dashicons 类名,如 dashicons-cart |
exclude_from_search |
是否排除出站内搜索 | 一般 false |
固定链接这块要多说一句。注册 CPT 只是往 WordPress 的重写规则表里写入规则,而规则实际是存在数据库 wp_options 的 rewrite_rules 里的。这个值是懒更新的——你不触发刷新,新注册的 CPT 规则就不在表里,前台访问 /product/xxx/ 直接 404,后台却一切正常。这就是”后台能看、前台 404″的根本原因。
刷新规则的代价不小:它会遍历全部规则重新序列化写入,且常常伴随一次数据库写。所以不能在每次 init 上都刷,正确做法是只在”规则可能变化”的时刻刷一次:插件启用/停用时、主题切换时、手动点一次后台「设置 → 固定链接 → 保存更改」。
保姆级实操
第一步:创建文章类型
后台「CPT UI → Add/Edit Post Types」:
- Post Type Slug:
product(只能用小写字母、数字、下划线,不超过 20 个字符,且不能与保留名冲突如post、page、attachment、revision、nav_menu_item); - Plural Label / Singular Label:
产品/产品; - 勾选
Has Archive、Show in REST、Public; - Supports 勾
Title、Editor、Thumbnail、Excerpt; Menu Icon填dashicons-cart。
保存后左侧菜单出现「产品」,点进去能正常新建。
第二步:刷新固定链接
这是必做的一步,两个办法任选:
# 命令行(推荐,最干净)
wp rewrite flush --hard
或者在后台「设置 → 固定链接」直接点一次「保存更改」(不用改任何内容)。完成后访问 /product/ 应该能看到归档页。
第三步:建分类法并挂载
「CPT UI → Add/Edit Taxonomies」,Taxonomy Slug 填 product_cat,Hierarchical 选 True(像分类目录)或 False(像标签),Attach to Post Type 勾上 product。分类法同样需要刷新固定链接。
第四步:建模板文件
WordPress 的模板层级对 CPT 有专门约定:
single-product.php → 单个产品页
archive-product.php → 产品归档页
taxonomy-product_cat.php → 产品分类页
没有这些文件时,会退回到 single.php / archive.php / index.php。建议至少建 archive-product.php,因为归档页通常要展示字段网格,跟博客列表完全不一样:
<?php
// archive-product.php
get_header(); ?>
<main class="product-grid">
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<article class="card">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail( 'medium', array( 'loading' => 'lazy' ) ); ?>
<h2><?php the_title(); ?></h2>
</a>
<p class="model"><?php echo esc_html( get_post_meta( get_the_ID(), 'model', true ) ); ?></p>
</article>
<?php endwhile; the_posts_pagination(); else : ?>
<p>暂无产品。</p>
<?php endif; ?>
</main>
<?php get_footer();
第五步:导出代码,脱离插件
「CPT UI → Tools → Get Code」可以拿到当前配置的 register_post_type() 代码。粘到主题的 functions.php 或更推荐的小型功能插件里,就可以卸载 CPT UI(插件卸了类型还在,因为注册逻辑变成代码了):
add_action( 'init', function () {
register_post_type( 'product', array(
'labels' => array(
'name' => '产品',
'singular_name' => '产品',
'add_new_item' => '添加产品',
),
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'menu_icon' => 'dashicons-cart',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'product', 'with_front' => false ),
) );
} );
代码移走后,务必再 wp rewrite flush 一次,并在卸载插件前后各确认一遍归档页可访问。
第六步:只在必要时刷新规则(代码版)
// 只在插件激活时刷一次,避免每次请求都写数据库
register_activation_hook( __FILE__, function () {
mimangfei_register_product_cpt(); // 先注册
flush_rewrite_rules(); // 再刷新
} );
register_deactivation_hook( __FILE__, function () {
flush_rewrite_rules();
} );
常见踩坑
- Slug 超过 20 个字符或含大写字母。
register_post_type()对 slug 有硬性限制(≤20 字符、仅小写字母数字下划线),超限会注册失败且无明显报错。 with_front没关。 若站点固定链接结构是/blog/%postname%/,默认with_front => true会让产品链接变成/blog/product/xxx/。想独立就设'with_front' => false。exclude_from_search忘了改。 默认新建的 CPT 是否被搜索包含取决于public,如果搜不到产品,检查publicly_queryable与exclude_from_search,并用pre_get_posts把它并入主查询。- 归档页翻页 404。 归档页
has_archive为 true 但分页/product/page/2/404,多半是固定链接未刷新,或主题里用了自定义WP_Query却没处理paged变量。 show_in_rest关着,古腾堡编辑器打不开或区块保存报错。 需要在 REST 中暴露的 CPT 必须开show_in_rest => true。- Slug 与已存在的页面冲突。 已经有一个别名为
/product/的页面时,CPT 归档会与之打架,访问结果取决于规则优先级,表现非常诡异。改名或删掉冲突页面。 - 案例: 某站点上线前在测试环境建好 CPT 并导入了 400 条产品,上线时只导了数据库没导 CPT UI 配置,结果前台
/product/全部 404,而后台菜单、列表、编辑页一切正常——因为 CPT 的注册信息存在wp_posts里(post_type 为cptui_post_type之类的插件存储),没迁过去。补救办法是把配置以代码形式写进主题,或用 CPT UI 的 Import/Export 功能导出 JSON 再导入。教训:CPT 定义属于”结构”,必须像代码一样纳入版本控制,不能只存在于数据库里。
总结
CPT UI 的正确用法是”界面探索、代码落地”:前期用它快速试参数、看效果,稳定后用 Tools → Get Code 导出,把 register_post_type() 写进主题或功能插件,最终让结构脱离插件存在。
行动清单:slug 控制在 20 字符内的小写;has_archive、show_in_rest、supports 按需勾;每次新增/改名后立刻 wp rewrite flush;归档与单页分别建 archive-{slug}.php 和 single-{slug}.php;上线前确认 CPT 配置已在代码里。产品字段怎么做可以接着看 Advanced Custom Fields 字段组教程,字段多了之后别忘了按 ACF get_field 性能优化 里的方法压一下查询次数;如果还想彻底不依赖插件,Meta Box 与 Pods 插件对比 里有另一套思路。
