TanStack/table · error

coreColumnsFeature require an id when using an accessorFn

Error message

coreColumnsFeature require an id when using an accessorFn

What it means

constructColumn in packages/table-core derives a column id from accessorKey or header string. When a column uses accessorFn (a function accessor) there is no key to infer an id from, so if no explicit id is given the library throws this dev-mode error rather than generating a duplicate-prone implicit id.

Source

Thrown at packages/table-core/src/core/columns/constructColumn.ts:94

          result = result?.[key]
          if (process.env.NODE_ENV === 'development' && result === undefined) {
            console.warn(
              `"${key}" in deeply nested key "${accessorKey}" returned undefined.`,
            )
          }
        }

        return result as TValue
      }
    } else {
      accessorFn = (originalRow: TData) =>
        (originalRow as any)[resolvedColumnDef.accessorKey]
    }
  }

  if (!id) {
    if (process.env.NODE_ENV === 'development') {
      throw new Error(
        resolvedColumnDef.accessorFn
          ? `coreColumnsFeature require an id when using an accessorFn`
          : `coreColumnsFeature require an id when using a non-string header`,
      )
    }
    throw new Error()
  }

  // Create column with shared prototype for memory efficiency
  const columnPrototype = getColumnPrototype(table)
  const column = Object.create(columnPrototype) as Column_CoreProperties<
    TFeatures,
    TData,
    TValue
  >

  // Only assign instance-specific properties
  column.accessorFn = accessorFn

View on GitHub (pinned to d01c01bedb)

Solutions

  1. Add an explicit id to the column definition alongside accessorFn.
  2. If the column also has a string header usable as id, set id to the same stable string used elsewhere (row.getCellValue, grouping keys).
  3. Verify all generated/dynamic column defs always include id when accessorFn is present.

Example fix

// before
{ accessorFn: (row) => row.user.name, header: 'User' }
// after
{ id: 'user.name', accessorFn: (row) => row.user.name, header: 'User' }
Defensive patterns

Strategy: validation

Validate before calling

function assertColumnHasId(def) {
  if (!def.id && def.accessorFn) {
    throw new Error('Column with accessorFn requires an explicit id: ' + (def.header ?? def))
  }
}
columns.forEach(assertColumnHasId) // before createTable/columns feature init

Type guard

function hasRequiredId(def: ColumnDef<any>): def is ColumnDef<any> & { id: string } {
  return typeof def.id === 'string' && def.id.length > 0
}

Try / catch

try {
  const column = constructColumn(id, originalColumnDef, resolvedColumnDef, table)
} catch (e) {
  if ((e as Error).message.includes('require an id')) {
    console.error('Column definition missing id:', originalColumnDef)
  } else throw e
}

Prevention

When it happens

Trigger: Passing a columnDef with accessorFn: (row) => ... but no id property, in development (NODE_ENV === 'development').

Common situations: Defining data-only columns with function accessors in an array of columnDefs; renaming accessorKey to accessorFn while removing id; dynamically built column configs.

Related errors


AI-assisted analysis of TanStack/table@d01c01bedb (2026-08-28). Data as JSON: /api/errors/b68025e142650aac. Report an issue: GitHub.