TanStack/table · warning
aggregationFn at index ${i} for column '${column.id}' needs
Error message
aggregationFn at index ${i} for column '${column.id}' needs a stable id What it means
When `columnDef.aggregationFn` is an array, each element must be resolvable to a stable id: either a plain string (used as both ref and id) or an object descriptor carrying `id` and `aggregationFn`. Bare function values or malformed objects have no id, so this warning fires and the entry is resolved as `{ aggregationFn: undefined, id: undefined }` — it is skipped in the keyed aggregation result. The id is required so aggregated results can be keyed and merged across grouped sub-rows.
Source
Thrown at packages/table-core/src/features/row-aggregation/rowAggregationFeature.utils.ts:300
: isAggregationFnDescriptor(item)
? item.id
: undefined
if (id !== undefined) ids[id] = (ids[id] ?? 0) + 1
}
const resolved: Array<ResolvedAggregationFn<TFeatures, TData>> = []
for (let i = 0; i < option.length; i++) {
const item = option[i]
const id =
typeof item === 'string'
? item
: isAggregationFnDescriptor(item)
? item.id
: undefined
if (id === undefined) {
warn(
`aggregationFn at index ${i} for column '${column.id}' needs a stable id`,
)
resolved.push({ aggregationFn: undefined, id: undefined })
continue
}
if (ids[id]! > 1) {
warn(`aggregationFn id '${id}' for column '${column.id}' is duplicated`)
resolved.push({ aggregationFn: undefined, id })
continue
}
const ref = isAggregationFnDescriptor(item) ? item.aggregationFn : item
resolved.push({
aggregationFn: resolveAggregationFn(column, ref),
id,
})
}View on GitHub (pinned to d01c01bedb)
Solutions
- Wrap each bare function in a descriptor with an explicit id: `{ id: 'sum', aggregationFn: myFn }`.
- Or use plain strings referencing registered fns: `aggregationFn: ['sum', 'mean']`.
- Check each array element for a literal `id` property; fix typos like `key` or `name`.
- Verify the multiple-aggregation result shape: entries without ids never appear in the keyed object, so add ids to see their values.
- Strengthen types (drop `as any`) so the compiler enforces the `AggregationFnDescriptor` shape.
Example fix
// before
aggregationFn: [sumFn, meanFn]
// after
aggregationFn: [
{ id: 'sum', aggregationFn: sumFn },
{ id: 'mean', aggregationFn: meanFn },
] Defensive patterns
Strategy: validation
Validate before calling
function assertStableIds(option: unknown) {
if (!Array.isArray(option)) return
option.forEach((item, i) => {
const ok =
typeof item === 'string' ||
(!!item && typeof item === 'object' && typeof (item as any).id === 'string')
if (!ok) throw new Error(`aggregationFn[${i}] must be a string or { id, aggregationFn }`)
})
}
assertStableIds(columnDef.aggregationFn) Type guard
function isAggregationFnDescriptor(
value: unknown,
): value is { id: string; aggregationFn: unknown } {
return (
!!value &&
typeof value === 'object' &&
typeof (value as any).id === 'string' &&
'aggregationFn' in (value as any)
)
} Try / catch
try {
const entries = column.getAggregationFns()
const missing = entries.filter((e) => e.id === undefined)
if (missing.length) {
console.warn(`${missing.length} aggregation entries skipped for lack of a stable id`)
}
} catch (e) {
console.error('failed to resolve aggregation entries', e)
} Prevention
- Never put bare functions in an array-valued aggregationFn; always wrap in { id, aggregationFn }.
- Keep ids unique per column and stable across renders (module-level constants, not inline literals in render).
- Enable strict TypeScript typing on columnDefs so descriptors missing `id` are rejected at compile time.
- Render-time check: in dev, assert every entry from getAggregationFns() has an id when aggregationFn is an array.
When it happens
Trigger: Passing `aggregationFn: [myFn, otherFn]` (bare functions, no wrapping descriptor); passing an object with only `aggregationFn` but no `id` key (or misspelled `id`); passing a non-string, non-descriptor value like a number inside the array.
Common situations: Migrating from a scalar `aggregationFn` to multiple aggregations and assuming plain functions work in arrays; building descriptors dynamically and omitting `id`; TypeScript's looser typing letting an object missing `id` slip through with `as any`.
Related errors
- ${message}
- aggregationFn '${String(ref)}' for column '${column.id}' is
- aggregationFn id '${id}' for column '${column.id}' is duplic
- globalFilterFn '${String(globalFilterFn)}' is not registered
- [table-worker] ${message}
AI-assisted analysis of TanStack/table@d01c01bedb (2026-08-28).
Data as JSON: /api/errors/3a32f5f8fad2e6bb.
Report an issue: GitHub.