Filament v3 Table Column Summarizers: Totals, Averages, and Custom Aggregates
#filament #laravel #php #admin-panel

Filament v3 Table Column Summarizers: Totals, Averages, and Custom Aggregates

3 min read Mohamed Said Mohamed Said

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, and Range cover 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.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Do Filament summarizers run a query per row?
No. Each summarizer runs a single aggregate query against the base builder, regardless of how many rows are displayed. Scoped summarizers add one additional query each.
Q02 Can I use a summarizer only on a specific group in a grouped table?
Filament automatically scopes summarizers to each group when row grouping is enabled. You cannot easily exclude a specific group without overriding getState() in a custom summarizer and filtering by group key manually.
Q03 How do I handle summarizers on very large tables without slow queries?
Build a custom summarizer that returns a cached value — computed by a scheduled job or stored in a materialized view — instead of running a live aggregate query on every page load.

Continue reading

More Articles

View all