Relationship Aggregates

Display counts, sums, averages, and more from related models directly in your table columns.

CountColumn::make('Posts')->setDataSource('posts')
Articles Tags Tag Count Published Articles
1 Miss Eve Will 769 Haven Course Lake Hipolito, NV 61850 West Angelita Alice a little. | Majesty?' he. | So she was ready.
South BarttownWinonalandMiafortSouth CorrinevilleNew Keltonborough
5 tags 18 9 50% 9 published
2 Mr. Cornelius Hansen DDS 8049 Ledner Rapid Apt. 014 North Nathaniel, SC 18886-9393 Kohlerport By the use. | VERY much. | Queen till she.
WinonalandSouth Elta
2 tags 25 15 60% 15 published
3 Dr. Albertha Abbott 7778 Erick Passage Blandamouth, ND 34081 North Ayla After a time she. | Alice, that. | Fish-Footman was.
East AbnerboroughSouth BarttownMiafortCraigbury
4 tags 9 4 44% 4 published
4 Citlalli Berge PhD No address No city The Dormouse had. | Alice in. | It means much.
ZboncakstadJaskolskihavenOdaland
3 tags 25 9 36% 9 published
5 Mr. Austen Harber 5157 Mayer Throughway Apt. 950 Cyrilhaven, NV 65970 Kohlerport Then they. | ARE OLD, FATHER. | Hatter asked.
West LarissaburghMiafortSouth EltaLake EmmasideMariemouth
5 tags 25 15 60% 15 published
6 Raegan Pfannerstill PhD 16411 Tressie Square West Phyllismouth, HI 89056 North Ayla Alice, as the jury. | Next came the. | Lory hastily. 'I.
East AbnerboroughSouth BarttownKeeblermouthRobelberg
4 tags 16 6 38% 6 published
7 Dan Ebert No address No city SOME change in. | QUITE as much use. | March Hare.
ShyannsideCraigbury
2 tags 20 8 40% 8 published
8 Mertie Kohler No address No city Father William,'. | Alice had. | I can remember.
CartwrighthavenSouth EltaJaskolskihavenCraigbury
4 tags 21 10 48% 10 published
9 Prof. Cornelius Herzog DDS 563 Ortiz Prairie Suite 942 Lake Damarismouth, MN 84789 South Tomasfurt By the use of a. | There could be NO. | I never heard.
East AbnerboroughWest LarissaburghMariemouthEast EwellstadOdaland
5 tags 13 9 69% 9 published
10 Barbara Metz 918 Waelchi View Suite 490 Colehaven, KY 64041-2304 Lake Orvillestad I don't think,'. | Alice could not. | The long.
Cartwrighthaven
1 tags 24 14 58% 14 published
Total Users: 10 hasOne Nested: address → group → city hasMany belongsToMany Custom formatter Avg: 19.6 | Total: 196 Total: 99 Avg: 50% Total: 99

Showing 1 to 10 of results

Note for v3 This package version does not provide the v4 helpers heading(), description(), counts(), displayField(), relationshipSeparator(), limit() or formatRelations(). The demo above uses the v3 equivalents: CountColumn::setDataSource() for counts, ArrayColumn with data()/outputFormat()/separator() for relationship collections, and label()->html() for computed cells. Aggregate helpers for sum/avg exist as SumColumn/AvgColumn (demoed above as "Published Sum" and "Publish Rate"); there is no min/max aggregate column, and scoped counts must be added with withCount() in builder(). The page heading/description are rendered by this view rather than by the table component.

✨ Overview

Relationship aggregates allow you to display data from related models using Eloquent's built-in aggregate methods. This is similar to Filament Tables' relationship support and works seamlessly with existing Laravel relationships.

1 Basic Relationships (hasOne/BelongsTo)

Use dot notation to access related model data. The table automatically joins the necessary tables:

// Display address from related Address model
Column::make('Address', 'address.address')
    ->sortable()
    ->searchable(),

// Nested relationships
Column::make('City', 'address.group.city.name')
    ->sortable(),

2 Counting Relationships

Use CountColumn to display the number of related records:

// Count all related records
CountColumn::make('Articles')
    ->setDataSource('articles')
    ->sortable(),

// Scoped counts have no column-level API in v3 -
// add them in builder() and render with label()
public function builder(): Builder
{
    return User::query()->withCount([
        'articles as published_articles_count' => fn($q) => $q->where('is_published', true),
    ]);
}

Column::make('Published')
    ->label(fn($row) => $row->published_articles_count . ' published'),

// Count belongsToMany relationship
CountColumn::make('Tags')
    ->setDataSource('tags')
    ->sortable(),

3 Displaying Relationship Collections

Show collections from hasMany or belongsToMany relationships:

// Display article titles (limit is applied inside data())
ArrayColumn::make('Articles')
    ->data(fn($value, $row) => $row->articles->take(3))
    ->outputFormat(fn($index, $value) => e($value->title))
    ->separator(' | '),

// Display tags with custom formatting
ArrayColumn::make('Tags')
    ->data(fn($value, $row) => $row->tags)
    ->outputFormat(fn($index, $value) => '<span>' . e($value->name) . '</span>')
    ->flexRow(['class' => 'flex-wrap gap-1']),

4 Sum Aggregate

Use SumColumn to total a column from related records. The package auto-registers withSum() on the builder for you. In the demo above, the "Published Sum" column sums the boolean is_published flag on the articles relation — the sum of a 0/1 column is the number of published articles:

// Sum a column on a relation (exposed on the row as articles_sum_is_published)
SumColumn::make('Published Sum')
    ->setDataSource('articles', 'is_published')
    ->sortable()
    ->footer(fn($rows) => 'Total: ' . ($rows->sum('articles_sum_is_published') ?? 0)),

// Scoped sums are not available on the column in v3 -
// use withSum() in builder() and render with label()

5 Average Aggregate

Use AvgColumn to calculate the average (withAvg() is auto-registered on the builder). The "Publish Rate" column above averages the boolean is_published flag and overrides the default output with label() to render a percentage:

// Average a column on a relation (exposed as articles_avg_is_published)
AvgColumn::make('Publish Rate')
    ->setDataSource('articles', 'is_published')
    ->sortable()
    // Optional: label() overrides the raw average with custom formatting
    ->label(fn($row) => round(($row->articles_avg_is_published ?? 0) * 100) . '%'),

6 Min/Max Aggregates

Not available in v3. There is no min/max aggregate column - only CountColumn, SumColumn and AvgColumn. Use Eloquent in builder() instead:

public function builder(): Builder
{
    return User::query()
        ->withMin('orders', 'created_at')
        ->withMax('orders', 'created_at');
}

Column::make('First Order')
    ->label(fn($row) => $row->orders_min_created_at ?? 'Never'),

Column::make('Last Order')
    ->label(fn($row) => $row->orders_max_created_at ?? 'Never'),

7 Column Naming Convention

Column names must follow Laravel's naming convention:

Method Pattern Example
CountColumn{relationship}_countposts_count
SumColumn{relationship}_sum_{column}orders_sum_total
AvgColumn{relationship}_avg_{column}reviews_avg_rating
min / maxnot available in v3use withMin()/withMax()

Key Benefits

  • Automatic Query Optimization — Uses Eloquent's efficient aggregate queries
  • Scoped Aggregates — Filter related records before aggregating
  • Sortable & Searchable — Works with all column features
  • Type Safe — Follows Laravel conventions for predictable behavior