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 extended-acf field object at compile time.
The two forms run in different phases. Their execution order determines which value is used when both set the same option.
Array configuration#
Pass raw ACF settings as an array:
$group->text('title')->config([
'label' => 'Headline',
'required' => true,
'instructions' => 'Keep it short.',
'maxlength' => 60,
]);
Settings that can safely pass through to extended-acf’s withSettings() are kept 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 replace earlier values when you configure the same setting 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',
]
Each new list replaces the previous list:
$group->select('style')
->config([
'choices' => ['a', 'b'],
])
->config([
'choices' => ['c'],
]);
The resulting value is:
['c']
Use associative arrays when later configuration should add or update individual keys. Use lists when the later declaration should replace the earlier list.
Reserved settings#
extended-acf rejects eight settings in withSettings(). ACF Chef handles these settings separately before passing through the rest.
| 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 children through configuration as well would allow that structure to differ from the node tree.
Four reserved settings, label, name, key and collapsed, are used only when their value is a non-empty string. Invalid values use a fallback or are ignored without 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,
]);
This does not output 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,
]);
This 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 exactly as supplied:
[
'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. The rest of compilation therefore uses the explicit key correctly.
An explicit field key applies to that field only. It does not change the ancestry used to key its children.
For example, a container with an explicit key can still have children whose keys come from the normal key strategy:
$group->group('content', fn ($content) => $content
->text('title'))
->config([
'key' => 'field_legacy_content',
]);
The resulting keys can 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. This can appear to work for a leaf field. For a container, it can cause children to fall back to raw extended-acf hashes. The unused strategy key also 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',
],
]);
ACF Chef does not move config keys into the wrapper automatically. A top-level width, class or id stays 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']]);
The wrapper keeps both entries.
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);
To move width, class or id config keys into the wrapper, register shorthands for them. See Macros and shorthands.
Deferred configuration#
Pass a closure to use 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 extended-acf field object, applies the accumulated array configuration, attaches any children, applies collapsed, and then runs the deferred closure.
The closure receives 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 declares the closure first and the array second:
$group->text('title')
->config(fn ($field) => $field
->helperText('from closure'))
->config([
'instructions' => 'from array',
]);
The output is still:
'instructions' => 'from closure'
All array configuration is merged while the definition is being described. All deferred closures run later, during compilation.
The closure’s value is used 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 receives the result of the earlier one. Its changes replace any earlier values for the same settings.
A closure may replace the field#
The normal fluent form changes 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 the rest of compilation.
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.
Configure the field you receive unless you need to replace it.
Deferred closures do not delegate#
The closure receives the actual extended-acf field object, not an ACF Chef cursor.
Method calls inside the closure do not delegate to the parent:
$group->text('title')->config(fn ($field) => $field
->requried());
A typo fails on the field object:
Method Extended\ACF\Fields\Text::requried does not exist.
The call cannot pass to the field set and accidentally create a sibling field.
Deferred configuration is the strictest part of the fluent API because a misspelled native field method fails on the field object.
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 macros with those names on extended-acf’s Field class, unless the project has already defined a macro with the same name. Existing macros are left unchanged.
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.