Advanced Custom Fields: Getting Started

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.