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
- Pattern 2: get_field_count() inside the loop
- Pattern 3: direct meta read for performance
- Counting only rows that match a condition
- Counting across many posts at once
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:
$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):
$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:
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:
$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:
$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):
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:
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.
- Repeater field (Advanced Custom Fields docs)advancedcustomfields.com
- get_post_meta() function reference (WordPress Developer Resources)developer.wordpress.org





