TanStack/table · error

coreColumnsFeature require an id when using a non-string hea

Error message

coreColumnsFeature require an id when using a non-string header

What it means

constructColumn throws this when a column definition has no id and its header is not a plain string, so the library cannot infer an id (only string headers or accessorKey yield an id). It is the non-accessorFn branch of the same missing-id guard, in development mode.

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.
  2. If the column needs no accessor, still give it a stable id like 'actions'.
  3. Keep headers as plain strings where possible so ids can be inferred.

Example fix

// before
{ header: (props) => <ActionsMenu {...props} /> }
// after
{ id: 'actions', header: (props) => <ActionsMenu {...props} /> }
Defensive patterns

Strategy: validation

Validate before calling

function assertColumnIdOrHeader(def) {
  const inferable = typeof def.id === 'string' || typeof def.header === 'string' || typeof def.accessorKey === 'string'
  if (!inferable) throw new Error('Column needs an explicit id (non-string header, no accessorKey)')
}
columns.forEach(assertColumnIdOrHeader)

Type guard

function canInferColumnId(def: ColumnDef<any>): boolean {
  return typeof def.id === 'string' || typeof def.header === 'string' || typeof def.accessorKey === 'string'
}

Try / catch

try {
  table.getColumn('actions') // built via constructColumn
} catch (e) {
  if ((e as Error).message.includes('require an id')) {
    console.error('Display-only column missing id:', e)
  } else throw e
}

Prevention

When it happens

Trigger: Passing a columnDef with a non-string header (component/JSX/render function) and no accessorKey/accessorFn and no id, in development.

Common situations: Display-only columns with custom render headers (e.g. row actions, drag handles) that omit id; migrating columns from string headers to custom header components.

Related errors


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