Registering ACF field groups in PHP
Advanced Custom Fields (ACF) lets you add structured data — a subtitle, a price, a gallery, a list of team members — to posts, pages, terms, users, options pages and blocks. Field groups are usually built in the admin UI and stored in the database, but for themes and plugins you ship to clients it is far better to register them in code with acf_add_local_field_group(): the fields are version-controlled, deploy with your code, cannot be broken by an editor, and load without a database query.
Writing those arrays by hand is tedious and error-prone. Every field needs a unique key, a name, the right type-specific settings and correctly nested location rules, and repeaters need parent_repeater references. This builder gives you the ACF editing experience in the browser and outputs clean PHP in the same shape ACF’s own exporter produces, hooked to acf/include_fields (ACF 6+) with a function_exists() guard so your site never fatals when ACF is deactivated.
A second panel writes the template code for every field: get_field() with the correct escaping for its type (esc_html(), esc_url(), wp_kses_post(), wp_get_attachment_image()), have_rows() loops for repeaters and array access for groups — ready to paste into single.php or a block template.
How to use the WordPress ACF Field Generator
- Click Load sample to explore a complete group, or Start blank. Give the group a title and set location rules — rules in one box are AND, separate boxes are OR.
- Add fields, choose their type and expand each card to set the label, name, instructions and type-specific options such as choices, return format or post-type filters.
- Use the arrows to reorder fields, and add sub fields to Group and Repeater fields. Errors such as duplicate names are highlighted in red.
- Copy the PHP into your theme’s
functions.php, a plugin or aninc/acf-fields.phpfile that you include. - Copy the template usage code into your template and adjust the markup.
Example
The generated structure for a single text field (keys are deterministic hashes):
add_action( 'acf/include_fields', function () {
if ( ! function_exists( 'acf_add_local_field_group' ) ) {
return;
}
acf_add_local_field_group( array(
'key' => 'group_7c1f0a3e9b2d4',
'title' => 'Book Details',
'fields' => array(
array(
'key' => 'field_3e8a91c07f5b2',
'label' => 'Subtitle',
'name' => 'subtitle',
'type' => 'text',
),
),
'location' => array( array( array( 'param' => 'post_type', 'operator' => '==', 'value' => 'book' ) ) ),
) );
} );Things worth knowing
- Keys must be unique site-wide and must not change once content exists if you rely on them. This tool derives them from a hash of the group title and field path — not random — so regenerating gives the same keys.
- Field names are what you query. Values are saved as post meta under the name (and a hidden
_namemeta pointing to the key). Keep names short, snake_case and unique within their level. - Return formats matter. An Image field returning an array gives you
ID,url,altand sizes; returning an ID lets you usewp_get_attachment_image()for responsivesrcsetmarkup. - Repeater and Gallery fields require ACF PRO; the free plugin ignores them.
- Prefer the JSON workflow for teams that edit fields in the admin? Use the ACF JSON Generator and read the ACF field groups guide.
Where these fields usually live
ACF groups are typically attached to a custom post type from the CPT Generator or to terms of a custom taxonomy from the Taxonomy Generator. To output fields inside post content, wrap the template code in a shortcode.
Frequently Asked Questions
Can I still edit fields registered in PHP from the ACF admin?
No. Local field groups appear under the field-group list but are read-only, because PHP is the source of truth. To edit them in the UI, import the JSON version instead or change the code.
Why acf/include_fields instead of acf/init?
ACF 6 introduced acf/include_fields as the dedicated hook for registering local fields, and its own PHP export uses it. acf/init still works on older versions.
Do I need to escape get_field() output?
Yes. get_field() returns raw values, so escape at output: esc_html() for text, esc_url() for URLs, esc_attr() in attributes and wp_kses_post() for WYSIWYG content. Since ACF 6.2.5 the_field() escapes unsafe HTML automatically, but explicit escaping is clearer and works everywhere.
How do I read fields from an options page or a term?
Pass the object as the second argument: get_field( 'logo', 'option' ) for options pages, get_field( 'color', 'term_12' ) or get_field( 'color', $term ) for terms, and get_field( 'bio', 'user_3' ) for users.
What happens if two field groups use the same field name?
They share the same meta key and overwrite each other on save. Only use the same name in two groups when they never appear on the same screen.