Why Summarizers Matter in Admin Panels
Most Filament tables display rows. Summarizers display meaning — a running total of invoice amounts, an average order value, a count of flagged records. Without them, users export to a spreadsheet just to sum a column. Filament v3 solves this natively.
Built-in Summarizers
Filament ships four summarizers out of the box: Sum, Average, Count, and Range. Attach them to any column via the summarize() method.
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\Summarizers\Average;
TextColumn::make('amount')
->money('usd')
->summarize([
Sum::make()->label('Total'),
Average::make()->label('Avg'),
]),
Filament runs a single aggregate query per summarizer — not one per row — so this is safe on large datasets.
Scoping a Summarizer to a Subset
Sometimes you want the sum only for paid invoices while still showing all rows. Use ->query() on the summarizer:
Sum::make()
->label('Paid Total')
->query(fn ($query) => $query->where('status', 'paid')),
The scoped query runs independently of the table's own filters, giving you a stable aggregate even when the user applies their own search.
Building a Custom Summarizer
The built-ins cover 80 % of cases. For the rest, extend Summarizer directly.
Use case: display the median of a numeric column. SQL has no universal MEDIAN(), so we'll pull the sorted values and compute it in PHP — acceptable for admin panels where the result set is bounded.
namespace App\Filament\Summarizers;
use Filament\Tables\Columns\Summarizers\Summarizer;
use Illuminate\Database\Query\Builder;
class Median extends Summarizer
{
public function getState(Builder $query): mixed
{
$values = $query
->orderBy($this->getColumn()->getName())
->pluck($this->getColumn()->getName())
->values();
$count = $values->count();
if ($count === 0) {
return null;
}
$mid = (int) floor($count / 2);
return $count % 2 === 0
? ($values[$mid - 1] + $values[$mid]) / 2
: $values[$mid];
}
}
Attach it like any built-in:
use App\Filament\Summarizers\Median;
TextColumn::make('response_time_ms')
->summarize(Median::make()->label('Median ms')),
Formatting the Output
Summarizers inherit ->formatStateUsing(), so you can format without touching the column itself:
Median::make()
->label('Median ms')
->formatStateUsing(fn ($state) => $state ? number_format($state, 1).' ms' : '—'),
Summarizers in Grouped Tables
When you enable row grouping (->groups()), Filament renders summarizers per group as well as in the global footer. No extra configuration needed — it just works, because each group exposes its own scoped query builder to the summarizer.
->groups([
Group::make('status')->label('Status'),
])
You'll see a subtotal row under each status group and a grand total at the bottom. This is one of the most underused features in Filament v3.
Performance Notes
- Each summarizer fires one additional query against the base Eloquent builder, not the paginated result.
- Scoped summarizers (
.query()) fire their own separate query. - For very large tables (millions of rows), push aggregates to a materialized view or a scheduled cache and return the cached value from
getState().
Takeaways
Sum,Average,Count, andRangecover most admin needs with zero boilerplate.- Scoped summarizers let you show stable aggregates independent of user-applied filters.
- Custom summarizers require only one method —
getState(Builder $query)— making them trivial to write. - Grouped tables get per-group subtotals automatically.
- For huge datasets, cache the aggregate and return it from a custom summarizer to avoid slow footer queries.