You output an ACF (SCF) repeater field with a three-part set: have_rows(), the_row() and get_sub_field(). The shape is nearly identical to the post loop, so once you learn the pattern you can reuse it everywhere.
<?php if (have_rows('items')) : ?>
<?php while (have_rows('items')) : the_row(); ?>
<?php the_sub_field('title'); ?>
<?php endwhile; ?>
<?php endif; ?>TipsACF or SCF—which one should I install?The difference between ACF and SCF, and how to chooseA repeater field is a collection of rows
A repeater field lets you add as many copies of the same set of input fields (rows) as you want. For an FAQ, for instance, you can add as many question-and-answer rows as you need.
FAQ (repeater field: faq)
├── row 1 question: How much is shipping? answer: A flat $5 nationwide
├── row 2 question: Can I return it? answer: Within 7 days of delivery…
└── row 3 question: Do you gift wrap? answer: Yes, free of chargeThe outer name (faq) is the repeater as a whole, and the inner names (question) are sub fields. That parent-child relationship maps straight onto the shape of the code.
The basic pattern: writing the three-part set
<?php if (have_rows('faq')) : ?>
<dl class="faq">
<?php while (have_rows('faq')) : the_row(); ?>
<dt><?php the_sub_field('question'); ?></dt>
<dd><?php the_sub_field('answer'); ?></dd>
<?php endwhile; ?>
</dl>
<?php endif; ?>The three roles break down like this.
have_rows('faq')—checks whether there are rows (used in both theifand thewhile)the_row()—advances to the next row. Without it you get an infinite loopthe_sub_field('question')—prints a value from the current row (useget_sub_field()when you want it in a variable)
You wrap it in an if to avoid leaving an empty <dl> behind when there are zero rows. Keeping headings and wrapper elements out of the output too is the standard way to write it.
Rows containing images and links
Even when a sub field is an image or a link, you pull it out with the same get_sub_field().
<?php if (have_rows('members')) : ?>
<ul class="members">
<?php while (have_rows('members')) : the_row(); ?>
<li>
<?php echo wp_get_attachment_image(get_sub_field('photo'), 'medium'); ?>
<p class="name"><?php the_sub_field('name'); ?></p>
<?php if ($url = get_sub_field('sns')) : ?>
<a href="<?php echo esc_url($url); ?>">Social</a>
<?php endif; ?>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>The one thing to watch is that image fields change how you write them depending on the return value (ID, URL, array).
TipsHow to display images with ACFHow to write image fields for each return valueNested repeaters (a repeater inside a repeater)
You can put a repeater field inside a repeater field. The way you write it is exactly the same on the inside—just stack another three-part set with a different name.
<?php while (have_rows('plans')) : the_row(); ?>
<section>
<h3><?php the_sub_field('plan_name'); ?></h3>
<?php if (have_rows('features')) : ?> <!-- the inner repeater -->
<ul>
<?php while (have_rows('features')) : the_row(); ?>
<li><?php the_sub_field('feature_text'); ?></li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
</section>
<?php endwhile; ?>The inner have_rows('features') looks inside the current row (the one the_row() advanced to), so there’s no need to spell out the parent name.
Counting rows, and knowing which row you’re on
<?php $rows = get_field('faq'); ?>
<p><?php echo count($rows); ?> in total</p>
<?php while (have_rows('faq')) : the_row(); ?>
<p>Q<?php echo get_row_index(); ?>: <?php the_sub_field('question'); ?></p>
<?php endwhile; ?>count(get_field('faq'))—the row count (with zero rowsget_field()returnsfalse, so checking with anifbeforecount()is safer)get_row_index()—which row you’re on (starts at 1)
You can also write it with foreach
Receive the whole repeater with get_field() and you can treat it as an ordinary PHP array. When the have_rows() shape feels hard to read, this gives the same result.
<?php $faq = get_field('faq'); ?>
<?php if ($faq) : ?>
<?php foreach ($faq as $row) : ?>
<dt><?php echo esc_html($row['question']); ?></dt>
<dd><?php echo esc_html($row['answer']); ?></dd>
<?php endforeach; ?>
<?php endif; ?>The array keys are the sub field names. Note, though, that this form sometimes skips the formatting (return value conversion) for things like image fields—so use have_rows() by default, and foreach only for plain text to stay on the safe side.
Outputting a repeater from another post
When you want “another post’s repeater” on an archive page or similar, pass a post ID as the second argument.
<?php while (have_rows('faq', 123)) : the_row(); ?>It’s useful for things like using a static page’s option fields on the front page. Pass option and you read values from an options page.
Summary
- Repeater fields use the three-part set of
have_rows()/the_row()/get_sub_field()—the same shape as the post loop - Wrap it in
if (have_rows(...))and no empty list is left behind when there are zero rows - Nesting is just stacking the same pattern. Count rows with
count(get_field()), and get the current row withget_row_index() - Forgetting
the_row()means an infinite loop, and mixing upget_field()with the sub functions means empty values—those two are the classic stumbles
Build things so that content grows just by adding rows, and you get a site whose updates you can hand off.