This section covers how custom aggregation functions can be supplied and used in the grid.
Registering Custom Functions Copy Link
Custom functions can be registered to the grid by name via the aggFuncs grid option. The functions can then be applied to columns by referencing the function name in the column definition. The default values of "sum", "min", "max", "first", "last", "count" and "avg" can also be overwritten with custom implementations.
The above example demonstrates the following configuration to register a custom "range" function and applies it to the total column:
<ag-grid-angular
[columnDefs]="columnDefs"
[aggFuncs]="aggFuncs"
/* other grid options ... */ />
this.columnDefs = [
{ field: 'total', aggFunc: 'range' },
];
this.aggFuncs = {
'range': params => {
const values = params.values;
return values.length > 0 ? Math.max(...values) - Math.min(...values) : null;
}
}; Directly Applied Functions Copy Link
For columns not Configured via the UI, it can be simpler to directly apply custom functions to columns. This can be done by passing a custom function directly to the column aggFunc property.
Direct functions will not appear in the Columns Tool Panel, work when Saving and Applying Column State, or work with Grid State. To use these features, register custom functions instead.
The above example demonstrates the following configuration to apply a custom "range" function to the total column:
<ag-grid-angular
[columnDefs]="columnDefs"
/* other grid options ... */ />
this.columnDefs = [
{
field: 'total',
aggFunc: params => {
const values = params.values;
return values.length > 0 ? Math.max(...values) - Math.min(...values) : null;
}
}
]; Multiple Group Levels Copy Link
When rows are grouped by more than one column, a custom aggregation runs from the lowest group level upwards.
- For the lowest-level groups,
params.valuescontains the raw cell values from the leaf rows. - For higher-level groups,
params.valuescontains the aggregated results from the child groups, not the original row values.
This means a custom aggregation function must be able to combine its own previous results. For simple aggregations like sum, this works naturally: adding partial sums gives the same total as adding every leaf. For aggregations like range, it does not: combining child ranges with Math.max(...values) - Math.min(...values) gives the range of the child ranges, not the original min/max spread across the leaves.
To support multiple group levels, return an IAggFuncResult instead of a raw scalar. This wrapper stores the displayed aggregate value, plus any extra data the next level up needs to aggregate correctly.
interface IAggFuncResult<TAggValue = number | bigint | null> {
// Returns a string representation of the aggregated value. Used also for sorting.
toString(): string;
// The aggregated scalar value.
value?: TAggValue;
// The count of aggregated values. Present on `avg` results.
count?: number;
// Returns the numeric representation of the aggregated value. Used also for sorting.
toNumber?(): TAggValue;
}For example, a range aggregation can return the displayed range as value, and also store min and max. Parent groups then combine the child min and max values to compute the correct parent range. The built-in avg and count aggregations use the same wrapper shape.
Reading Child Values Copy Link
Parent groups read each child's value using getDataValue. The mode argument controls what is returned:
'data'returns the stored wrapper unchanged, so the parent can read fields likeminandmaxoff it.'value'unwraps the wrapper to a scalar by callingtoNumber()when present, otherwise reading thevaluefield. The wrapper is returned unchanged if neither is available.
For child group rows, getDataValue(column, 'data') returns the IAggFuncResult produced by the child aggregation. For leaf rows, it returns the leaf cell value — usually a primitive read from the column's field, or a wrapper if the column has a valueGetter that returns one.
A custom aggregation function therefore needs to handle both cases: raw leaf values at the lowest level, and IAggFuncResult wrappers from child groups at higher levels.
Range Example Copy Link
The example below applies a range aggregation across two grouping levels (country, then year). The custom function reads each child via getDataValue, falling back to the stored min/max when the child is a sub-group.
A class is a convenient way to define the wrapper: toNumber() and toString() live on the prototype, and instanceof makes it easy to detect a wrapper alongside a typeof check for primitive leaf values.
Here is the configuration used in the example above:
/** Carries `min`/`max` alongside the scalar `value` so the parent recomputes in O(N). */
class RangeResult implements IAggFuncResult<number> {
constructor(
readonly value: number,
readonly min: number,
readonly max: number,
) {}
toNumber() { return this.value; }
toString() { return this.value.toFixed(2); }
}
function rangeAggFunc(params) {
let min = Infinity;
let max = -Infinity;
for (const child of params.aggregatedChildren) {
const childValue = child.getDataValue(params.column, 'data');
if (typeof childValue === 'number') {
// Leaf rows expose the raw `total` number.
min = Math.min(min, childValue);
max = Math.max(max, childValue);
} else if (childValue instanceof RangeResult) {
// Sub-group rows expose the wrapper produced one level down.
min = Math.min(min, childValue.min);
max = Math.max(max, childValue.max);
}
}
return Number.isFinite(min) ? new RangeResult(max - min, min, max) : null;
}
Ratio Example Copy Link
A ratio of two sums (gold to silver medals) uses the same pattern: carry both running totals on the wrapper so the parent can divide at any level. Giving leaf rows the same wrapper shape via a valueGetter lets the aggregation function read every child uniformly, without branching on leaf vs. group.
Here is the configuration used in the example above:
class RatioResult implements IAggFuncResult<number | null> {
readonly value: number | null;
constructor(
readonly gold: number,
readonly silver: number,
) {
this.value = silver ? gold / silver : null;
}
toNumber() { return this.value; }
toString() { const v = this.value; return v === null ? '' : v.toFixed(2); }
}
// Every leaf returns a `RatioResult` so the aggFunc reads each child the same way. Rows with no
// silvers carry `silver: 0` — `toString` blanks the cell, while `gold`/`silver` stay available
// for the parent group's running totals. Footer/filler rows have no `data`; return undefined
// so the cell is left empty.
function leafRatioValueGetter(params) {
if (!params.data) {
return undefined;
}
const { gold, silver } = params.data;
return new RatioResult(gold, silver);
}
function ratioAggFunc(params) {
let gold = 0;
let silver = 0;
for (const child of params.aggregatedChildren) {
const ratio = child.getDataValue(params.column, 'data');
if (ratio instanceof RatioResult) {
gold += ratio.gold;
silver += ratio.silver;
}
}
return new RatioResult(gold, silver);
}
This pattern works whenever each child can be summarised by a few numbers that the parent combines:
- Range — carry
minandmax; the parent takes the lowestminand highestmax. - Weighted average — carry
sumandcount; the parent adds them up, then divides. - Standard deviation — carry
sum,sum-of-squares, andcount; the parent adds all three. - Ratio of sums — carry the two running sums; the parent adds and divides.
The pattern only works when the values stored on the wrapper are enough on their own to combine into the parent summary, without revisiting the leaves. Aggregations like median or percentile do not have that property: a parent's median cannot be derived from its children's medians. For those, walk every descendant leaf directly via params.rowNode.getAggregatedChildren(params.column, true).
Custom aggregation functions can also be used with Editing Group Rows. When a group row is edited, the built-in distribution automatically handles sum, avg, min, max, first, and last. For custom aggregation functions, define a per-aggregation distribution strategy or provide a custom groupRowValueSetter callback.