ACF gives you complete freedom in how you name field groups, field names, and field keys. That freedom is the trap. A few conventions kept consistently across a multi-year codebase make the difference between "I can find this field in five seconds" and "I have to grep the whole repo and the database to figure out what controls this." Cost of conventions: zero. Benefit: compounds across every team member, every refactor, every audit, every WP-CLI script for years.
Jump to:
- Field names: snake_case, lowercase, no abbreviations
- Field keys: hierarchy-encoded and globally unique
- Field group keys: post-type prefixed
- Sub-field naming inside Repeaters and Flexible Content
- True/False fields: prefix with is_ or has_
- Avoid these patterns
- The local-JSON convention
Field names: snake_case, lowercase, no abbreviations
Field names appear in wp_postmeta.meta_key, in template code as get_field( 'name' ), in update_field calls, and in WP-CLI queries. They are the most-used identifier across the codebase.
The convention:
- snake_case.
hero_heading, notheroHeading,Hero_Heading, orhero-heading. - Lowercase only. ACF is case-sensitive, and inconsistent casing is the source of "the field returns nothing" bugs.
- No abbreviations.
descriptionnotdesc,featured_imagenotfeat_img. Two extra characters today saves five minutes of confusion next year. - Singular for single-value fields, plural for arrays.
authorfor a Post Object field,authorsfor a Relationship or Repeater of authors. - No leading underscore. Underscored meta keys are conventionally hidden from the WordPress admin meta box list; ACF uses
_fieldnamefor its field-key reference rows, and your own field names should not collide.
Examples:
| Good | Bad |
|---|---|
hero_heading | heroHeading, Hero_Heading |
featured_image | feat_img, featuredImg |
team_members | team_member_list, members |
is_featured | featured_flag, feat |
event_start_date | start, startDate |
Field keys: hierarchy-encoded and globally unique
Every ACF field has a unique key (field_abc123...) that identifies it across the system. You can let ACF auto-generate keys, but if you register field groups via PHP, choosing readable keys yourself helps debugging immensely.
The convention I use:
group_prefix for field group keys.group_listing_core.field_prefix for field keys.field_listing_hero_heading.layout_prefix for Flexible Content layout keys.layout_listing_stat_row.- Hierarchy encoded in the key. A field inside the
stat_rowlayout of thepage_builderFlexible Content field on thelistingpost type might befield_listing_pb_stat_row_label. The prefix encodes where it lives.
Why this matters: when you see a _fieldname reference in wp_postmeta pointing at field_listing_pb_stat_row_label, you know immediately what field that is and where it is defined. Auto-generated keys (field_5f8a1b3c2d4e5) require you to look them up against the field group registration.
Example registration:
acf_add_local_field_group( [
'key' => 'group_listing_core',
'title' => 'Listing Core',
'fields' => [
[
'key' => 'field_listing_hero_heading',
'name' => 'hero_heading',
'type' => 'text',
],
[
'key' => 'field_listing_page_builder',
'name' => 'page_builder',
'type' => 'flexible_content',
'layouts' => [
'layout_listing_pb_hero' => [
'key' => 'layout_listing_pb_hero',
'name' => 'hero',
'sub_fields' => [
[
'key' => 'field_listing_pb_hero_heading',
'name' => 'heading',
'type' => 'text',
],
],
],
],
],
],
] );The keys read top-to-bottom as a path: field_listing_pb_hero_heading is "the heading field inside the hero layout of the page_builder field on the listing post type." Self-documenting.
Field group keys: post-type prefixed
Group field groups by the post type they belong to. group_listing_core, group_listing_meta, group_listing_seo. When you have 20+ field groups across the site, prefixing makes them sortable in the admin UI and easy to find in the JSON-sync directory.
Avoid:
- Generic names like
group_main,group_settings,group_fields. They tell you nothing. - Project-prefixed names like
group_acme_listing_core. The project context is the entire repo; restating it in every key is noise.
Sub-field naming inside Repeaters and Flexible Content
Sub-field names live in their parent context (a sub-field label inside the stats Repeater is only label from that context). So the SHORT name in the sub-field is fine: label, value, unit: but the KEY should encode the full hierarchy: field_listing_pb_stat_row_stats_label.
Why: when you read wp_postmeta, you see meta keys like page_builder_2_stats_3_label. The corresponding _field_key reference row points at the field key. If the key is descriptive, you immediately know what label means in context. If it is auto-generated, you have to chase the reference.
The same logic applies to ACF Block sub-fields, Group field children, and any other nesting structure.
True/False fields: prefix with is_ or has_
Naming convention specifically for True/False fields:
is_featured: boolean state of the post.has_video: boolean presence of related content.should_index: boolean instruction to other code.allow_comments: boolean permission.
The prefix tells the reader "this is a boolean, not a string." get_field( 'is_featured' ) reads naturally; get_field( 'featured' ) is ambiguous about what type comes back.
Avoid generic boolean names like flag, option, setting, enabled. They tell you nothing about what is being toggled.
Avoid these patterns
A non-exhaustive list of antipatterns I have seen on inherited codebases:
- CamelCase field names.
HeroHeading. Inconsistent with WordPress meta key conventions, hard to type, breaksget_fieldlookups when someone triesheroHeading. - Field names with hyphens.
hero-heading. PHP variable rules do not allow hyphens; you cannot do$post['hero-heading']directly. snake_case is friendlier. - Numbered field names.
image_1,image_2,image_3. Use a Repeater. Numbered fields cannot be iterated cleanly. - Single-letter prefixes.
f_heading,s_subtitle. Saves three characters; costs your sanity. - Auto-generated keys for hand-registered field groups. If you write PHP registration, write descriptive keys. The auto-generated
field_5f8a1b3c2d4e5is fine for editor-managed field groups but terrible for code-managed ones. - Duplicate field names across post types with different shapes. Two post types both have a
detailsfield; one is a Repeater, the other is a wysiwyg. Cross-post-type code is now confused.
The local-JSON convention
ACF supports local JSON: drop field group export JSON files into your theme's acf-json/ directory and ACF auto-syncs them. The filename follows the field group key: group_listing_core.json.
The benefit: field group changes go through git. Editors edit fields in wp-admin; ACF saves the JSON; you commit the JSON. Different developers see each other's field group changes in the diff just like code.
The convention I add:
- Keep
acf-json/at the theme root, not inmu-plugins. ACF defaults to the active theme's directory. - One JSON file per field group, named after the group key.
- Do NOT also register field groups via PHP for the same field group. Pick one source of truth.
For the broader pattern of agency-scale ACF organization, see Using ACF Like a Lightweight Component System. For the AI-assisted scaffolding of new field groups following these conventions, see Using Claude CLI to Manage WordPress Sites.
Sources
Authoritative references this article was fact-checked against.
- Field keys (Advanced Custom Fields docs)advancedcustomfields.com
- Register ACF fields via PHP (Advanced Custom Fields docs)advancedcustomfields.com





