Skip to main content

Field Groups, Location Rules, and Naming Conventions

A maintainable ACF system depends less on how many fields you have and more on whether groups are correctly scoped and named.

Learning Focus

You will learn to design purpose-driven field groups, apply deterministic location rules, and enforce naming conventions with code and CLI verification. This is critical because ambiguous group scope and weak naming cause the majority of long-term ACF maintenance pain.

Concept Overview

Field groups are not just editor forms. They are schema boundaries. A healthy boundary means each group serves one rendering responsibility, appears only where it should, and exposes field names that are unambiguous in templates, APIs, and migration scripts.

Location rules are your routing layer for schema. If location logic is broad, unrelated editors see irrelevant fields and may save values in the wrong context. If location logic is too narrow or misconfigured, required fields disappear where templates expect them. Precision matters more than convenience.

Naming conventions are your long-term compatibility contract. service_headline communicates both domain and purpose. title_text communicates nothing and eventually collides with other components. Teams that enforce naming policies at registration-time avoid subtle bugs during refactors and staff handovers.

Core Idea

Small, clearly owned field groups + strict location targeting + deterministic naming = predictable runtime behavior and faster team delivery.

Mental Model

Think of it as...Because...
Field group as a bounded contextIt should model one business area (service hero, staff profile, case study stats)
Location rules as route guardsThey control where schema is allowed to appear and be edited
Field names as public API contractsTemplates and automation rely on them staying stable
Prefix strategy as namespace managementPrevents collisions between teams and modules

Why It Matters

ApproachWhat HappensImpact in Production
One group per template responsibilityEditors see relevant fields onlyFewer input errors and faster publishing
Broad location rules (post_type == page) everywhereUnrelated pages inherit irrelevant fieldsNoise, misuse, and inconsistent data entry
Prefix naming (service_, staff_, case_)Retrieval code is clear and collision-resistantSafer refactors and lower onboarding cost
Enforce naming policy in codeInvalid field names are rejected earlyPrevents hidden technical debt in schema layer
Overlapping groups with generic names (wrong pattern)Two groups target same context with ambiguous fieldsTemplate confusion, duplicate data points, and production regressions

Reference Table

Term/APISignature/SyntaxPurposeKey Notes
acf_add_local_field_group()`acf_add_local_field_group(array $field_group): arrayfalse`Register group with explicit location and field names
Location rule structure'location' => [ [ [ 'param' => 'page_template', 'operator' => '==', 'value' => 'template-service.php' ] ] ]Scope fields to exact admin contextNested arrays represent OR/AND rule groups
acf/prepare_field`add_filter('acf/prepare_field', callable $callback): arrayfalse`Modify/hide field before render
acf/validate_valueadd_filter('acf/validate_value', callable $callback, int $priority, int $accepted_args): mixedValidate field input before saveGood for policy enforcement by field name/key
acf_get_field_groups()acf_get_field_groups(array $filter = []): arrayQuery runtime groups and location metadataEssential CLI audit primitive
acf_get_fields()`acf_get_fields(intstringarray $parent): array`
wp evalwp eval '<php-code>'Run runtime audits for naming/scopeWorks in CI and maintenance workflows
Repeater/Flexible namingservice_highlights, staff_sections, case_stat_rowsStructured collection naming patternACF Pro Required when using Repeater/Flexible

Practical Use Cases

Use Case 1 — Segment field groups by template responsibility

An agency maintains two page families: service landing pages and case-study pages. Editors previously saw every field on every page. The team now creates two groups with strict location targeting and domain-prefixed names.

  1. Register one group for service template pages.
  2. Register another group for case-study post type.
  3. Prefix all service fields with service_ and case fields with case_.
  4. Keep location rules mutually exclusive.
  5. Verify group keys, names, and locations with WP-CLI.
wp-content/themes/clinic-pro/inc/acf/group-segmentation.php
<?php

declare(strict_types=1);

add_action('acf/init', function (): void {
acf_add_local_field_group([
'key' => 'group_service_template_fields',
'title' => 'Service Template Fields',
'fields' => [
[
'key' => 'field_service_headline',
'label' => 'Service Headline',
'name' => 'service_headline',
'type' => 'text',
],
[
'key' => 'field_service_cta_label',
'label' => 'Service CTA Label',
'name' => 'service_cta_label',
'type' => 'text',
],
[
'key' => 'field_service_cta_url',
'label' => 'Service CTA URL',
'name' => 'service_cta_url',
'type' => 'url',
],
],
'location' => [
[[
'param' => 'page_template',
'operator' => '==',
'value' => 'template-service.php',
]],
],
]);

acf_add_local_field_group([
'key' => 'group_case_study_fields',
'title' => 'Case Study Fields',
'fields' => [
[
'key' => 'field_case_outcome_summary',
'label' => 'Outcome Summary',
'name' => 'case_outcome_summary',
'type' => 'textarea',
],
[
'key' => 'field_case_roi_percent',
'label' => 'ROI Percent',
'name' => 'case_roi_percent',
'type' => 'number',
],
],
'location' => [
[[
'param' => 'post_type',
'operator' => '==',
'value' => 'case_study',
]],
],
]);
});
terminal: command
wp eval '$groups=acf_get_field_groups(); print_r(array_column($groups,"key"));'
wp eval '$g=acf_get_field_groups(["key"=>"group_service_template_fields"]); print_r($g[0]["location"]);'
wp eval '$g=acf_get_field_groups(["key"=>"group_case_study_fields"]); $fields=acf_get_fields($g[0]); print_r(array_column($fields,"name"));'
terminal: output
Array
(
[0] => group_service_template_fields
[1] => group_case_study_fields
)
Array
(
[0] => Array
(
[0] => Array
(
[param] => page_template
[operator] => ==
[value] => template-service.php
)
)
)
Array
(
[0] => case_outcome_summary
[1] => case_roi_percent
)
note

Scope first, then fields. If scope is wrong, naming discipline alone will not save model quality.

Use Case 2 — Enforce naming policy at runtime with validation hooks

A distributed team keeps introducing generic field names like title_text and description. You need a policy that blocks invalid names and surfaces violations during save operations.

  1. Define allowed prefixes (service_, staff_, case_, global_).
  2. Add acf/prepare_field to annotate policy failures during editing.
  3. Add acf/validate_value to block save when invalid names are used.
  4. Log violations for audit.
  5. Verify policy behavior with WP-CLI by inspecting field names.
wp-content/themes/clinic-pro/inc/acf/naming-policy.php
<?php

declare(strict_types=1);

add_action('acf/init', function (): void {
if (!defined('CLINIC_ACF_ALLOWED_PREFIXES')) {
define('CLINIC_ACF_ALLOWED_PREFIXES', ['service_', 'staff_', 'case_', 'global_']);
}
});

add_filter('acf/prepare_field', function ($field) {
if (!is_array($field) || empty($field['name'])) {
return $field;
}

$name = (string) $field['name'];
$allowed = CLINIC_ACF_ALLOWED_PREFIXES;
$isAllowed = false;

foreach ($allowed as $prefix) {
if (str_starts_with($name, $prefix)) {
$isAllowed = true;
break;
}
}

if (!$isAllowed) {
$field['instructions'] = trim((string) ($field['instructions'] ?? ''))
. ' [Naming policy warning: use service_/staff_/case_/global_ prefix]';
}

return $field;
}, 20);

add_filter('acf/validate_value', function ($valid, $value, $field, $input) {
if (!is_array($field) || empty($field['name'])) {
return $valid;
}

$name = (string) $field['name'];

foreach (CLINIC_ACF_ALLOWED_PREFIXES as $prefix) {
if (str_starts_with($name, $prefix)) {
return $valid;
}
}

error_log('[acf-naming-policy] rejected=' . $name);
return 'Field name must start with service_, staff_, case_, or global_';
}, 10, 4);
terminal: command
wp eval '$groups=acf_get_field_groups(); foreach($groups as $group){$fields=acf_get_fields($group); foreach($fields as $field){echo $field["name"] . PHP_EOL;}}'
wp eval '$invalid=[]; $allowed=["service_","staff_","case_","global_"]; foreach(acf_get_field_groups() as $group){foreach(acf_get_fields($group) as $field){$ok=false; foreach($allowed as $p){if(str_starts_with($field["name"],$p)){$ok=true; break;}} if(!$ok){$invalid[]=$field["name"];}}} print_r($invalid);'
terminal: output
service_headline
service_cta_label
service_cta_url
case_outcome_summary
case_roi_percent
Array
(
)
warning

Policy hooks should enforce conventions, not hide bad design. If many names fail, revisit model ownership and review process.

Use Case 3 — Edge case: overlapping location rules create duplicate editor contexts

A rushed release adds a broad post_type == page group that overlaps a service-template group. Editors now see duplicate or conflicting fields, and developers receive inconsistent values depending on which fields were edited last.

❌ Fragile Pattern

wp-content/themes/clinic-pro/inc/acf/overlap-fragile.php
<?php

declare(strict_types=1);

add_action('acf/init', function (): void {
acf_add_local_field_group([
'key' => 'group_generic_page_fields',
'title' => 'Generic Page Fields',
'fields' => [
[
'key' => 'field_title_text',
'label' => 'Title Text',
'name' => 'title_text',
'type' => 'text',
],
],
'location' => [[[
'param' => 'post_type',
'operator' => '==',
'value' => 'page',
]]],
]);
});

✅ Robust Pattern

wp-content/themes/clinic-pro/inc/acf/overlap-robust.php
<?php

declare(strict_types=1);

add_action('acf/init', function (): void {
acf_add_local_field_group([
'key' => 'group_service_template_fields',
'title' => 'Service Template Fields',
'fields' => [
[
'key' => 'field_service_headline',
'label' => 'Service Headline',
'name' => 'service_headline',
'type' => 'text',
],
],
'location' => [[[
'param' => 'page_template',
'operator' => '==',
'value' => 'template-service.php',
]]],
]);

acf_add_local_field_group([
'key' => 'group_default_page_fields',
'title' => 'Default Page Fields',
'fields' => [
[
'key' => 'field_page_intro',
'label' => 'Page Intro',
'name' => 'global_page_intro',
'type' => 'textarea',
],
],
'location' => [[[
'param' => 'page_template',
'operator' => '!=',
'value' => 'template-service.php',
]]],
]);
});
terminal: command
wp eval '$service=acf_get_field_groups(["key"=>"group_service_template_fields"]); $default=acf_get_field_groups(["key"=>"group_default_page_fields"]); print_r($service[0]["location"]); print_r($default[0]["location"]);'
wp eval '$all=[]; foreach(acf_get_field_groups() as $g){foreach(acf_get_fields($g) as $f){$all[]=$f["name"];}} print_r(array_filter(array_count_values($all), fn($n)=>$n>1));'
terminal: output
Array
(
[0] => Array
(
[0] => Array
(
[param] => page_template
[operator] => ==
[value] => template-service.php
)
)
)
Array
(
[0] => Array
(
[0] => Array
(
[param] => page_template
[operator] => !=
[value] => template-service.php
)
)
)
Array
(
)
warning

If editors report "duplicate fields," inspect location overlap first. It is usually a scoping bug, not a rendering bug.

Common Mistakes

MistakeRoot CauseWhat Breaks in ProductionCorrect Pattern
Creating one mega group for many templatesNo schema ownership boundariesEditors save irrelevant values and introduce noiseSplit groups by component/template and verify with acf_get_field_groups() filters
Generic names like title_textNo naming policy enforcementName collisions and unreadable templatesEnforce prefixes via acf/validate_value and audit with wp eval
Overlapping location rulesBroad rule logic added for speedDuplicate inputs and conflicting dataUse explicit template/post-type constraints and CLI location dumps
Renaming names after launch without migrationName treated as label, not API contractExisting values appear missing in templatesKeep old names or migrate with wp post meta + update_field()
Missing required flags for mandatory render inputsNo distinction between optional and mandatory fieldsEmpty headings/CTAs on live pagesSet required => 1 and run content completeness audits
Ignoring policy drift over timeNo recurring model auditsTechnical debt accumulates in schema layerAdd monthly wp eval naming compliance checks
Deep Dive: Why Overlapping Location Rules Are Harder to Debug Than They Look

Overlaps rarely fail loudly. Editors simply see extra fields and may fill whichever one looks familiar, which creates divergent data paths. Templates then read one field while editors updated another, producing seemingly random frontend mismatches. Because both fields can have similar labels, support teams often suspect caching or deployment issues first. The fastest diagnosis is to inspect each group's location arrays and identify mutually non-exclusive conditions.

wp eval 'foreach(acf_get_field_groups() as $g){echo $g["key"] . PHP_EOL; print_r($g["location"]);} '

Best Practices

  1. Define one owner and one purpose per field group: e.g., "Service Hero" owned by marketing-engineering team.
  2. Use prefixes that encode domain context: service_, staff_, case_, global_.
  3. Design location rules as explicit constraints, not broad guesses: avoid blanket post_type == page where possible.
  4. Audit naming compliance via CLI regularly: wp eval over acf_get_fields() output.
  5. Treat field names as stable contracts: prefer additive change over destructive rename.
  6. Model optional vs required intentionally: required fields only where template cannot safely fallback.
  7. Document location intent in code comments and team docs, then verify in runtime outputs.

Hands-On Practice

Exercise 1: Split one mega group into two scoped groups

Create scoped group registrations similar to Use Case 1, then run:

wp eval 'print_r(array_column(acf_get_field_groups(), "title"));'

After completing this exercise, output should include two distinct groups:

Service Template Fields
Case Study Fields

Exercise 2: Verify location exclusivity for service template

Run:

wp eval '$g=acf_get_field_groups(["key"=>"group_service_template_fields"]); print_r($g[0]["location"]);'

After completing this exercise, output should include:

[param] => page_template
[value] => template-service.php

Exercise 3: Enforce naming policy with hooks

Create wp-content/themes/clinic-pro/inc/acf/naming-policy.php from Use Case 2 and run:

wp eval '$invalid=[]; $allowed=["service_","staff_","case_","global_"]; foreach(acf_get_field_groups() as $group){foreach(acf_get_fields($group) as $field){$ok=false; foreach($allowed as $p){if(str_starts_with($field["name"],$p)){$ok=true; break;}} if(!$ok){$invalid[]=$field["name"];}}} print_r($invalid);'

After completing this exercise, expected output:

Array
(
)

Exercise 4: Create test content objects for each model

Run:

wp post create --post_title="Service Rule Drill" --post_status=publish --post_type=page
wp post create --post_title="Case Rule Drill" --post_status=publish --post_type=case_study

After completing this exercise, expected output pattern:

Success: Created post
Success: Created post

Exercise 5: Detect duplicate field names across all groups

Run:

wp eval '$names=[]; foreach(acf_get_field_groups() as $group){foreach(acf_get_fields($group) as $field){$names[]=$field["name"];}} print_r(array_filter(array_count_values($names), fn($n)=>$n>1));'

After completing this exercise, expected output should be empty:

Array
(
)

CLI Reference

CommandPurposeReal Example Output
wp eval 'print_r(array_column(acf_get_field_groups(), "key"));'List runtime group keysgroup_service_template_fields group_case_study_fields
wp eval '$g=acf_get_field_groups(["key"=>"group_service_template_fields"]); print_r($g[0]["location"]);'Inspect one group's location rulesparam => page_template
wp eval '$g=acf_get_field_groups(["key"=>"group_case_study_fields"]); print_r(array_column(acf_get_fields($g[0]),"name"));'List names for one groupcase_outcome_summary case_roi_percent
wp eval '$invalid=[]; ... ; print_r($invalid);'Run naming-prefix compliance checkArray ( )
wp post create --post_title="Service Rule Drill" --post_status=publish --post_type=pageCreate test page for location verificationSuccess: Created post 1330.
wp post create --post_title="Case Rule Drill" --post_status=publish --post_type=case_studyCreate test case-study objectSuccess: Created post 1331.
wp eval '$names=[]; ... ; print_r(array_filter(array_count_values($names), fn($n)=>$n>1));'Detect duplicate field namesArray ( )
`wp plugin list --status=activegrep advanced-custom-fields-pro`Confirm dependency availability

What's Next

tip

Revisit this lesson when your team adds a new template or post type and needs to decide how to scope field groups without creating overlap or naming debt.