Advanced Custom Fields (ACF) is one of the most popular WordPress plugins — it lets you add structured data fields to posts, pages, users, taxonomy terms, and options pages through a graphical interface, then retrieve those values in templates with a clean API.
Problem: WordPress post meta is powerful but the built-in admin UI only provides a basic text input — there is no native way to add date pickers, image selectors, relationship fields, or repeater rows to an edit screen.
Solution: Use Advanced Custom Fields (ACF) to register field groups through the admin UI or PHP, then retrieve values with get_field() in templates. Field groups can be conditionally displayed per post type, page template, taxonomy term, or user role.
Install ACF via WP-CLI:
wp plugin install advanced-custom-fields --activate
After creating a field group in Custom Fields → Add New, retrieve values in templates:
// Basic field — returns the raw value
$subtitle = get_field( 'subtitle' );
// Output directly (wraps get_field + echo)
the_field( 'subtitle' );
// Image field — returns an array with url, width, height, alt, sizes
$image = get_field( 'hero_image' );
if ( $image ) {
echo '<img src="' . esc_url( $image['url'] ) . '"'
. ' alt="' . esc_attr( $image['alt'] ) . '"'
. ' width="' . esc_attr( $image['width'] ) . '"'
. ' height="' . esc_attr( $image['height'] ) . '">';
}
// Relationship field — returns an array of WP_Post objects
$related = get_field( 'related_posts' );
if ( $related ) {
foreach ( $related as $post ) {
echo '<a href="' . esc_url( get_permalink( $post ) ) . '">'
. esc_html( $post->post_title ) . '</a>';
}
}
// Repeater field — loops over rows of sub-fields
if ( have_rows( 'faq_items' ) ) {
while ( have_rows( 'faq_items' ) ) {
the_row();
$question = get_sub_field( 'question' );
$answer = get_sub_field( 'answer' );
echo "<dt>$question</dt><dd>$answer</dd>";
}
}
Register field groups in code (keeps them in version control and avoids the database dependency):
add_action( 'acf/init', function() {
acf_add_local_field_group( [
'key' => 'group_hero',
'title' => 'Hero Section',
'fields' => [
[
'key' => 'field_hero_subtitle',
'label' => 'Subtitle',
'name' => 'subtitle',
'type' => 'text',
],
],
'location' => [ [ [ 'param' => 'post_type', 'operator' => '==', 'value' => 'page' ] ] ],
] );
} );
NOTE: Use acf_add_local_field_group() to define field groups in code for any production theme or plugin — it keeps configuration in Git and avoids the extra database overhead of storing field group metadata in wp_posts.