ACF Repeater Fields: Storage and Programmatic Creation
1. How ACF Repeater Fields are Stored in the Database
ACF (Advanced Custom Fields) Repeater fields are stored in the wp_postmeta table using a specific convention that "flattens" the repeated data into individual meta rows.
Referencing a Repeater field named "Content + Image Repeater" (machine name/slug is content_with_repated_images and it has subfields content and image), here is how it is stored in the database:
A. The Main Row Count
First, ACF stores the total number of rows in the repeater as a single integer value under the main field name.
- meta_key:
content_with_repated_images - meta_value:
3(for example, if you have 3 rows of data)
B. The Data Rows (Subfields)
Each subfield in every row is stored as its own separate entry in wp_postmeta, indexed by the row number (starting at 0).
Row 0:
content_with_repated_images_0_content: "This is the text for the first item..."content_with_repated_images_0_image:245(The Attachment ID of the image)
Row 1:
content_with_repated_images_1_content: "This is the text for the second item..."content_with_repated_images_1_image:246
C. The Reference Keys (Hidden)
For every visible data key, ACF stores a corresponding "reference key" starting with an underscore _. This key holds the unique Field Key (e.g., field_5d123456...) which ACF uses to look up the field's settings (type, label, formatting rules).
_content_with_repated_images:field_XXXXXXX(Key for the parent repeater)_content_with_repated_images_0_content:field_YYYYYYY(Key for the content subfield)_content_with_repated_images_0_image:field_ZZZZZZZ(Key for the image subfield)
Visual Summary
| meta_id | post_id | meta_key | meta_value | Note |
|---|---|---|---|---|
| 101 | 15 | content_with_repated_images | 2 | Count: 2 Rows total |
| 102 | 15 | _content_with_repated_images | field_64ac123... | Reference key |
| 103 | 15 | content_with_repated_images_0_content | "Hello World" | Row 1 Content |
| 104 | 15 | _content_with_repated_images_0_content | field_64ac456... | Ref Content |
| 105 | 15 | content_with_repated_images_0_image | 24 | Row 1 Image ID |
| 106 | 15 | _content_with_repated_images_0_image | field_64ac789... | Ref Image |
| 107 | 15 | content_with_repated_images_1_content | "Second Item" | Row 2 Content |
2. How to Create/Populate These Fields Programmatically
Option 1: Using ACF Functions (Recommended)
If the ACF plugin is active, using update_field() is the safest and easiest method. It handles all the database serialization and reference keys for you.
You need the Field Key (e.g., field_123456...) of the repeater to ensure it creates the structure correctly if it's empty. You can find this in the ACF Field Group editor or by inspecting a post that already has data.
<?php
// Define the data you want to insert
$repeater_data = array(
array(
// 'subfield_name' => 'Value'
'content' => 'First paragraph text here...',
'image' => 123, // Image Attachment ID
),
array(
'content' => 'Second paragraph text here...',
'image' => 456, // Different Attachment ID
),
);
// Update valid Post ID
$post_id = 101;
// "content_with_repated_images" is the Field Name (slug)
// If this is the FIRST time you are adding data to this post,
// using the Field Key (field_XXXXXX) is safer than the slug.
$field_key = 'field_5f7b123456789'; // Example Key for the Repeater
update_field( $field_key, $repeater_data, $post_id );
?>
Option 2: Using WordPress Native Functions (Performance / No ACF)
If you need to bulk import thousands of items and update_field is too slow, or if ACF isn't active in your importer context, you must manually construct the database rows as described in the previous section.
Crucial Step: You must know the Field Keys (strings starting with field_) for the Repeater itself and each Subfield.
<?php
$post_id = 101;
$repeater_name = 'content_with_repated_images';
// --- Configuration (Get these from ACF UI -> Tools -> Export code) ---
$keys = array(
'repeater' => 'field_5f00000000001', // Key for the Repeater
'content' => 'field_5f00000000002', // Key for Content Subfield
'image' => 'field_5f00000000003', // Key for Image Subfield
);
// Data to insert
$rows = array(
array( 'content' => 'Text A', 'image' => 10 ),
array( 'content' => 'Text B', 'image' => 20 ),
);
// 1. Update the Main Count and Reference Key
update_post_meta( $post_id, $repeater_name, count($rows) );
update_post_meta( $post_id, '_' . $repeater_name, $keys['repeater'] );
// 2. Loop through rows and add flattened data
foreach ( $rows as $index => $row ) {
// Content Subfield
$content_key = "{$repeater_name}_{$index}_content";
update_post_meta( $post_id, $content_key, $row['content'] );
update_post_meta( $post_id, '_' . $content_key, $keys['content'] );
// Image Subfield
$image_key = "{$repeater_name}_{$index}_image";
update_post_meta( $post_id, $image_key, $row['image'] );
update_post_meta( $post_id, '_' . $image_key, $keys['image'] );
}
?>
How to Register the Field Group Programmatically
If you need to create the actual field definition (not just the data), use acf_add_local_field_group.
if( function_exists('acf_add_local_field_group') ):
acf_add_local_field_group(array(
'key' => 'group_content_repeater',
'title' => 'Article Content',
'fields' => array(
array(
'key' => 'field_5f00000000001',
'label' => 'Content + Image Repeater',
'name' => 'content_with_repated_images',
'type' => 'repeater',
'sub_fields' => array(
array(
'key' => 'field_5f00000000002',
'label' => 'Content',
'name' => 'content',
'type' => 'wysiwyg',
),
array(
'key' => 'field_5f00000000003',
'label' => 'Image',
'name' => 'image',
'type' => 'image',
'return_format' => 'id',
),
),
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'post',
),
),
),
));
endif;