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
- Simple field types: text, number, boolean, select
- Image, File, and Gallery fields
- Repeater fields
- Flexible Content fields
- Relationship and Post Object fields
- Writing to options pages and user meta
- Field keys vs field names
- Gotchas that bite
The function signature
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:
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 / checkboxThe 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.
Image, File, and Gallery fields
These accept the WordPress attachment ID regardless of the field's "Return Format" setting:
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 fieldThe 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:
$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:
$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:
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:
$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):
update_field( 'related_posts', [10, 20, 30], $post_id ); // relationship
update_field( 'featured_post', 42, $post_id ); // post objectThe 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':
update_field( 'site_announcement', 'Holiday hours in effect', 'option' );
update_field( 'footer_links', [...], 'option' );For user meta:
update_field( 'bio', 'Senior WordPress developer', 'user_42' );
update_field( 'social_links', [...], 'user_' . $user_id );For taxonomy term meta:
update_field( 'featured_image', 99, 'term_5' );
update_field( 'description_long', '...', 'category_5' ); // works with taxonomy name tooField 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).
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:
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.
- update_field() function reference (Advanced Custom Fields docs)advancedcustomfields.com
- Field keys (Advanced Custom Fields docs)advancedcustomfields.com





