Field keys#
A field key is part of your site’s data.
ACF stores a field’s key alongside every value it saves. ACF uses that key to find the field definition and interpret the stored value.
If the key changes, the definition no longer matches the saved data. The field can appear empty while the old value remains in the database, without reporting an error.
ACF Chef therefore includes key generation in the field-group definition.
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 ignores the prefix supplied by the field object.
This also applies to flexible-content layouts. A layout receives a field_ key under NamespacedKeys:
field_hero_sections
field_hero_sections_media
field_hero_sections_media_picture
This strategy uses field_ for layouts as part of its data format, intentionally replacing the upstream prefix.
You can recognise readable keys 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 hashing it. A readable group key 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 want to keep 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() are described below.
$prefix#
The prefix comes from the compiled field object’s own key-prefix property.
It is not inferred from the ACF Chef verb.
A custom strategy can therefore use a custom field class’s prefix without ACF Chef needing to recognise that field 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.
The verb has already applied any name transformations.
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, it appears twice.
Add the namespace prefix 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
An explicit field key applies only to that field. It does not rename the subtree beneath it.
Use an explicit field key when one field must keep 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.
To preserve keys on an existing site, account for this difference. Changing a group key can change the namespace of the entire field tree even when none of the field declarations change.
Duplicate keys#
ACF Chef checks for duplicate keys produced by the strategy.
If two nodes compile to the same key, it raises DuplicateFieldKey and names the path with the duplicate key:
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. Two fields given the same explicit key therefore do not trigger 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 makes references match the final field keys, whichever strategy produced them.
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.
Values absent from the compiler’s key map stay exactly as written.
An unmapped key may be an explicit key or a reference to a field owned by another group. The compiler preserves these values because the developer supplied them 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 determines 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.