Skip to content

Repeater Field

A dynamic repeatable row builder for collecting structured multi-item data.

Overview

The Repeater field renders a list of rows where each row contains a defined set of sub-fields. The user can add, remove, and reorder rows. It returns an array of associative arrays — one per row — each containing the values of its sub-fields. Use it for team members, testimonials, pricing table rows, FAQ items, feature lists, custom menu items, or any other structured repeatable content in your theme.


Field Registration

php

[
    'id'      => 'team_members',
    'type'    => 'repeater',
    'title'   => __('Team Members', 'your-textdomain'),
    'subtitle' => __('Add team members to display on the About page', 'your-textdomain'),
    'fields'  => [
        [
            'id'    => 'name',
            'type'  => 'text',
            'title' => __('Name', 'your-textdomain'),
        ],
        [
            'id'    => 'role',
            'type'  => 'text',
            'title' => __('Role', 'your-textdomain'),
        ],
        [
            'id'    => 'photo',
            'type'  => 'image',
            'title' => __('Photo', 'your-textdomain'),
        ],
        [
            'id'    => 'bio',
            'type'  => 'textarea',
            'title' => __('Bio', 'your-textdomain'),
        ],
    ],
]

Field Options

OptionTypeRequiredDescription
idstringUnique field identifier — used as the option key
typestringMust be repeater
titlestringLabel shown above the repeater
subtitlestringSmaller descriptive text shown below the label
descstringHelp text shown below the repeater
fieldsarrayArray of sub-field definitions — same structure as regular fields, but without section-level keys
defaultarrayArray of default row data — each item is an associative array matching the sub-field IDs
requiredarrayConditional logic rules — see Conditional Logic

Supported Sub-field Types

Most standard field types are supported as sub-fields inside a Repeater row:

text, textarea, number, slider, select, button_set, radio, checkbox, toggle, color, image, icon, url, email

Complex nested types such as repeater, typography, spacing, border, and dimensions are not supported as sub-fields.


Return Value

Type: array

Returns an indexed array of associative arrays — one per row — each containing the sub-field values keyed by sub-field ID. Returns an empty array if no rows have been added.

php

$members = themeplus_get_option( 'team_members', [] );
// Returns:
// [
//     ['name' => 'Alice', 'role' => 'Designer', 'photo' => 42, 'bio' => '...'],
//     ['name' => 'Bob',   'role' => 'Developer', 'photo' => 43, 'bio' => '...'],
// ]

Usage Examples

Rendering team members

php

$members = themeplus_get_option( 'team_members', [] );

if ( ! empty( $members ) ) {
    echo '<div class="team-grid">';
    foreach ( $members as $member ) {
        $name     = $member['name']  ?? '';
        $role     = $member['role']  ?? '';
        $photo_id = $member['photo'] ?? '';
        $bio      = $member['bio']   ?? '';

        echo '<div class="team-member">';
        if ( $photo_id ) {
            echo wp_get_attachment_image( $photo_id, 'medium', false, ['class' => 'team-member__photo'] );
        }
        echo '<h3 class="team-member__name">'  . esc_html( $name ) . '</h3>';
        echo '<p class="team-member__role">'   . esc_html( $role ) . '</p>';
        echo '<p class="team-member__bio">'    . esc_html( $bio  ) . '</p>';
        echo '</div>';
    }
    echo '</div>';
}

FAQ accordion

php

[
    'id'      => 'faq_items',
    'type'    => 'repeater',
    'title'   => __('FAQ Items', 'your-textdomain'),
    'fields'  => [
        [
            'id'    => 'question',
            'type'  => 'text',
            'title' => __('Question', 'your-textdomain'),
        ],
        [
            'id'    => 'answer',
            'type'  => 'textarea',
            'title' => __('Answer', 'your-textdomain'),
        ],
    ],
]

php

$faq_items = themeplus_get_option( 'faq_items', [] );

if ( ! empty( $faq_items ) ) {
    echo '<dl class="faq">';
    foreach ( $faq_items as $item ) {
        $question = $item['question'] ?? '';
        $answer   = $item['answer']   ?? '';

        if ( ! $question ) continue;

        echo '<dt class="faq__question">' . esc_html( $question ) . '</dt>';
        echo '<dd class="faq__answer">'   . esc_html( $answer   ) . '</dd>';
    }
    echo '</dl>';
}

Testimonials slider

php

[
    'id'      => 'testimonials',
    'type'    => 'repeater',
    'title'   => __('Testimonials', 'your-textdomain'),
    'fields'  => [
        [
            'id'    => 'quote',
            'type'  => 'textarea',
            'title' => __('Quote', 'your-textdomain'),
        ],
        [
            'id'    => 'author',
            'type'  => 'text',
            'title' => __('Author Name', 'your-textdomain'),
        ],
        [
            'id'    => 'company',
            'type'  => 'text',
            'title' => __('Company / Role', 'your-textdomain'),
        ],
        [
            'id'    => 'avatar',
            'type'  => 'image',
            'title' => __('Avatar', 'your-textdomain'),
        ],
        [
            'id'      => 'rating',
            'type'    => 'select',
            'title'   => __('Rating', 'your-textdomain'),
            'default' => '5',
            'options' => [
                '5' => '★★★★★',
                '4' => '★★★★☆',
                '3' => '★★★☆☆',
            ],
        ],
    ],
]

With a default row

php

[
    'id'      => 'social_links_custom',
    'type'    => 'repeater',
    'title'   => __('Custom Social Links', 'your-textdomain'),
    'default' => [
        ['label' => 'GitHub', 'url' => '', 'icon' => 'fa-brands fa-github'],
    ],
    'fields'  => [
        [
            'id'    => 'label',
            'type'  => 'text',
            'title' => __('Label', 'your-textdomain'),
        ],
        [
            'id'    => 'url',
            'type'  => 'text',
            'title' => __('URL', 'your-textdomain'),
        ],
        [
            'id'      => 'icon',
            'type'    => 'icon',
            'title'   => __('Icon', 'your-textdomain'),
            'default' => 'fa-brands fa-github',
        ],
    ],
]

With a conditional field

php

[
    'id'      => 'enable_testimonials',
    'type'    => 'toggle',
    'title'   => __('Enable Testimonials Section', 'your-textdomain'),
    'default' => false,
],
[
    'id'       => 'testimonials',
    'type'     => 'repeater',
    'title'    => __('Testimonials', 'your-textdomain'),
    'fields'   => [
        ['id' => 'quote',  'type' => 'textarea', 'title' => __('Quote', 'your-textdomain')],
        ['id' => 'author', 'type' => 'text',     'title' => __('Author', 'your-textdomain')],
    ],
    'required' => ['enable_testimonials', '==', true],
],

Notes

  • Always check ! empty( $rows ) before looping — the field returns an empty array when no rows have been added.
  • Always use the ?? null coalescing operator when reading sub-field values from each row — individual keys may be absent if a row was saved before a sub-field was added.
  • Sub-field IDs must be unique within the repeater but do not need to be globally unique across the entire options panel.
  • Rows are returned in the order the user arranged them in the panel — use this for drag-and-drop ordered content like feature lists or pricing tiers.
  • Avoid registering too many sub-fields per row — five to eight sub-fields per row keeps the panel usable. For complex structured data, consider splitting into multiple repeaters.

On This Page