Finding and editing fields#

ACF Chef lets you find, change and remove fields after they have been declared.

These operations work on the node tree during the describe phase. Nothing has been compiled yet, so they are ordinary lookups and mutations rather than rewrites of finished ACF arrays.

The same API is available on a FieldGroup, a FieldSet and any container cursor.

Find a field#

Use find() to retrieve a cursor for a field:

$title = $group->find('content.title');

The returned cursor points at the existing node.

It does not delegate to the surrounding container. That keeps later operations scoped to the field you deliberately addressed.

Modify a field#

Pass an array to modify() to merge configuration into the target:

$group->modify('content.title', [
    'required' => true,
    'wrapper' => ['width' => 50],
]);

The array goes through the same configuration path as calling config() on the field cursor.

That means any configuration shorthands your application registered apply here too.

modify() returns the container it was called on, so edits can chain:

$group
    ->modify('content.title', ['required' => true])
    ->modify('content.summary', ['rows' => 4]);

Modify with a closure#

Pass a closure when you need the target cursor itself:

$group->modify('content.title', function ($title) {
    $title->config([
        'label' => 'Headline',
        'required' => true,
    ]);
});

The cursor passed to the closure has delegation disabled.

The closure's return value is discarded. Mutate the cursor you receive; there is no reason to return it.

Remove a field#

Use remove() to delete a node from its container:

$group->remove('content.subtitle');

Like modify(), remove() returns the container it was called on:

$group
    ->remove('content.subtitle')
    ->remove('content.kicker');

Removing a path that does not resolve is an error. ACF Chef does not silently ignore it.

A silent no-op would let a shared definition change underneath consuming code without telling you that the field you expected to remove no longer exists.

Path syntax#

Use . between path segments:

content.media.picture

-> is accepted as an alias:

content->media->picture

Both address the same node.

Segments are trimmed and empty segments are discarded before lookup.

Names are matched in two passes#

At each level, ACF Chef first compares the requested segment against every child’s declared name.

Only if no exact declared-name match exists does it compare the sanitised forms.

That allows:

$group->text('Media Items');

$group->find('media_items');

to resolve the field declared as Media Items.

The exact-name pass comes first so sanitisation does not override an explicitly declared match.

Layouts use ordinary path segments#

Flexible-content layouts participate in paths like any other field.

Given:

$group->flexibleContent('sections', fn ($sections) => $sections
    ->layout('media', fn ($media) => $media
        ->image('picture')));

you can address the image as:

$group->find('sections.media.picture');

There is no separate layout syntax.

Marker suffixes are not part of paths#

Path lookup uses the field’s declared name, not the compiled marker suffix.

For example:

$group->tab('Content');

compiles with the ACF field name:

content_tab

but the path remains the declared name.

These resolve:

$group->find('Content');
$group->find('content');

This does not:

$group->find('content_tab');

Conditional target resolution is deliberately different: it accepts either the declared marker name or its compiled suffixed name.

See Conditional logic for that lookup behaviour.

Add fields to a container you found#

Because a cursor returned by find() or modify() does not delegate, a field verb on a container cursor means “add inside this field”:

$content = $group->find('content');

$content->text('kicker');

kicker becomes a child of content.

The same makes this concise:

$group->modify('content', fn ($content) => $content
    ->text('kicker'));

You can also use add():

$group->modify('content', fn ($content) => $content
    ->add('text', 'kicker'));

or use():

$group->modify('content', fn ($content) => $content
    ->use(fn ($fields) => $fields
        ->text('kicker')
        ->text('eyebrow')));

All three stay inside the addressed container.

Leaf fields cannot accept children#

The same non-delegating behaviour prevents a field verb from escaping a leaf cursor and becoming a sibling unexpectedly.

If title is a text field:

$title = $group->find('title');

$title->textarea('summary');

the call raises BadMethodCallException rather than adding summary beside title. The message explains that delegation is disabled.

Calling add() or use() on the same leaf fails more directly:

The [text] field [title] is not a container, so it cannot hold fields.

Targeted edits therefore stay targeted.

Path failures explain where resolution stopped#

An unresolved path raises UnresolvedPath.

If the current container has fields, the error names the missing segment and lists what was available:

Unresolved path [content.nope]: no field named [nope]. Available here: [title], [media], [kicker].

If the container is empty:

Unresolved path [content.title]: no field named [title]. That container has no fields.

If the path attempts to descend through a leaf field:

Unresolved path [content.tagline.nope]: [tagline] is a [text] field, which cannot contain other fields.

The error reports the segment where resolution failed rather than treating the whole path as an opaque string.

find(), modify() and remove() all use the same resolver, so the same diagnostics apply to each operation.