本文约 6,375 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:文章只有标题正文,业务数据往哪放
做企业站时客户会提一串需求:产品要有”型号、参数表、PDF 下载”,案例要有”客户名称、施工时间、现场图集”,首页要有”轮播图(图片+标题+链接)可增删改”。这些东西用正文编辑器硬写,客户改一次就得找你一次,而且数据结构混乱,没法按型号筛选。
WordPress 自带的”自定义字段”能解决这个问题,但原生界面是一张键值对的裸表,客户填的时候完全是猜。Advanced Custom Fields(简称 ACF)做的事就是把这张裸表包装成有类型、有校验、有布局的表单:图片字段给上传按钮,日期字段给日历,关联字段给下拉搜索。字段存进去的依然是 wp_postmeta,但填的人和读的人都舒服了。
ACF 分免费版和 PRO 版。免费版已经覆盖文本、图片、文件、选择、关联、Repeater、Gallery、Flexible Content 等核心类型,足够绝大多数站点;PRO 版多的是 Options Page、Repeater 之外的 Gallery 增强、Clone 字段、区块(ACF Blocks)等。下面以免费版为主,涉及 PRO 的地方会标注。
原理分析:字段组、位置规则与存储
ACF 的核心概念是三层嵌套:字段(Field)→ 字段组(Field Group)→ 位置规则(Location Rules)。
- 字段定义”填什么”:
name(存储用的键)、label(界面显示名)、type(类型)、instructions、required、default_value。 - 字段组是一组字段的集合,也就是后台那张表单的容器,可以控制显示位置(
normal/side/acf_after_title)、样式(盒装 / 无缝)和顺序。 - 位置规则决定这个字段组出现在哪些编辑界面上。规则是”与/或”组合的,比如”文章类型 = 产品 且 产品分类 = 机械设备”。
存储层面要记住两点:
- 字段值存进
wp_postmeta,meta_key 就是字段的name。所以get_field( 'model' )本质上等价于get_post_meta( get_the_ID(), 'model', true )。 - 字段组本身(配置)默认存在
wp_posts里,post_type为acf-field-group,字段为acf-field。这意味着字段配置是数据库数据,不是代码——换了环境就得同步,这是后面要讲本地 JSON 的原因。
| 常用字段类型 | 存储形态 | 典型用途 |
|---|---|---|
| Text / Textarea / Number | 字符串或数字 | 型号、简介、排序值 |
| Image / File | 附件 ID(整数) | 轮播图、PDF 下载 |
| Select / Checkbox / Radio | 单个值或序列化数组 | 枚举选项 |
| True / False | 1 或空 |
布尔开关 |
| Post Object / Relationship | ID 或 ID 数组 | 关联文章、交叉推荐 |
| Taxonomy | 分类 ID | 跨类型打标 |
| Repeater / Gallery | 行数 + 多个子键 | 参数表、图集 |
| Group | 子字段数组 | 结构化分组 |
保姆级实操
第一步:建字段组并设置位置规则
后台「Custom Fields → Field Groups → Add New」,填标题,加字段,然后在 Location 区域写规则。例如”只在产品类型下显示”:Post Type is equal to Product。规则保存后,去产品编辑页就能看到表单。
第二步:前台调用基础字段
<?php
// 单个字段:第二个参数不传时默认取当前文章 ID
$model = get_field( 'model' ); // 返回值
the_field( 'model' ); // 直接 echo,等价于 echo get_field()
// 图片字段默认返回数组,也可指定返回格式
$image = get_field( 'cover_image' );
if ( $image ) {
printf(
'<img src="%s" width="%d" height="%d" alt="%s" loading="lazy">',
esc_url( $image['url'] ),
(int) $image['width'],
(int) $image['height'],
esc_attr( $image['alt'] )
);
}
// 拿完整字段定义(含 choices 等配置)用 get_field_object
$field = get_field_object( 'model' );
echo esc_html( $field['label'] );
图片字段的返回格式可以在字段配置里选 Array / URL / ID。建议统一用 Array,前台能拿到 alt、width、height,对 SEO 和 CLS 都有好处。
第三步:Repeater 循环输出参数表
Repeater 是实际项目里用得最多、也最容易写错的类型:
<?php if ( have_rows( 'specs' ) ) : ?>
<table class="spec-table">
<tbody>
<?php while ( have_rows( 'specs' ) ) : the_row(); ?>
<tr>
<th><?php echo esc_html( get_sub_field( 'label' ) ); ?></th>
<td><?php echo esc_html( get_sub_field( 'value' ) ); ?></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
<?php endif; ?>
have_rows() / the_row() / get_sub_field() 这套函数操作的是 ACF 内部的行指针,不能在同一个页面里嵌套着乱调;Flexible Content 里则用 get_row_layout() 判断当前布局:
<?php
while ( have_rows( 'page_blocks' ) ) : the_row();
if ( 'hero' === get_row_layout() ) {
get_template_part( 'blocks/hero' );
} elseif ( 'faq' === get_row_layout() ) {
get_template_part( 'blocks/faq' );
}
endwhile;
第四步:用 PHP 注册字段组(团队协作推荐)
界面配置的字段组不在版本控制里,多人协作必然打架。改用 acf_add_local_field_group() 写在主题或插件里:
add_action( 'acf/init', function () {
acf_add_local_field_group( array(
'key' => 'group_product',
'title' => '产品信息',
'fields' => array(
array(
'key' => 'field_model',
'label' => '产品型号',
'name' => 'model',
'type' => 'text',
),
array(
'key' => 'field_specs',
'label' => '参数表',
'name' => 'specs',
'type' => 'repeater',
'sub_fields' => array(
array( 'key' => 'field_spec_label', 'label' => '项目', 'name' => 'label', 'type' => 'text' ),
array( 'key' => 'field_spec_value', 'label' => '数值', 'name' => 'value', 'type' => 'text' ),
),
),
),
'location' => array(
array(
array( 'param' => 'post_type', 'operator' => '==', 'value' => 'product' ),
),
),
) );
} );
第五步:把界面配置导出为 JSON(ACF Local JSON)
如果坚持用界面配置,至少启用本地 JSON,让配置落到文件、进 Git:
add_filter( 'acf/settings/save_json', function () {
return get_stylesheet_directory() . '/acf-json';
} );
add_filter( 'acf/settings/load_json', function ( $paths ) {
$paths[] = get_stylesheet_directory() . '/acf-json';
return $paths;
} );
启用后,每次在后台保存字段组,都会往主题目录下的 acf-json/ 写一个 JSON 文件;部署到新环境时 ACF 会自动从这些文件加载。
第六步:全局设置用选项页(ACF PRO)
add_action( 'acf/init', function () {
if ( function_exists( 'acf_add_options_page' ) ) {
acf_add_options_page( array(
'page_title' => '站点全局设置',
'menu_slug' => 'site-settings',
'capability' => 'manage_options',
) );
}
} );
// 前台取值时第二个参数固定写 'option'
$icp = get_field( 'icp_number', 'option' );
常见踩坑
get_field()必须在循环内或显式传 ID。 在WP_Query自定义循环里,get_the_ID()返回的是循环当前文章,不是主循环文章;不确定时永远写全get_field( 'x', $post_id )。- 字段
name用了中文或连字符。name会成为 meta_key,建议只用小写字母和下划线,且不要用wp_开头。 the_field()不做转义。 它直接 echo,字段内容含 HTML 时既可能破坏布局也可能引入 XSS。输出到前台优先get_field()+esc_html()/esc_url()/wp_kses_post()。- 改了字段
name等于换了一个键,旧数据还在老键上,前台会突然全部取不到。改名前先想清楚要不要做数据迁移。 - Repeater 里套 Repeater 会显著拖慢列表页。 每一行每个子字段都对应一条 meta 记录,100 篇文章 × 20 行 × 3 子字段就是 6000 行。列表页不要渲染 Repeater,只渲染简单字段,具体优化思路见 ACF get_field 性能优化。
- 字段组配置丢了。 换服务器只导了
wp_posts没导acf-field-group,后台字段全空。启用本地 JSON 可以根治。 - 案例: 一个机械外贸站把产品参数做成 Repeater,后台填了三年的数据。后来同事在字段组里把子字段
value改名成spec_value(为了”更规范”),保存后前台参数表全空,客户以为数据丢了。恢复办法是写一次性脚本,用$wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_key='specs_%_spec_value' WHERE meta_key LIKE 'specs_%_value'" )批量改名——注意 Repeater 子字段的 meta_key 形如specs_0_value,是带行号的。教训是:字段name一旦上线就等同于数据库表结构,改名必须配套迁移脚本。
总结
ACF 的正确用法可以浓缩成三条:字段组靠位置规则收敛到具体场景,避免”全站一个巨型字段组”;字段配置要么用 acf_add_local_field_group() 写成代码,要么启用本地 JSON,总之必须进版本控制;前台取值一律显式传 ID 并做转义。
上手顺序建议:先做两个字段的字段组跑通”后台填—前台读”闭环,再上 Repeater,再上 Flexible Content 做区块化页面。涉及全局配置时用 Options Page 而不是塞进某个文章;涉及大量重复数据时先评估性能再决定要不要 Repeater。更多关于字段读取的性能细节可以接着看 ACF 自定义字段性能优化,而产品类型本身如何优雅创建,可以参看 Custom Post Type UI 创建自定义文章类型。
