Group aggregators

Declarative per-column rollups for group rows via the aggregate column option: sum, avg, min, max, count, countDistinct, extent, first, or a custom (values, rows) reducer. Each rollup is formatted with the column format and shown in the group header.

A live, editable Svelte 5 data grid example from the SvGrid gallery (Sorting & Grouping). See the SvGrid documentation for the full API.

About this example

Declarative per-column rollups for group rows in the Svelte 5 data grid. Set aggregate on a column and its group header shows the value formatted with the column's own format: sum for revenue with a currency format, avg for win rate as a percent, count, min, max, countDistinct, extent, first, or a custom (values, rows) reducer such as a median. The groupable shortcut switches grouping on.

Declarative per-column aggregation for group rows. Set aggregate on a column and the group header shows the rolled-up value, formatted with the column's own format:

{ field: 'revenue', aggregate: 'sum', format: { type: 'currency' } } { field: 'winRate', aggregate: 'avg', format: { type: 'percent' } } { field: 'score', aggregate: (vals) => median(vals) } // custom

Built-ins: sum, avg, min, max, count, countDistinct, extent, first - plus any custom (values, rows) => value reducer.

Imports, features and API used

Imports: @svgrid/grid

Table features registered: columnGroupingFeature

Columns: region (Region), rep (Rep), revenue (Revenue), deals (Deals), winRate (Win rate), score (Median score)

Frequently asked questions

Which aggregators are built in?

sum, avg, min, max, count, countDistinct, extent and first. Set one as the column's aggregate and the group row displays it through the column's format, so a currency column shows a currency total.

How do I write a custom aggregator?

Pass a function: aggregate: (values, rows) => median(values). It receives the leaf values of the group and the row objects, and its return value is rendered like a built-in.

Do aggregates nest for multi-level groups?

Yes. Each group level is rolled up from its own leaf rows, so a region total and a rep subtotal both show correctly when grouped by region then rep.

Related documentation

Related articles

Source code (142-group-aggregators.svelte)

<!-- Documented in: docs/help/grouping/aggregators.md -->
<script lang="ts">
  /**
   * 142. Group aggregators
   * ----------------------
   * Declarative per-column aggregation for group rows. Set `aggregate` on a
   * column and the group header shows the rolled-up value, formatted with the
   * column's own `format`:
   *
   *   { field: 'revenue', aggregate: 'sum',  format: { type: 'currency' } }
   *   { field: 'winRate', aggregate: 'avg',  format: { type: 'percent' } }
   *   { field: 'score',   aggregate: (vals) => median(vals) }   // custom
   *
   * Built-ins: sum, avg, min, max, count, countDistinct, extent, first -
   * plus any custom (values, rows) => value reducer.
   */
  import {
    SvGrid,
    tableFeatures,
    columnGroupingFeature,
    type GridColumns,
    type SvGridApi,
  } from '@svgrid/grid'

  const features = tableFeatures({ columnGroupingFeature })

  type Row = {
    id: number
    rep: string
    region: string
    revenue: number
    deals: number
    winRate: number
    score: number
  }

  let seed = 0xa11ce
  const rnd = () => ((seed = (seed * 1103515245 + 12345) >>> 0) / 0xffffffff)
  const REGIONS = ['Americas', 'EMEA', 'APAC']
  const NAMES = ['Ada', 'Grace', 'Alan', 'Margaret', 'Linus', 'Donald', 'Brian', 'Dennis', 'Barbara', 'Ken', 'Edsger', 'Tim']
  const rows: Row[] = NAMES.flatMap((name, i) =>
    REGIONS.map((region, j) => ({
      id: i * 3 + j,
      rep: name,
      region,
      revenue: Math.round(20_000 + rnd() * 200_000),
      deals: Math.round(2 + rnd() * 40),
      winRate: Math.round(rnd() * 100) / 100,
      score: Math.round(rnd() * 100),
    })),
  )

  const median = (vals: number[]): number => {
    if (!vals.length) return 0
    const s = [...vals].sort((a, b) => a - b)
    return s[Math.floor(s.length / 2)]!
  }

  const columns: GridColumns<Row> = [
    { field: 'region', header: 'Region', width: 140 },
    { field: 'rep', header: 'Rep', width: 130 },
    {
      field: 'revenue',
      header: 'Revenue',
      width: 150,
      aggregate: 'sum',
      format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } },
    },
    { field: 'deals', header: 'Deals', width: 110, aggregate: 'sum', align: 'right' },
    {
      field: 'winRate',
      header: 'Win rate',
      width: 120,
      aggregate: 'avg',
      align: 'right',
      format: { type: 'percent' },
    },
    {
      field: 'score',
      header: 'Median score',
      width: 130,
      aggregate: median,
      align: 'right',
    },
  ]

  let api = $state<SvGridApi<typeof features, Row> | null>(null)
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <div
    class="shrink-0 rounded-lg border px-4 py-3"
    style="border-color: var(--sg-border); background: var(--sg-header-bg);"
  >
    <p class="text-sm font-semibold" style="color: var(--sg-fg);">
      Per-column rollups via <code>aggregate</code>
    </p>
    <p class="mt-1 text-xs" style="color: var(--sg-muted);">
      Grouped by Region. Revenue + Deals = sum, Win rate = avg, Median score =
      a custom reducer. Each rollup is formatted with its column's
      <code>format</code> and shown in the group header. Use the column menu to
      group by another field.
    </p>
  </div>

  <div class="flex-1 min-h-0">
    <SvGrid responsive={true}
      columnResize
      data={rows}
      columns={columns}
      features={features}
      groupable
      selectionMode="none"
      rowHeight={36}
      containerHeight="100%"
      fitColumns={true}
      onApiReady={(a) => {
        api = a
        queueMicrotask(() => a.setGroupBy(['region']))
      }}
    />
  </div>
</section>

View this example on GitHub

More Sorting & Grouping examples

  • Grouping + aggregation - Group by department, sum salaries, average performance, expand/collapse keys.
  • Group panel (drag & drop) - A Group Panel: drag chips into the panel to group, drag inside to reorder grouping levels, × to ungroup. Drives api.setGroupBy() under the hood.
  • Group display modes + footers - Switch between groupRows banners, a single combined Group column, and one column per grouped field. groupFooters closes each group with a subtotal row under the real columns. Paging counts DATA rows, so pageSize means what it says: a page reprints the banners its rows sit under, and footers never eat the budget.
  • Tree data (hierarchy) - treeData nests rows by parent id into an expandable hierarchy. Tree rows stay real data rows - own cells, formatting, editing - and just gain an expander plus indent. Takes flat parent-id data directly, or nested children arrays via flattenTreeData. Full treegrid a11y with arrow-key expand.
  • Reporting workspace - Pivot-lite: group-by chips, per-column aggregator picker, saved views with localStorage persistence, live KPI strip + summary cards.