Table
Row Grouping
Group rows by a column value: the table orders records so groups stay contiguous, renders a header row for each group, and adds per-group subtotal rows for every column with a summary — on top of the usual grand-total footer.
On this page
Group rows by a column value: the table orders records so groups stay contiguous, renders a header row for each group, and adds per-group subtotal rows for every column with a summary — on top of the usual grand-total footer.
Quick Start
use NyonCode\WireTable\Columns\TextColumn;use NyonCode\WireTable\Table; public function table(Table $table): Table{ return $table ->model(Invoice::class) ->columns([ TextColumn::make('number')->label('Invoice'), TextColumn::make('customer')->label('Customer'), TextColumn::make('total') ->suffix(' Kč') ->summaryDecimals(0) ->summarizeSum('Sum'), ]) ->groupBy('customer');}
┌──────────────────────────────┐│ Acme │ ← group header├────────────┬─────────────────┤│ INV-2 │ 250 Kč ││ INV-4 │ 25 Kč ││ Sum: │ 275 Kč │ ← group subtotal├──────────────────────────────┤│ Beta ││ INV-1 │ 100 Kč ││ INV-3 │ 50 Kč ││ Sum: │ 150 Kč │├────────────┼─────────────────┤│ Sum: │ 425 Kč │ ← grand total footer└────────────┴─────────────────┘
Configuration
| Method | Effect |
|---|---|
groupBy(string $column) |
Group rows by a direct column on the model |
groupLabel(string|Closure) |
Customize the group header label |
groupSummaries(bool) |
Toggle per-group subtotal rows (default on) |
Group Labels
The header shows the raw group value by default. A string label becomes a prefix; a closure receives the value and the group's first record:
->groupBy('customer')->groupLabel('Customer') // "Customer: Acme"->groupBy('status')->groupLabel(fn ($value) => match ($value) { 'paid' => '✓ Paid', 'pending' => '⏳ Pending', default => ucfirst((string) $value),})
Empty and null group values render as —.
Sorting
Grouping prepends an ascending order on the group column, so any other sort —
the configured defaultSort() or a user's header click — applies within
each group. Sorting by the group column itself takes over completely: the
user's direction then controls group order (and groups stay contiguous, since
sorting by the group column orders groups by definition).
Subtotals
Group subtotal rows appear automatically for every column with a summary; all aggregate types and formatting apply. Subtotals are computed in memory from the group's rows on the current page.
TextColumn::make('total') ->summaryDecimals(0) ->summarizeSum('Sum') // → group subtotal row + grand total footer ->summarizeAvg('Average'), // each summary gets its own subtotal row
Disable the subtotal rows (keeping headers and the footer) with
->groupSummaries(false).
Limits
- Direct columns only.
groupBy('customer.name')throws — grouping must order the query by the group column, which a relationship path can't do without a join. Expose the related value on the query (join + select alias) and group by the alias instead. - Pagination splits groups. A group crossing a page boundary shows a
partial subtotal on each page. For strict accounting reports, disable
pagination (
->paginated(false)) or raiseperPage(). - Desktop table layout. Group headers/subtotals render in the standard table layout; the stacked mobile card layout ignores grouping.
- Exports contain data rows and grand totals, not group subtotal rows.
Related Docs
- Summaries — aggregate types, scopes, formatting
- Columns
- Table Overview