Skip to main content

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_idpost_idmeta_keymeta_valueNote
10115content_with_repated_images2Count: 2 Rows total
10215_content_with_repated_imagesfield_64ac123...Reference key
10315content_with_repated_images_0_content"Hello World"Row 1 Content
10415_content_with_repated_images_0_contentfield_64ac456...Ref Content
10515content_with_repated_images_0_image24Row 1 Image ID
10615_content_with_repated_images_0_imagefield_64ac789...Ref Image
10715content_with_repated_images_1_content"Second Item"Row 2 Content

2. How to Create/Populate These Fields Programmatically​

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;