Tips・WordPress custom fields (ACF / SCF)

How to output an ACF repeater field

How to output ACF (SCF) repeater fields in your template. Covers the three-part set of have_rows, the_row and get_sub_field, nested repeaters, and what causes an infinite loop, with code.

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 choose

A 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 charge

The 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 the if and the while)
  • the_row()—advances to the next row. Without it you get an infinite loop
  • the_sub_field('question')—prints a value from the current row (use get_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.

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 value

Nested 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 rows get_field() returns false, so checking with an if before count() 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

  1. Repeater fields use the three-part set of have_rows() / the_row() / get_sub_field()—the same shape as the post loop
  2. Wrap it in if (have_rows(...)) and no empty list is left behind when there are zero rows
  3. Nesting is just stacking the same pattern. Count rows with count(get_field()), and get the current row with get_row_index()
  4. Forgetting the_row() means an infinite loop, and mixing up get_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.

FAQ

get_field() comes back empty for values inside a repeater field.
Inside a repeater you use get_sub_field() (or the_sub_field() if you're only displaying). get_field() looks at fields directly on the post, so it can't reach values inside a row.
My page goes blank, or never finishes loading.
Forgetting the_row() inside the while loop means the rows never advance, so you get an infinite loop. Learn while (have_rows('items')) : the_row(); as a single unit.
Can I use repeater fields in the free version of ACF?
With ACF (the WP Engine version) you need the paid ACF PRO. SCF, distributed by WordPress officially, includes it for free from the start, so with SCF you can use it right away.
I want to count how many rows there are.
count(get_field('items')) gives you the row count. To know which row you're on inside the loop, use get_row_index() (it starts at 1).