Fields and verbs#

Each extended-acf 15 field class has an ACF Chef verb. Calling a verb adds a field node to the current container and returns a cursor for the new field.

$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 includes 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 a sorted list of the currently registered verbs:

$verbs = Chef::registry()->verbs();

The command-line equivalent prints the same list of registered verbs:

vendor/bin/acf-chef show: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 in the same way when you pass the verb as data:

$group->add('flexible content', 'sections');

Use the expected casing. For a name without separators, normalisation only lowercases the first character, 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 replaces the derived label.

ACF Chef uses an explicit name exactly as written.

Without an explicit name, ordinary field names are not sanitised:

$group->text('Hero Title');

The ACF field name is:

Hero Title

The key is still sanitised from the field path:

field_hero_hero_title

Choose valid ACF field names yourself. ACF Chef leaves ordinary names as written.

Marker fields receive a suffix#

Three built-in verbs change the derived name:

  • tab
  • accordion
  • message
$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'));

With the suffix, they compile to separate keys:

field_hero_content_tab
field_hero_content
    field_hero_content_title

Saved ACF data refers to these keys, so the keys must be distinct.

For marker names, ACF Chef sanitises the supplied argument before adding the suffix. Ordinary field names use the argument exactly as written.

Setting an explicit name skips sanitisation and the suffix:

$group->tab('Content')->config([
    'name' => 'primary_navigation',
]);

The resulting name is exactly primary_navigation, with no _tab suffix.

You can enable the same behaviour for custom registered verbs:

Chef::registry()->nameSuffix('divider', '_divider');

Containers#

Four built-in verbs can contain children:

  • group
  • repeater
  • flexibleContent
  • layout

Define their children in 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.

Configuration after the closure therefore 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 you pass the verb as 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 contains the trailing arguments for the equivalent verb call.

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');

Static analysers also understand this form without a generated verb stub:

$set->add('acfeColumn', 'column_a');

A dynamic verb call and add() both look up verbs in the registry, but add() does not pass the call along through cursor delegation. An unregistered verb therefore fails immediately.

add() on a cursor means “add inside”#

A verb call and add() behave differently 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 to chain sibling fields. Use add() when you pass the verb as data, or when you want to add a child to the container field a cursor points at.