acf-field-group-development
Builds and reviews Advanced Custom Fields field groups defined in PHP or Local JSON. Covers stable group and field keys, storage names, acf/init timing, location rules, REST exposure, plugin-owned JSO
SKILL.mdacf/acf-field-group-development/SKILL.md
GitHubACF field group development
Treat an ACF field definition as a versioned data contract. The field key is ACF's identity, the field name becomes the storage/API name, the field type determines raw and formatted value shapes, and location rules decide where the editing UI appears. Changing any of these can be a data migration even when the admin screen still looks correct.
Choose the ownership model
| Model | Use it when | Important behavior |
|---|---|---|
PHP with acf_add_local_field_group() |
A plugin owns the schema and deployments must be deterministic | Definitions are not editable in the normal Field Groups UI. |
| Local JSON | Site builders edit in ACF UI and the result must be version controlled and synchronized | JSON and database copies can diverge; review and sync deliberately. |
| Database only | A site owner intentionally manages the schema in one environment | Poor fit for reusable plugins and automated deployments. |
Do not register the same group independently in PHP and JSON. Pick one canonical owner, or make the precedence and sync workflow explicit.
Register a plugin-owned group
Register on acf/init. Do not wrap registration in is_admin(): frontend calls
to get_field() need the same definitions for field lookup and formatting.
add_action( 'acf/init', static function (): void {
if ( ! function_exists( 'acf_add_local_field_group' ) ) {
return;
}
acf_add_local_field_group( array(
'key' => 'group_acme_event_details',
'title' => __( 'Event details', 'acme-events' ),
'fields' => array(
array(
'key' => 'field_acme_event_start',
'label' => __( 'Start time', 'acme-events' ),
'name' => 'acme_event_start',
'type' => 'date_time_picker',
'display_format' => 'Y-m-d H:i',
'return_format' => 'Y-m-d H:i:s',
'required' => 1,
),
array(
'key' => 'field_acme_event_summary',
'label' => __( 'Summary', 'acme-events' ),
'name' => 'acme_event_summary',
'type' => 'textarea',
'rows' => 4,
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'acme_event',
),
),
),
'show_in_rest' => 1,
) );
} );
Use a stable vendor/plugin prefix in every key and field name. Never generate keys at runtime, translate them, or include an environment name. Labels and instructions are display text and should be translated; keys and names are not.
Preserve identity and storage
- Every
group_*andfield_*key must be globally unique and stable. - A field name is a database/API contract. Renaming it does not migrate existing
values or the hidden
_field_namereference row. - Reusing one key for a different field makes the later local definition replace the earlier definition in ACF's local store.
- Reordering fields and changing labels is usually presentation-only. Changing type, return format, parent structure, or name needs a migration and read-path regression test.
- Use field keys with
update_field()when creating a value for the first time, especially during imports. This lets ACF create the reference row needed to resolve the type and format later reads.
Local JSON inside a plugin
The default acf-json folder belongs to the active theme. A reusable plugin must
add its own load path and, if it owns authoring, its own save path. Keep the path
inside the plugin and version the JSON files.
add_filter( 'acf/settings/load_json', static function ( array $paths ): array {
$paths[] = __DIR__ . '/acf-json';
return array_values( array_unique( $paths ) );
} );
add_filter( 'acf/settings/save_json', static function ( string $path ): string {
return __DIR__ . '/acf-json';
} );
Only add the save filter when editors are expected to author this plugin's schema. Otherwise ship reviewed JSON as read-only definitions. A custom save path is not automatically a load path; configure both sides.
Free and PRO boundary
The local-field API, Local JSON, location rules, REST setting, and custom field type API are available in Free and PRO 6.8.10. The following definitions require ACF PRO:
repeaterflexible_contentgalleryclone- Options Pages and ACF Blocks
Feature-detect the exact dependency before registering a PRO-only schema. For an optional enhancement, omit or replace that field deliberately. For a required field, fail visibly in admin instead of silently registering an unusable type.
Review checklist
- Identify who owns the schema: PHP, Local JSON, or database.
- Confirm every key is unique and stable and every name is intentionally namespaced.
- Verify registration occurs before any field read, normally on
acf/init. - Check the location rule against the actual post type, taxonomy, user, options page, block, or other object.
- Record raw and formatted value shapes for every field used by code.
- Mark each PRO-only field or feature in code and documentation.
- Test a fresh object, an object with existing data, REST if enabled, and the frontend read path.
Cross-references
acf-value-storage-frontendfor database rows, writes, reads, and rendering.acf-pro-complex-fieldsfor Repeater, Flexible Content, Gallery, and Clone.acf-custom-field-typewhen the required editor/value contract cannot be expressed with a built-in field.
References
- Official PHP field registration guide.
- Official Local JSON guide.
- Official field group guide.
- Official ACF settings reference.
- Verified ACF 6.8.10 source paths:
includes/local-fields.phpincludes/local-json.phpincludes/locations.phpincludes/post-types/class-acf-field-group.php