TechEarl

ACF Flexible Content Loop Not Working? Here's the Fix

ACF Flexible Content loops fail silently in five predictable ways: missing have_rows, wrong context, missing the_row inside the loop, post ID confusion, and nested loops. Here's the fix for each.

Ishan Karunaratne⏱️ 5 min readUpdated
Share thisCopied
ACF Flexible Content loop failing silently? Five common causes: missing have_rows, wrong context, missing the_row, post ID confusion, nested loops. Real fixes.

The ACF Flexible Content loop fails silently in five predictable ways. The function calls run, no PHP error is thrown, and the output is just empty. After fifteen years of building Flexible Content systems for agency clients (it is the foundation of every reusable component library I have shipped), I have seen each of these so many times that the diagnostic is now near-automatic. Here is the fix for each.

Jump to:

Cause 1: Missing have_rows() entirely

The canonical Flexible Content loop uses have_rows() to drive iteration:

php
if ( have_rows( 'page_builder' ) ) {
    while ( have_rows( 'page_builder' ) ) {
        the_row();
        $layout = get_row_layout();
        get_template_part( 'template-parts/flexible-content/' . $layout );
    }
}

If you wrote it with get_field( 'page_builder' ) and tried to foreach over the result, you skipped the have_rows() mechanism entirely. The data is technically there, but get_sub_field() calls inside your loop will return nothing because there is no active row context.

Fix: rewrite the loop using have_rows() and the_row(). The full pattern is documented in the ACF have_rows() reference.

Cause 2: Wrong post context

have_rows() defaults to the current post in the loop. If you are calling it from a template that is not inside the_loop() (a footer template, a custom widget, a REST callback), the function does not know which post's data to read.

Symptoms: the loop returns false. var_dump( get_field( 'page_builder', get_the_ID() ) ) returns false too, even though the data exists in the database.

Fix: pass the post ID explicitly as the second argument:

php
if ( have_rows( 'page_builder', $post_id ) ) {
    while ( have_rows( 'page_builder', $post_id ) ) {
        the_row();
        // ...
    }
}

For options pages, the second argument is 'option' rather than a post ID:

php
if ( have_rows( 'global_announcements', 'option' ) ) { /* ... */ }

For users, taxonomies, and comments, the second argument follows the standard ACF post ID convention ('user_42', 'term_5', 'comment_99').

Cause 3: Missing the_row() inside the loop

This one is easy to miss and produces the most confusing symptom. The loop runs but every get_sub_field() returns nothing.

php
// Broken: missing the_row()
while ( have_rows( 'page_builder' ) ) {
    $layout = get_row_layout(); // returns nothing
}

the_row() is what advances the internal row pointer and makes the current row's sub-fields available. Without it, get_sub_field() and get_row_layout() have no row to read from.

Fix: add the_row(); as the first call inside every while ( have_rows() ) loop. This is per the ACF the_row() reference.

php
while ( have_rows( 'page_builder' ) ) {
    the_row(); // <-- this
    $layout = get_row_layout();
    // ...
}

Cause 4: Using get_sub_field() outside the loop

get_sub_field() only works inside a have_rows() loop with the_row() active. If you try to use it elsewhere, you get nothing back, and ACF does not throw an error.

php
// Broken: get_sub_field outside the loop returns nothing
$heading = get_sub_field( 'heading' ); // no active row context

Fix: use get_field() if you are not inside a row, or wrap your code in the proper have_rows() / the_row() structure.

Cause 5: Nested loops without resetting

When you nest Flexible Content layouts (a "page builder" with a "stat row" layout that contains a repeater of stats), each level of nesting needs its own have_rows() / the_row() pair, and you must not break out of the outer loop before the inner one completes.

php
while ( have_rows( 'page_builder' ) ) {
    the_row();
    if ( get_row_layout() === 'stat_row' ) {
        // Nested loop
        if ( have_rows( 'stats' ) ) {
            while ( have_rows( 'stats' ) ) {
                the_row();
                $label = get_sub_field( 'label' );
                $value = get_sub_field( 'value' );
                echo "<dt>$label</dt><dd>$value</dd>";
            }
        }
    }
}

Common bug: forgetting that the inner get_sub_field( 'label' ) reads from the inner row, not the outer. As long as you have called the_row() on the inner loop, this works correctly.

If you accidentally use the_field( 'page_builder' ) or another function that resets the outer pointer, the outer loop breaks mid-iteration. Stick to have_rows(), the_row(), and get_sub_field() exclusively inside the nested structure.

The diagnostic order

When a Flexible Content loop is silently empty, my order of checks:

  1. Confirm data exists. var_dump( get_field( 'page_builder', get_the_ID() ) ); should return an array. If false, the data is not there (wrong post ID, wrong field name, or the field group is not assigned to this post type).
  2. Confirm have_rows() returns true. Add var_dump( have_rows( 'page_builder' ) ); directly before the loop. If false, the field name is wrong or you need to pass the post ID explicitly.
  3. Confirm the_row() is called. Look at the first line inside while ( have_rows() ). It must be the_row();.
  4. Confirm get_row_layout() returns the expected string. Add var_dump( get_row_layout() ); inside the loop. If it returns false, you are likely outside the row context.
  5. Confirm sub-field names match the field group registration. Use wp acf get (or check the field group export JSON) to see the actual sub-field names. Typos in get_sub_field( 'heading' ) vs get_sub_field( 'heading_text' ) are common.

The five causes above account for the overwhelming majority of "loop not working" tickets. If your debug session does not match any of them, the issue is usually in the field group registration itself (typo in the registered field name, wrong location rule) rather than in the template code. The full agency-scale pattern for Flexible Content is in Why Many Agencies Still Prefer ACF Over Gutenberg. For AI-assisted debugging of these patterns, see Using Claude CLI to Manage WordPress Sites.

Sources

Authoritative references this article was fact-checked against.

TagsWordPressACFFlexible ContentDebugging

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts