TechEarl

How to Update ACF Fields Programmatically

update_field() is the canonical way to write ACF data programmatically: the function signature, how to write to Repeater and Flexible Content fields, when to use field keys instead of names, options pages and user meta, and the gotchas that bite.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Canonical guide to update_field(): signature, Repeater and Flexible Content writes, field keys vs names, options pages, user meta, and the gotchas.

update_field() is the canonical way to write ACF data programmatically from PHP. It works for every field type ACF supports, on posts, users, taxonomy terms, options pages, and comments. The function signature is simple but the right value shape varies by field type, and a few gotchas will trip you up on Repeater and Flexible Content writes. After many years of running data-migration scripts against ACF fields on directory sites at scale, here is the canonical reference.

Jump to:

The function signature

php
update_field( $selector, $value, $post_id );
  • $selector (string): the field name OR the field key (field_abc123).
  • $value (mixed): the value to write. Shape depends on the field type.
  • $post_id (mixed): the post ID, or 'option', 'user_42', 'term_5', 'comment_99'.

Returns true on success, false on failure. The function will create the meta entry if it does not exist.

Simple field types: text, number, boolean, select

The straightforward cases:

php
update_field( 'hero_heading', 'Welcome to the site', $post_id );
update_field( 'view_count', 1024, $post_id );
update_field( 'featured', true, $post_id );
update_field( 'priority', 'high', $post_id );      // select field
update_field( 'tags', ['blue', 'green'], $post_id ); // multi-select / checkbox

The text, textarea, and wysiwyg fields all accept strings. Numbers accept numeric values. True/False accepts booleans. Single-select fields accept the option value as a string. Multi-select and checkbox fields accept arrays of option values.

These accept the WordPress attachment ID regardless of the field's "Return Format" setting:

php
update_field( 'hero_image', 42, $post_id );          // image field
update_field( 'brochure_pdf', 99, $post_id );        // file field
update_field( 'gallery', [42, 99, 173, 256], $post_id ); // gallery field

The return format only affects how get_field() hydrates the stored value when reading. Storage is always the attachment ID. See ACF Image Field Returning an Array Instead of a URL? for the read-time return-format details.

Repeater fields

Repeater values are arrays of arrays. The outer array represents rows; each inner array maps sub-field names to values:

php
$team = [
    [
        'name'  => 'Alex Chen',
        'role'  => 'Lead Engineer',
        'photo' => 42, // attachment ID
    ],
    [
        'name'  => 'Sam Patel',
        'role'  => 'Designer',
        'photo' => 99,
    ],
];

update_field( 'team_members', $team, $post_id );

The sub-field names in the inner arrays must match the sub-field names registered in the field group. A typo silently writes nothing for that sub-field.

To append a row to an existing Repeater rather than overwriting:

php
$existing = get_field( 'team_members', $post_id ) ?: [];
$existing[] = [
    'name' => 'Jordan Lee',
    'role' => 'Project Manager',
    'photo' => 173,
];
update_field( 'team_members', $existing, $post_id );

ACF also provides add_row() which appends a single row without the read-then-write dance:

php
add_row( 'team_members', [
    'name' => 'Jordan Lee',
    'role' => 'Project Manager',
    'photo' => 173,
], $post_id );

Flexible Content fields

Flexible Content values are arrays of arrays, with each inner array including an acf_fc_layout key naming the layout:

php
$page_builder = [
    [
        'acf_fc_layout' => 'hero',
        'heading' => 'About our work',
        'subheading' => 'Custom WordPress development since 2011',
    ],
    [
        'acf_fc_layout' => 'cta',
        'heading' => 'Start a project',
        'button_label' => 'Get in touch',
        'button_url' => '/contact',
    ],
    [
        'acf_fc_layout' => 'stat_row',
        'stats' => [
            ['label' => 'Sites shipped', 'value' => '120', 'unit' => '+'],
            ['label' => 'Years operating', 'value' => '15', 'unit' => ''],
        ],
    ],
];

update_field( 'page_builder', $page_builder, $post_id );

The acf_fc_layout value must match a layout name registered in the field group. The sub-field names inside each layout block must match that layout's sub-field registration. Nested Repeater data (like the stats array inside stat_row) follows the same array-of-arrays structure.

Relationship and Post Object fields

These accept arrays of post IDs (for Relationship) or a single post ID (for Post Object):

php
update_field( 'related_posts', [10, 20, 30], $post_id );    // relationship
update_field( 'featured_post', 42, $post_id );              // post object

The order of IDs in the array is preserved as the relationship order. For deep coverage of relationship-field write gotchas, see Why ACF Relationship Fields Lose Their Selected Order.

Writing to options pages and user meta

The third argument changes context. For options pages, pass 'option':

php
update_field( 'site_announcement', 'Holiday hours in effect', 'option' );
update_field( 'footer_links', [...], 'option' );

For user meta:

php
update_field( 'bio', 'Senior WordPress developer', 'user_42' );
update_field( 'social_links', [...], 'user_' . $user_id );

For taxonomy term meta:

php
update_field( 'featured_image', 99, 'term_5' );
update_field( 'description_long', '...', 'category_5' ); // works with taxonomy name too

Field keys vs field names

update_field() accepts either the field name (hero_heading) or the field key (field_abc123def456). The field key is preferred for code that runs before ACF has fully initialized (very early hooks, REST API callbacks) because it does not require ACF to do the name-to-key lookup.

To find a field's key: export the field group from Custom Fields > Tools > Export Field Groups, or inspect the field in the admin UI (the key shows under the field name in edit mode).

php
update_field( 'field_abc123def456', 'New heading', $post_id );

For most application code running during normal WordPress requests, the field name is fine. For mu-plugin code that runs very early, use the key. The full reference is in ACF's Field Keys documentation.

Gotchas that bite

The function exists check. If ACF is deactivated or not yet loaded, update_field() does not exist. Code that runs unconditionally on init or earlier should guard:

php
if ( function_exists( 'update_field' ) ) {
    update_field( 'foo', 'bar', $post_id );
}

Object cache invalidation. If your site uses a persistent object cache, the meta value written by update_field() may not appear in subsequent get_field() calls in the same request without explicit cache invalidation. ACF handles this internally in most cases, but custom caching layers in front of wp_postmeta may not.

The Flexible Content layout name must exist. If you write a Flexible Content row with acf_fc_layout set to a layout name that does not exist in the field group registration, the row writes but renders as nothing because the template loop will not match it.

Repeater sub-field key collisions. Two sub-fields in the same Repeater cannot have the same name. Even across nested Repeaters in different layouts, ACF will store data correctly but get_sub_field() reads in the inner context can become ambiguous if names overlap.

Save hooks fire on programmatic writes. update_field() triggers acf/save_post actions. If you have a hook that does expensive work on save, calling update_field in a tight loop can be slow. For bulk imports, consider update_post_meta() directly (which bypasses ACF's hook layer) and only call update_field when the ACF-specific behavior is needed.

For agency-scale ACF migration patterns (especially when paired with WP-CLI), see Using AI with WP-CLI for Faster WordPress Operations. For safe acf/save_post patterns, see Useful Things You Can Do with acf/save_post.

Sources

Authoritative references this article was fact-checked against.

TagsWordPressACFWP-CLIMigrations

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Populate ACF Fields from Gravity Forms

Gravity Forms can create WordPress posts on submission, but mapping form fields to ACF fields requires a hook. The canonical pattern uses gform_after_submission to call update_field. Plus the dedicated Gravity Forms + ACF plugin path.

Using AI to Update ACF Fields and WordPress Content

AI plus WP-CLI plus ACF is the canonical pattern for bulk content updates that used to take a careful afternoon. Schema-aware update_field calls, content rewrites at scale, image alt backfills, and the safety patterns that prevent disasters.

How to Write an Effective System Prompt

Write an effective system prompt for an LLM with five parts: role, capabilities, constraints, output format, refusal policy. With before/after examples and the structure that maximises cache hits.