Fields and verbs#
Every extended-acf 15 field class has a corresponding ACF Chef verb. A verb adds a field node to the current container and returns a cursor for the field it created.
$group
->text('title')
->image('hero_image')
->trueFalse('featured');
The first argument is the field name. Configure everything else with config().
Built-in verbs#
ACF Chef ships 36 verbs:
| Verb | extended-acf class | Container |
|---|---|---|
accordion |
Extended\ACF\Fields\Accordion |
|
buttonGroup |
Extended\ACF\Fields\ButtonGroup |
|
checkbox |
Extended\ACF\Fields\Checkbox |
|
colorPicker |
Extended\ACF\Fields\ColorPicker |
|
datePicker |
Extended\ACF\Fields\DatePicker |
|
dateTimePicker |
Extended\ACF\Fields\DateTimePicker |
|
email |
Extended\ACF\Fields\Email |
|
file |
Extended\ACF\Fields\File |
|
flexibleContent |
Extended\ACF\Fields\FlexibleContent |
yes |
gallery |
Extended\ACF\Fields\Gallery |
|
googleMap |
Extended\ACF\Fields\GoogleMap |
|
group |
Extended\ACF\Fields\Group |
yes |
iconPicker |
Extended\ACF\Fields\IconPicker |
|
image |
Extended\ACF\Fields\Image |
|
layout |
Extended\ACF\Fields\Layout |
yes |
link |
Extended\ACF\Fields\Link |
|
message |
Extended\ACF\Fields\Message |
|
number |
Extended\ACF\Fields\Number |
|
oembed |
Extended\ACF\Fields\Oembed |
|
pageLink |
Extended\ACF\Fields\PageLink |
|
password |
Extended\ACF\Fields\Password |
|
postObject |
Extended\ACF\Fields\PostObject |
|
radioButton |
Extended\ACF\Fields\RadioButton |
|
range |
Extended\ACF\Fields\Range |
|
relationship |
Extended\ACF\Fields\Relationship |
|
repeater |
Extended\ACF\Fields\Repeater |
yes |
select |
Extended\ACF\Fields\Select |
|
tab |
Extended\ACF\Fields\Tab |
|
taxonomy |
Extended\ACF\Fields\Taxonomy |
|
text |
Extended\ACF\Fields\Text |
|
textarea |
Extended\ACF\Fields\Textarea |
|
timePicker |
Extended\ACF\Fields\TimePicker |
|
trueFalse |
Extended\ACF\Fields\TrueFalse |
|
url |
Extended\ACF\Fields\URL |
|
user |
Extended\ACF\Fields\User |
|
wysiwyg |
Extended\ACF\Fields\WYSIWYGEditor |
Chef::registry()->verbs() returns the live registered list, sorted:
$verbs = Chef::registry()->verbs();
The command-line equivalent prints the same live registry:
vendor/bin/acf-chef verbs
That list includes field types your project has registered in addition to the 36 built-ins.
Verb spelling#
Verb names are normalised to camel case before lookup.
Use camelCase, snake_case or kebab-case:
$group->flexibleContent('sections');
$group->flexible_content('sections');
$group->add('flexible-content', 'sections');
All three resolve to flexibleContent.
Names with spaces are normalised through the same path when the verb is supplied as data:
$group->add('flexible content', 'sections');
Do not rely on arbitrary casing. Normalisation only lowercases the first character of an already unseparated name, so URL becomes uRL, not the registered url.
Use url.
Field names and labels#
For an ordinary field, ACF Chef uses the argument exactly as written for the field name:
$group->text('hero_title');
This produces the name hero_title and derives the label Hero Title.
Override either value through configuration:
$group->text('hero_title')->config([
'name' => 'headline',
'label' => 'Main heading',
]);
A non-empty string label wins over the derived label.
An explicit name is used verbatim.
Without an explicit name, ordinary field names are not sanitised:
$group->text('Hero Title');
The ACF field name is literally:
Hero Title
The key is still sanitised from the field path:
field_hero_hero_title
Choose valid ACF field names yourself. ACF Chef does not silently rewrite ordinary names.
Marker fields receive a suffix#
Three built-in verbs alter a derived name:
tabaccordionmessage
$group
->tab('Content')
->accordion('Advanced')
->message('Note');
Their names become:
content_tab
advanced_accordion
note_message
The suffix prevents key collisions.
For example, without it these two declarations would both derive the same path:
$group
->tab('Content')
->group('content', fn ($content) => $content
->text('title'));
Instead they compile separately:
field_hero_content_tab
field_hero_content
field_hero_content_title
The distinction matters because the key is what saved ACF data refers to.
Marker names are the exception to the ordinary “use the argument verbatim” rule. ACF Chef sanitises the supplied argument before adding the suffix.
An explicit name suppresses that behaviour:
$group->tab('Content')->config([
'name' => 'primary_navigation',
]);
The emitted name is exactly primary_navigation, with no _tab suffix.
Custom registered verbs can opt into the same behaviour:
Chef::registry()->nameSuffix('divider', '_divider');
Containers#
Exactly four built-in verbs can contain children:
grouprepeaterflexibleContentlayout
They accept their child definition as a closure:
$group->group('content', fn ($content) => $content
->text('title')
->textarea('summary'));
A container verb always returns a cursor for the container itself, never the cursor returned by the last child inside its closure.
That is why configuration after the closure applies to the container:
$group->group('content', fn ($content) => $content
->text('title'))
->config(['required' => true]);
See Nesting and chaining for container scope and cursor delegation.
Tabs and accordions are not containers#
tab and accordion affect presentation in the ACF editor, but they do not contain the fields that visually follow them.
ACF stores them as markers in the same flat field list as their neighbouring fields. Allowing a closure beneath one would produce a nested structure that ACF cannot read back.
Write fields after the marker instead:
$group
->tab('Content')
->text('title')
->textarea('summary')
->accordion('Advanced')
->text('internal_note');
Add a verb by name#
Use add() when the verb itself is data:
$type = 'text';
$group->add($type, 'title');
Its signature is:
add(string $verb, string $name, array $arguments = [])
The third argument is not field configuration. It is the list of trailing arguments the equivalent verb call would have received.
Configure the returned cursor exactly as you would after a normal verb call:
$group->add('text', 'title')
->config(['required' => true]);
Do not put field settings in the third argument:
$group->add('text', 'title', ['required' => true]);
That array is treated as verb arguments. Because it has no value at position 0, required is silently ignored.
For a container, put its closure or Recipe at position 0:
$group->add('group', 'content', [
fn ($content) => $content->text('title'),
])->config(['width' => 50]);
That is the dynamic equivalent of:
$group->group('content', fn ($content) => $content
->text('title'))
->config(['width' => 50]);
If position 0 is present but is not a closure or Recipe, the field rejects it as an invalid nesting argument.
add() is useful when the type is chosen at run time:
$type = $multiline ? 'textarea' : 'text';
$group->add($type, 'description');
It is also the form static analysers understand without a generated verb stub:
$set->add('acfeColumn', 'column_a');
A dynamic verb call and add() both resolve through the registry, but add() does not escalate through cursor delegation. An unregistered verb therefore fails immediately.
add() on a cursor means “add inside”#
There is one important asymmetry between a verb call and add() when you already have a cursor.
On a normal chaining cursor, another verb delegates to the parent container and adds a sibling:
$group
->text('title')
->textarea('summary');
summary is a sibling of title.
add() is a real cursor method. On a cursor, it means “add inside the field this cursor points at”:
$cursor->add('text', 'title');
That requires the cursor to point at a container field.
Use normal verb calls for fluent sibling chaining. Use add() when the verb is data, or when you deliberately want to add inside a container cursor.