Field keys#
A field key is part of your site’s data.
ACF stores a field’s key alongside every value it saves. That key is how ACF connects the stored value back to the field definition that knows how to interpret it.
If the key changes, the definition no longer matches the saved data. The field can render empty while the old value remains in the database, and nothing reports an error.
That is why ACF Chef treats key generation as a first-class part of the field-group definition rather than an implementation detail.
Namespaced keys#
NamespacedKeys is the default strategy.
It builds readable keys from the field’s path through the group:
group_hero
field_hero_content
field_hero_content_title
Each path segment is sanitised, the result is lowercased and the segments are joined with underscores.
The group key is:
group_<sanitised name>
For fields, the strategy always uses the field_ prefix:
field_<group>_<parent>_<field>
It deliberately ignores the prefix supplied by the field object.
That matters for flexible-content layouts. A layout still receives a field_ key under NamespacedKeys:
field_hero_sections
field_hero_sections_media
field_hero_sections_media_picture
Using field_ for layouts is part of the data format chosen by this strategy, not an accidental loss of the upstream prefix.
Readable keys are useful because you can recognise them in the database, search for them in the codebase and predict them without first compiling the group.
Hashed keys#
HashedKeys follows extended-acf’s key format.
It hashes the same field path with fnv1a32 and keeps the prefix supplied by the field object:
field_<hash>
layout_<hash>
field_<hash>
A flexible-content layout therefore retains its upstream layout_ prefix.
The group key remains readable:
group_<sanitised name>
HashedKeys does not additionally lowercase the group name after sanitisation.
A group key is not stored alongside a field value, so there is less benefit in making it opaque. Keeping it readable is useful in the ACF admin and in code that works with field groups.
Use HashedKeys when a site already stores extended-acf keys or when you deliberately want to retain upstream’s key format.
The key strategy contract#
A key strategy implements two methods:
use AcfChef\Contracts\KeyStrategy;
interface KeyStrategy
{
public function key(
string $prefix,
array $ancestry,
string $name,
): string;
public function groupKey(string $name): string;
}
The three arguments passed to key() have precise meanings.
$prefix#
The prefix comes from the compiled field object’s own key-prefix property.
It is not inferred from the ACF Chef verb.
That means a custom field class with its own prefix can participate correctly in a custom strategy without ACF Chef needing special knowledge of that type.
$ancestry#
$ancestry contains the sanitised path from the field-group key stem down to the field’s parent.
The first entry is the group stem:
$ancestry[0]
The field’s own name is not included in that array. It is passed separately as $name.
For a field at:
hero → content → title
the strategy receives ancestry equivalent to:
['hero', 'content']
and the name:
title
$name#
$name is the field’s compiled name.
Any name transformation that belongs to the verb has already happened.
For example, a tab declared as:
$group->tab('Content');
reaches the key strategy as:
content_tab
not Content.
See Fields and verbs for marker suffixes and field-name derivation.
groupKey() also affects field keys#
The two strategy methods are not independent.
Before compiling the fields, ACF Chef takes the result of groupKey(), removes a leading group_ or field_, sanitises what remains and uses that value as the first ancestry segment for every field underneath the group.
A strategy that returns:
group_acme_hero
therefore gives its fields an ancestry stem of:
acme_hero
If key() also adds acme, the decoration appears twice.
Decorate the namespace in groupKey() or in key(), not both.
The same rule applies when a group receives an explicit key: changing the group key changes the ancestry stem beneath it.
Explicit field keys affect one field#
Set an explicit field key through configuration:
$group->group('content', fn ($content) => $content
->text('title'))
->config([
'key' => 'field_legacy_content',
]);
The container keeps that exact key:
field_legacy_content
Its child still follows the normal strategy path:
field_hero_content_title
A field-level explicit key is a statement about that field only. It does not rename the subtree beneath it.
This is why explicit field keys are useful when one field must retain an identifier created by an older definition or another tool.
Do not call key() inside a deferred config() closure. Use config(['key' => ...]) so the compiler knows about the explicit key before it records its key mapping.
Explicit group keys rename the subtree#
A group-level explicit key behaves differently.
$group = FieldGroup::make('hero');
$group->config([
'key' => 'group_legacy_thing',
]);
$group->text('title');
The group key is:
group_legacy_thing
The compiler derives the ancestry stem legacy_thing from that key, so the child becomes:
field_legacy_thing_title
not:
field_hero_title
An explicit field key changes one field.
An explicit group key changes the namespace used by every field and layout underneath the group.
That distinction matters when preserving keys on an existing site. Changing a group key can move the entire field tree even when none of the field declarations change.
Duplicate keys#
ACF Chef detects collisions between the strategy keys it emits.
If two nodes compile to the same key, it raises DuplicateFieldKey and names the path that collided:
Duplicate field key [field_hero_title] at [title].
Two fields in this group share a name.
The check runs against ACF Chef’s final key format rather than extended-acf’s intermediate hashes.
Explicit field keys are not part of that mapping, so there is one exception: two fields given the same explicit key are not caught by DuplicateFieldKey.
Those reach extended-acf’s own uniqueness check instead:
The key [...] is not unique.
Keys and references are rewritten together#
Compilation finishes with two recursive rewrite passes over the serialised field array.
Both passes descend through:
sub_fields- flexible-content
layouts
The first pass replaces every mapped field and layout key with the value produced by the active strategy.
The second pass updates settings that refer to those keys:
- A repeater’s
collapsedvalue - The
fieldvalue in everyconditional_logicrule
This keeps references aligned with whichever strategy produced the final field keys.
For example:
$group->repeater('rows', fn ($rows) => $rows
->text('title'))
->config([
'collapsed' => 'title',
]);
under the default strategy ends with:
'collapsed' => 'field_hero_rows_title'
Conditional references go through the same mapping.
Anything that is not present in the compiler’s key map is left exactly as written.
That is intentional. An unmapped key may be a deliberate explicit key or a reference to a field owned by another group. Rewriting either would change a value the developer supplied deliberately.
Choose a strategy#
Set the default strategy through Chef:
use AcfChef\Chef;
use AcfChef\KeyStrategy\HashedKeys;
Chef::keyStrategy(new HashedKeys());
Every group without its own override uses that strategy.
For one field group, call keyStrategy() on the group instead:
$group->keyStrategy(new HashedKeys());
The per-group strategy governs the group key and every field and layout key underneath it.
See Field groups for the per-group override and How it works for when keys and references are rewritten during compilation.