Configuring fields#
Use config() to set field options.
It has two forms:
- Pass an array to merge ACF settings into the field definition.
- Pass a closure to configure the real extended-acf field object at compile time.
The two forms run at different phases, and that difference determines which value wins when they overlap.
Array configuration#
Pass raw ACF settings as an array:
$group->text('title')->config([
'label' => 'Headline',
'required' => true,
'instructions' => 'Keep it short.',
'maxlength' => 60,
]);
Anything that can safely pass through to extended-acf’s withSettings() is retained as supplied.
Repeated calls merge into the same field configuration:
$group->text('title')
->config([
'required' => true,
'instructions' => 'Keep it short.',
])
->config([
'maxlength' => 60,
]);
Later values win when the same setting is configured again.
Associative arrays merge; lists replace#
Nested associative arrays merge key by key:
$group->select('style')
->config([
'choices' => [
'a' => 'A',
],
])
->config([
'choices' => [
'b' => 'B',
],
]);
The resulting choices contain both entries:
[
'a' => 'A',
'b' => 'B',
]
Lists are replaced wholesale:
$group->select('style')
->config([
'choices' => ['a', 'b'],
])
->config([
'choices' => ['c'],
]);
The resulting value is:
['c']
Use associative arrays when later configuration should extend individual keys. Use lists when the later declaration should replace the earlier list.
Reserved settings#
extended-acf rejects eight settings in withSettings(). ACF Chef intercepts them before the remaining settings are passed through.
| Setting | Behaviour |
|---|---|
label |
Used to construct the field |
name |
Used to construct the field |
key |
Applied as an explicit key |
conditional_logic |
Compiled as conditional rules |
collapsed |
Applied through collapsed() |
type |
Discarded |
sub_fields |
Discarded |
layouts |
Discarded |
type is discarded because the field class determines its ACF type.
sub_fields and layouts are discarded because children come from the ACF Chef node tree. Supplying a second child structure through configuration would let the two representations disagree.
Four reserved settings — label, name, key and collapsed — are honoured only when their value is a non-empty string. Invalid values fall back or disappear silently rather than raising an argument error.
conditional_logic is handled separately. It must be an array; a non-array value is silently ignored.
For example:
$group->text('title')->config([
'label' => 123,
]);
does not emit 123 as the label. ACF Chef falls back to the label derived from the field name.
Likewise:
$group->repeater('rows', fn ($rows) => $rows
->text('title'))
->config([
'collapsed' => true,
]);
does not apply a collapsed field because true is not a non-empty string.
Explicit names and labels#
Override the derived label or name through configuration:
$group->text('css_id')->config([
'label' => 'CSS ID',
'name' => 'css_identifier',
]);
The explicit name is used verbatim:
[
'label' => 'CSS ID',
'name' => 'css_identifier',
'type' => 'text',
'key' => 'field_hero_css_identifier',
]
See Fields and verbs for the normal name and label derivation rules.
Explicit keys#
Set a field key through the array form:
$group->text('title')->config([
'key' => 'field_legacy_title',
]);
ACF Chef applies that key before recording its compile-time key mapping, so the explicit key is handled correctly by the rest of compilation.
An explicit field key applies to that field only. It does not change the ancestry used to key its children.
For example, an explicitly keyed container can still have children using their normal strategy keys:
$group->group('content', fn ($content) => $content
->text('title'))
->config([
'key' => 'field_legacy_content',
]);
The keys can therefore be:
field_legacy_content
field_hero_content_title
This differs from an explicit field-group key, which changes the ancestry stem for every field beneath the group. See Field groups.
Do not set an explicit key by calling key() inside a deferred configuration closure.
By then, ACF Chef has already recorded the node’s strategy key. A leaf can appear to work, but a container can cause its children to fall back to raw extended-acf hashes, and the unused strategy key remains claimed for duplicate detection.
Use config(['key' => ...]) for explicit keys.
collapsed#
Use collapsed on a repeater to name the sub-field ACF should display as the row heading:
$group->repeater('rows', fn ($rows) => $rows
->text('title'))
->config([
'collapsed' => 'title',
]);
ACF Chef applies the setting through the repeater’s native collapsed() method, then rewrites the reference to the compiled field key:
'collapsed' => 'field_hero_rows_title'
In extended-acf 15, collapsed() exists only on Repeater.
Using the setting on another field type raises BadMethodCallException rather than emitting something ACF cannot use:
$group->text('title')->config([
'collapsed' => 'subtitle',
]);
The error identifies the field type:
[text] fields do not support the [collapsed] setting.
The wrapper#
ACF’s wrapper setting is written as ACF defines it:
$group->text('title')->config([
'wrapper' => [
'width' => 50,
'class' => 'hero-title',
'id' => 'primary-title',
],
]);
No config key expands into it on its own. A top-level width, class or id stays exactly where you put it, because ACF Chef registers no configuration shorthands of its own.
Because wrapper is an associative array, successive config() calls merge into it rather than replacing it:
$group->text('title')
->config(['wrapper' => ['data-role' => 'heading']])
->config(['wrapper' => ['class' => 'hero-title']]);
Both wrapper entries survive.
width() on a cursor is the one exception in the fluent API. It writes wrapper.width directly, as a real cursor method rather than a config key:
$group->text('title')->width(50);
If you want width, class or id to fold into the wrapper as config keys, register shorthands for them yourself. That is covered in Macros and shorthands.
Deferred configuration#
Pass a closure when you want the native extended-acf field API:
$group->text('title')->config(fn ($field) => $field
->required()
->maxLength(60)
->helperText('Keep it short.'));
The closure is stored during the describe phase.
At compile time ACF Chef constructs the real extended-acf field object, applies the accumulated array configuration, attaches any children, applies collapsed, and only then invokes the deferred closure.
The closure therefore sees the assembled field and can override configuration applied earlier.
Array configuration always runs before closures#
Declaration order does not change the phase order.
This definition writes the closure first and the array second:
$group->text('title')
->config(fn ($field) => $field
->helperText('from closure'))
->config([
'instructions' => 'from array',
]);
It still emits:
'instructions' => 'from closure'
All array configuration is merged while the definition is being described. All deferred closures run later, during compilation.
The closure therefore wins even though the array call appears later in the PHP source.
When you register multiple deferred closures, they run in registration order:
$group->text('title')
->config(fn ($field) => $field
->helperText('first'))
->config(fn ($field) => $field
->helperText('second'));
The later closure sees the result of the earlier one and its final write wins.
A closure may replace the field#
The normal fluent form mutates the field and returns it, or returns nothing:
$group->text('title')->config(fn ($field) => $field
->required());
Returning nothing keeps the field ACF Chef passed in.
If the closure returns another extended-acf Field, ACF Chef uses that replacement for everything downstream.
Use replacement carefully. The key mapping was recorded against the field’s compiled name before deferred closures ran. If the replacement has a different name, it no longer matches that mapping and can serialise with a raw extended-acf hash instead of the active ACF Chef key strategy.
Prefer configuring the field you receive unless replacing it is genuinely necessary.
Deferred closures do not delegate#
The closure receives the actual extended-acf field object, not an ACF Chef cursor.
There is no parent delegation inside it:
$group->text('title')->config(fn ($field) => $field
->requried());
A typo fails on the field object:
Method Extended\ACF\Fields\Text::requried does not exist.
It cannot bubble out to the field set and accidentally become a sibling field.
This makes deferred configuration the strictest part of the fluent API: a misspelled native field method fails where you wrote it.
Conditional rules inside a closure#
conditional() and orWhere() are available inside deferred configuration even though extended-acf does not define those methods itself:
$group->text('caption')->config(fn ($field) => $field
->required()
->conditional('has_image', '==', true));
ACF Chef registers guarded macros with those names on extended-acf’s Field class. If the project has already defined a macro with the same name, ACF Chef leaves it alone.
Rules recorded through ACF Chef’s macros are kept separately in a WeakMap keyed by the field object rather than written into its settings. This allows the same field object to remain usable through fields() without leaving an internal marker in its serialised settings.
The rules are resolved after the deferred closure finishes, through the same compiler path as conditional rules declared on a cursor.
See Conditional logic for grouping, operators, target resolution and value normalisation.