Search by

asignua / filament-group-summaries

m2niac

Column summaries (Sum, Average, Count, Range, Values or custom) printed in the group header row of Filament 5 tables, so they stay visible when the group is collapsed and respect filters and search; per-group control of the summary row Filament draws after each group; and a default sort direction pe

Package info

github.com/asignua/filament-group-summaries

pkg:composer/asignua/filament-group-summaries

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0

v1.1.0 2026-11-02 11:35 UTC

This package is auto-updated.

Last update: 2026-11-02 12:30:35 UTC


README

Stand With Ukraine Latest Version on Packagist Tests Total Downloads License Plumb score

Filament Group Summaries

Show a Filament table's column summaries in the group header row (they stay visible when the group is collapsed), hide the summary row drawn after each group, and give a group its own default sort direction. No Filament view is forked.

Requested in filamentphp/filament#19615 (summaries in the group header), with related asks #15353 (hide the summary per group) and #19753 (default direction for groups).

Screenshots

Summaries in the group header, one group collapsed

The same in dark mode

Coloured group headers with the summary row at the top, aligned under the columns

The same in dark mode

Requirements

  • PHP 8.3+
  • Filament 5.9+ (composer.json requires ^5.9). Developed and tested against Filament 5.9 (filament/tables v5.9.0). The plugin relies on a few Filament internals (listed in How it works); tests/FilamentInternalsTest.php fails with a message naming what moved if a Filament update breaks one.

Installation

composer require asignua/filament-group-summaries
php artisan filament:assets

Register the plugin on the panel (it links a tiny stylesheet after your theme):

use Asignua\FilamentGroupSummaries\GroupSummariesPlugin;

$panel->plugin(GroupSummariesPlugin::make());

Usage

Use SummaryGroup wherever you used Group; it extends it, so everything you already call still works.

use Asignua\FilamentGroupSummaries\SummaryGroup;
use Filament\Tables\Columns\Summarizers\Sum;

$table
    ->columns([
        TextColumn::make('status'),
        TextColumn::make('amount')->summarize(Sum::make()->label('Total')),
    ])
    ->groups([
        SummaryGroup::make('status')
            ->collapsible()
            ->summariesInHeader()     // Sum / Average / Count / Range / Values / custom, per group
            ->hideTrailingSummary()   // drop the row Filament draws after each group
            ->defaultDirection('desc'),
    ]);
Method What it does
summariesInHeader(bool|Closure = true) Prints each group's summaries in its header row. Values respect filters and search.
summaryColumns(?array $names) Limits the header to these columns (default: every visible column with a summarizer).
columnLabels(bool = true) Show or hide the column name before its summaries.
hideTrailingSummary(bool|Closure = true) Hides the per-group summary rows after the groups (the page / all-records totals in the footer stay).
position(SummaryPosition|Closure) Bottom (default, Filament's own), Top (a summary row right under the group header, aligned under the columns; the row after the records is hidden) or Both.
color(string|array|Closure|null) Tints the group header with a Filament colour (see below).
defaultDirection('asc'|'desc') Direction applied when the user picks this group in the grouping dropdown.
makeDefaultOn(Table $table) defaultGroup() that uses the group's own direction.

Summary row at the top of the group

use Asignua\FilamentGroupSummaries\SummaryPosition;

SummaryGroup::make('status')->position(SummaryPosition::Top);   // or ::Both to keep the row after the records too

The row sits in the group header (so it stays visible when the group is collapsed) and a small Alpine component lines its cells up under the table's header cells. On a narrow screen, or if a header cell is missing, the cells wrap as a strip instead; a column hidden responsively (visibleFrom()) is hidden in the row too. Only the table layout is supported: in a grid / Stack / Split layout the top row (like summariesInHeader()) does not render. With Top or Both, summariesInHeader() is not needed (the top row replaces the strip).

Header colour

SummaryGroup::make('status')->color('success');                       // any colour registered with FilamentColor
SummaryGroup::make('status')->color(['paid' => 'success', 'void' => 'danger']);   // by group key (or title)
SummaryGroup::make('status')->color(fn (?string $key, $record, $title, $state): ?string => match ($key) {
    'paid' => 'success',
    'open' => 'warning',
    default => null,       // neutral header
});

A shade map such as Color::Purple works too, and so does a closure returning one (a map without the 400 and 500 shades is ignored). The group key is null for a NULL value, so type it ?string. The tint uses the colour's own CSS variables, so custom colours and light/dark mode work. It is painted by an empty element inside the header (the header cell itself belongs to Filament's view), so the stylesheet is required.

For the table's default group use makeDefaultOn(); plain ->defaultGroup($group) keeps Table::defaultGroup()'s own direction argument ('asc').

How it works and its trade-offs

Filament has no hook for a group header, and index.blade.php is huge and changes every release, so the view is not overridden. Instead:

  • Header summaries. The header prints the group's description with {{ }}, which renders any Htmlable unescaped. SummaryGroup::getDescription() returns your description (if any) plus a strip of summaries. The values come from Filament's own getTableSummarySelectedState() (the same grouped query Filament runs for its summary rows), rendered by the columns' own summarizers, so formatting, labels and custom summarizers match the footer. The strip is cached per query, so any number of group headers cost one extra aggregate query per render.
  • Hide the trailing row. A marker element is emitted into the header and a CSS :has() rule hides a tr.fi-ta-summary-row that directly follows a record row and is not the last row of the tbody: that is always a per-group row (the next group header or the totals follow it). The page / all-records totals sit in the same tbody but follow a summary row or the page-summary header row; when the last group continues on the next page Filament skips its trailing row, and without a page-summary header (page summaries off, cursor pagination) the all-records total follows a record row, but it is then the last row and stays visible. So the totals stay visible on single-page, multi-page, cursor-paginated and unpaginated tables alike. One limit: with summaries(allTableCondition: false) on a page without a page-summary header row, the very last per-group row is the last row of the tbody and stays visible. If a Filament update changes that markup, the rows simply stay visible.
  • Default direction. The dropdown always sends group:asc the moment a group is picked, so a Livewire component hook (registered by the service provider) swaps in the group's direction when the group changes. A later explicit direction change inside the same group is respected.

Trade-offs, honestly:

  • The header is a single cell spanning the whole row, so the values are a strip of "Column Label value" items under the group title, not cells aligned under their columns. Aligned cells would need a fork of Filament's table view.
  • Filament still computes its own grouped aggregate for the (hidden) trailing rows: header summaries add one query on top of Filament's one.
  • The strip sits in the group description, which is a <p>: summarizer HTML is flattened to inline elements (block tags such as <div>, <ul>, <li> become <span>, Values without a custom ->view() becomes a comma-separated list; summarizers are rendered with toHtml(), so a custom ->view() shows in the header as in the footer).

Gotchas

  • Only aggregate summarizers (Sum, Average, plain Count, Range) use the shared grouped query. Summarizers that query on their own run their queries once per group header, on top of the ones Filament runs for its trailing rows: ->query(...) modifications, Values (a distinct()->pluck()), Count::icons() (a pluck plus a count per value) and ->using(...) callbacks.
  • hideTrailingSummary() is per SummaryGroup, not per group value (CSS cannot tell which trailing row belongs to which group).
  • Tables without an Eloquent summary query (custom array data) get no header summaries.
  • hideTrailingSummary() is CSS only. Without the stylesheet the header strip is unstyled and hideTrailingSummary() has no effect. GroupSummariesPlugin links it in a panel; outside a panel (a standalone Livewire table, or a panel without the plugin) add it to the layout yourself: <link rel="stylesheet" href="{{ \Filament\Support\Facades\FilamentAsset::getStyleHref('filament-group-summaries', 'asignua/filament-group-summaries') }}">.

AI agents

resources/boost/guidelines/core.blade.php ships Laravel Boost guidelines.

Testing

composer install
vendor/bin/phpunit

See the CHANGELOG and the LICENSE.

More plugins by asign

All plugins: https://filamentphp.com/plugins?q=asign