Average of Sub-Field Values in an ACF Repeater Field

Quick Summary
- Loop through the repeater with have_rows() and the_row(), add each test_score sub-field to a total, and divide by the number of rows from count( get_field() ).
- get_field() returns the repeater as an array of rows, so array_column(), array_filter() with is_numeric and array_sum() give the same average and also keep decimals and skip empty scores.
- Check that the repeater has rows first: on PHP 8, count() and array_column() throw a fatal TypeError on the false that get_field() returns for an empty repeater.
To get the average of a number stored in an ACF repeater, such as the mean of all test scores entered as rows, add up the sub-field in every row and divide the total by the number of rows. This snippet does it with a have_rows() loop. It needs Advanced Custom Fields PRO for the Repeater field and goes in the template that displays the post the repeater belongs to, for example single.php.
<?php
if ( have_rows('ss_results') ) :
$count = count(get_field('ss_results'));
$totaltaskpercentage = 0;
while ( have_rows('ss_results') ) : the_row();
$totaltaskpercentage += intval( get_sub_field('test_score'));
endwhile;
$final_result = $totaltaskpercentage / $count;
echo round($final_result, 2);
endif;
?>How it works
if ( have_rows( 'ss_results' ) )runs the code only when the repeater has rows, so it never divides by zero.get_field( 'ss_results' )returns the repeater as an array of rows, socount()gives the number of rows.- The
have_rows()andthe_row()loop goes through each row and adds itstest_scoresub-field to$totaltaskpercentage.intval()turns each score into a whole number, so decimals are dropped. - The total divided by the number of rows gives the average in
$final_result. Theechoprints it rounded to two decimal places; change the2for more or fewer. - Replace
ss_resultsandtest_scorewith your own field names: the Field Name of the repeater and of the sub-field, not their labels. - If the template can load while ACF is inactive, check that ACF is active before calling its functions.
A shorter version with array functions
get_field() returns the whole repeater as an array. Each row is an array keyed by sub-field name, so three rows of scores come back as [ [ 'test_score' => '80' ], [ 'test_score' => '92.5' ], [ 'test_score' => '71' ] ]. PHP’s array functions can calculate the average from that without a loop:
<?php
$rows = get_field( 'ss_results' );
if ( $rows ) {
$scores = array_filter( array_column( $rows, 'test_score' ), 'is_numeric' );
if ( $scores ) {
echo round( array_sum( $scores ) / count( $scores ), 2 );
}
}
?>get_field()returnsfalsewhen the repeater has no rows, soif ( $rows )stops there.array_column()takes thetest_scorevalue from every row.array_filter()withis_numericdrops rows whose score is empty, so they don’t count towards the average.array_sum()adds the remaining scores, decimals included, andcount()gives how many there are.
Which version should you use?
The two versions only differ when a score has decimals or a row has no score. With the three rows above, 80, 92.5 and 71, the loop prints 81 because intval() turns 92.5 into 92, and the array version prints 81.17. With 80, an empty score and 71, the loop counts the empty row as 0 and prints 50.33; the array version skips it and prints 75.5.
- Keep the loop when scores are whole numbers and an empty row should count as zero.
- Use the array version when scores can have decimals or some rows may be left empty.
- To keep decimals in the loop, change
intval()tofloatval(). Empty rows still count as zero.
Common problems
- Fatal error: count(): Argument #1 ($value) must be of type Countable|array. Since PHP 8.0,
count()throws aTypeErrorwhen it gets something other than an array, andarray_column()does the same.get_field()returnsfalsefor a repeater without rows, so always check for rows before counting, as both versions do. - Warning: array_sum(): Addition is not supported on type string. Since PHP 8.3,
array_sum()warns when it meets a value it can’t add, such as an empty score. Theis_numericfilter keeps those values out. - The repeater belongs to another post or an options page. Pass its ID as the second argument:
have_rows( 'ss_results', $post_id )andget_field( 'ss_results', $post_id ), or'option'instead of the ID for an ACF options page. The same pattern is used to read the first row of another post’s repeater.
Related articles
- reading the first row of an ACF repeater from another post: work with repeater rows as an array
