TechEarl

How to Count Rows in an ACF Repeater Field

Counting ACF Repeater rows is three short patterns: count() on the raw field, get_field_count() inside a loop, and a faster meta-only count that skips loading the rows. Each has its right use case.

Ishan Karunaratne⏱️ 5 min readUpdated
Share thisCopied
Three patterns for counting ACF Repeater rows: count() on the raw field, get_field_count, and a fast meta-only count without loading the rows.

Counting rows in an ACF Repeater is three short patterns depending on where you are in the code. count() on the raw field value is the simplest. get_field_count() works inside a have_rows() loop and is the cleanest when you are already iterating. A direct get_post_meta read of the count is the fastest for cases where you only need the count and not the row data, which matters at scale on directory sites with thousands of rows per post.

Jump to:

Pattern 1: count() on the raw field value

When you have the Repeater field value loaded as an array (which is what get_field returns for a Repeater), count is one call:

php
$rows = get_field( 'team_members', $post_id );
$count = is_array( $rows ) ? count( $rows ) : 0;

The is_array check handles the case where the field is empty or unset. get_field returns false for empty Repeaters, which would cause count(false) to emit a warning in PHP 8.x.

Use this pattern when you need the count and you are about to use the row data anyway. Loading the field is the expensive step; counting it once you have loaded it is free.

Pattern 2: get_field_count() inside the loop

When you are already inside a have_rows loop and want the total count (for "row 1 of 5" display):

php
$total = 0;
if ( have_rows( 'team_members' ) ) {
    $total = count( get_field( 'team_members' ) );
    $index = 0;
    while ( have_rows( 'team_members' ) ) {
        the_row();
        $index++;
        printf( '<li>Item %d of %d: %s</li>',
            $index, $total, esc_html( get_sub_field( 'name' ) )
        );
    }
}

There is no first-class get_field_count() helper in ACF, but the pattern above is the canonical way to get a "row N of M" display inside a loop. The count( get_field( ... ) ) call inside the loop hits ACF's internal cache so it does not re-query the database.

If you only need the current row index and not the total, ACF provides get_row_index() which returns the 1-based position of the current row in the loop. This is useful for first-row-only or alternate-row styling:

php
while ( have_rows( 'team_members' ) ) {
    the_row();
    if ( get_row_index() === 1 ) {
        // first row gets special treatment
    }
}

Pattern 3: direct meta read for performance

For sites with very large Repeaters (50+ rows per post, thousands of posts), loading the entire Repeater just to count it is wasteful. The count is stored as a single meta value with the field's name as the key:

php
$count = (int) get_post_meta( $post_id, 'team_members', true );

This is one meta read instead of N. On a Repeater with 200 rows and 5 sub-fields each, this saves 1,000+ meta reads (or one full Repeater hydration, depending on cache state).

The caveat: this assumes the count meta value is in sync with the actual sub-field rows. ACF maintains it correctly under normal use. If you ever directly manipulate wp_postmeta for this Repeater (via update_post_meta rather than update_field), the count can get out of sync and the direct-meta read returns the wrong number.

I use this pattern on directory listing index pages where I am rendering 50 listings, each with a "view N events" link. The count is needed for the link text; the actual event rows are loaded only when the user clicks through.

Counting only rows that match a condition

When you need the count of rows where a sub-field matches some condition, you have to iterate:

php
$count = 0;
if ( have_rows( 'team_members' ) ) {
    while ( have_rows( 'team_members' ) ) {
        the_row();
        if ( get_sub_field( 'is_active' ) ) {
            $count++;
        }
    }
}

For this case there is no meta-only shortcut. You have to read each row's sub-field. If this is on a hot path with thousands of rows, consider mirroring the count as a derived ACF field via acf/save_post (covered in Useful Things You Can Do with acf/save_post):

php
add_action( 'acf/save_post', function ( $post_id ) {
    $rows = get_field( 'team_members', $post_id ) ?: [];
    $active = array_filter( $rows, fn( $r ) => ! empty( $r['is_active'] ) );
    update_field( 'active_member_count', count( $active ), $post_id );
}, 20 );

Then your hot path reads active_member_count directly via get_field (one meta read) instead of iterating.

Counting across many posts at once

For agency reporting ("show me the total team-member count across all our listings"), the right approach is a custom WP-CLI command rather than a per-page query:

php
WP_CLI::add_command( 'listing report-team-members', function () {
    $total = 0;
    $posts = get_posts( [
        'post_type' => 'listing',
        'posts_per_page' => -1,
        'fields' => 'ids',
    ] );

    foreach ( $posts as $post_id ) {
        $total += (int) get_post_meta( $post_id, 'team_members', true );
    }

    WP_CLI::success( "Total team members across all listings: $total" );
} );

This uses Pattern 3 (direct meta read) for each post, avoiding the cost of hydrating Repeaters. On a site with 5,000 listings each having 10-50 team members, this runs in seconds rather than minutes.

For more on WP-CLI patterns that compose well with ACF (especially when paired with terminal-driven AI assistance), see Using AI with WP-CLI for Faster WordPress Operations.

The pattern choice is mostly about the use case: Pattern 1 when you have the data, Pattern 2 when you are looping anyway, Pattern 3 when count is all you need. At small scale the choice does not matter; at directory-site scale, Pattern 3 makes the difference between "page loads in 200ms" and "page loads in 2 seconds."

Sources

Authoritative references this article was fact-checked against.

TagsWordPressACFRepeater

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

How to Get ALT Text from an ACF Image Field

Getting alt text from an ACF Image field depends on the field's Return Format. Image Array gives you the alt directly. ID and URL formats need a wp_get_attachment helper. Plus the cleanest pattern for always-correct alt output.

How to Set Default Values in ACF Select Fields

ACF Select fields have a Default Value setting in the field group editor that handles the simple case. For dynamic defaults (computed from another field, role-based, or per-post-type), the acf/load_value filter is the right tool.

Sending Gravity Forms Data Into ACF Repeater Fields

Gravity Forms does not natively submit Repeater-shaped data, but three patterns handle the common cases: append-per-submission, single submission with grouped sections, and a custom JSON-encoded field. Here is each with code.