Typing APIs with feature maps

Posted on Mon 28 September 2026 in Tech, TypeScript

I was very intrigued by the latest release of TanStack Table, in particular the feature dependent type-safe API, in a nutshell it looks like this:

const 
const features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}
features
=
tableFeatures<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}>(features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
} & {}): {
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}

A helper function to help define the features that are to be imported and applied to a table instance. Use this utility to make it easier to have the correct type inference for the features that are being imported. Note: It is recommended to use this utility statically outside of a component.

Alongside feature modules, this object carries everything else that is statically stitched into the table:

  • Row model factories (sortedRowModel, filteredRowModel, etc.)
  • Row model function registries (sortFns, filterFns, aggregationFns), whose keys become the valid string values for sortFn, filterFn, globalFilterFn, and aggregationFn with full inference
  • Type-only tableMeta/columnMeta slots for declaring per-table meta types instead of using global declaration merging. The values are phantom (ignored and stripped at runtime); only their types are used.
@example
import {
  columnFilteringFeature,
  createFilteredRowModel,
  createSortedRowModel,
  filterFn_includesString,
  rowSortingFeature,
  sortFn_alphanumeric,
  sortFn_text,
  tableFeatures,
} from '@tanstack/react-table'
const features = tableFeatures({
  columnFilteringFeature,
  rowSortingFeature,
  filteredRowModel: createFilteredRowModel(),
  sortedRowModel: createSortedRowModel(),
  filterFns: { includesString: filterFn_includesString, myCustomFilterFn },
  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
  tableMeta: {} as { updateData: (rowIndex: number, columnId: string, value: unknown) => void },
  columnMeta: {} as { align?: 'left' | 'right' },
});
const table = useTable({ features, columns, data });
tableFeatures
({
coreReactivityFeature: TableReactivityBindingscoreReactivityFeature: function storeReactivityBindings(): TableReactivityBindings

TanStack Store–based reactivity for vanilla / non-framework use of constructTable, with createOptionsStore: true so table.optionsStore is available for subscriptions.

@example
import { constructTable, tableFeatures } from '@tanstack/table-core'
import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings'

const table = constructTable({
  features: tableFeatures({ coreReactivityFeature: storeReactivityBindings() }),
  // ...
})
storeReactivityBindings
(),
rowSortingFeature: TableFeaturerowSortingFeature, }) const
const basic: Table<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>
basic
=
constructTable<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>(tableOptions: TableOptions<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>): Table<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>

Constructs a table instance from normalized table internals.

This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations.

constructTable
({
TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
} & {}

The feature modules registered on this table instance.

Feature registration controls which state slices, options, and prototype APIs are available. This object also carries the table's row model factories (sortedRowModel, filteredRowModel, etc.), row model function registries (sortFns, filterFns, aggregationFns), and type-only meta slots (tableMeta, columnMeta).

features
,
TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.data: readonly {
    name: string;
}[]

The data for the table to display. When the data option changes reference, the table will reprocess the data.

data
,
TableOptions_Columns<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }, unknown>.columns: readonly ColumnDef<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}, unknown>[]

The array of column defs to use for the table.

columns
})
const
const enhanced: Table<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>
enhanced
=
constructTable<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>(tableOptions: TableOptions<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>): Table<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>

Constructs a table instance from normalized table internals.

This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations.

constructTable
({
TableOptions_Table<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.features: {
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
} & {}

The feature modules registered on this table instance.

Feature registration controls which state slices, options, and prototype APIs are available. This object also carries the table's row model factories (sortedRowModel, filteredRowModel, etc.), row model function registries (sortFns, filterFns, aggregationFns), and type-only meta slots (tableMeta, columnMeta).

features
:
tableFeatures<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}>(features: {
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
} & {}): {
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}

A helper function to help define the features that are to be imported and applied to a table instance. Use this utility to make it easier to have the correct type inference for the features that are being imported. Note: It is recommended to use this utility statically outside of a component.

Alongside feature modules, this object carries everything else that is statically stitched into the table:

  • Row model factories (sortedRowModel, filteredRowModel, etc.)
  • Row model function registries (sortFns, filterFns, aggregationFns), whose keys become the valid string values for sortFn, filterFn, globalFilterFn, and aggregationFn with full inference
  • Type-only tableMeta/columnMeta slots for declaring per-table meta types instead of using global declaration merging. The values are phantom (ignored and stripped at runtime); only their types are used.
@example
import {
  columnFilteringFeature,
  createFilteredRowModel,
  createSortedRowModel,
  filterFn_includesString,
  rowSortingFeature,
  sortFn_alphanumeric,
  sortFn_text,
  tableFeatures,
} from '@tanstack/react-table'
const features = tableFeatures({
  columnFilteringFeature,
  rowSortingFeature,
  filteredRowModel: createFilteredRowModel(),
  sortedRowModel: createSortedRowModel(),
  filterFns: { includesString: filterFn_includesString, myCustomFilterFn },
  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
  tableMeta: {} as { updateData: (rowIndex: number, columnId: string, value: unknown) => void },
  columnMeta: {} as { align?: 'left' | 'right' },
});
const table = useTable({ features, columns, data });
tableFeatures
({ ...
const features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}
features
, rowSelectionFeature: TableFeaturerowSelectionFeature }),
TableOptions_Table<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.data: readonly {
    name: string;
}[]

The data for the table to display. When the data option changes reference, the table will reprocess the data.

data
,
TableOptions_Columns<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }, unknown>.columns: readonly ColumnDef<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}, unknown>[]

The array of column defs to use for the table.

columns
,
})
const enhanced: Table<{
    rowSelectionFeature: TableFeature;
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>
enhanced
.
Table_RowSelection<{ rowSelectionFeature: TableFeature; coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>.toggleAllRowsSelected: (value?: boolean, opts?: {
    deselectAll?: boolean;
}) => void

Selects/deselects all rows in the table.

Deselecting keeps rows that cannot be selected in the selection map unless opts.deselectAll is true.

toggleAllRowsSelected
(true)
// this method does not exist on the basic version (missing rowSelectionFeature)
const basic: Table<{
    coreReactivityFeature: TableReactivityBindings;
    rowSortingFeature: TableFeature;
}, {
    name: string;
}>
basic
.toggleAllRowsSelected
Property 'toggleAllRowsSelected' does not exist on type 'Table<{ coreReactivityFeature: TableReactivityBindings; rowSortingFeature: TableFeature; }, { name: string; }>'.

Properties are available based on the features that were registered on the object. TanStack already published some very good articles here and here on how this works, but I would like to understand how it was really implemented, and how I could use this in my own APIs. In this article we will build a (very simple) editor using the same technique (this will also make the code easier to read as we won't need to carry TData everywhere).

First let's define what each feature can do

// an editor with a text feature can insert text
interface Editor_Text {
    Editor_Text.insertText: (value: string) => voidinsertText: (value: stringvalue: string) => void
}

// an editor with a history feature can undo and redo actions
interface Editor_History {
    Editor_History.undo: () => voidundo: () => void,
    Editor_History.redo: () => voidredo: () => void
}

// connect each feature with a name
type 
type EditorFeatureMap = {
    text: Editor_Text;
    history: Editor_History;
}
EditorFeatureMap
= {
text: Editor_Texttext: Editor_Text, history: Editor_Historyhistory: Editor_History }

Now let's implement those features, as the implementation does not really matter, I will keep that as simple as possible:

interface EditorCore {
  EditorCore.text: stringtext: string
}

// features are built using a factory, this lets us share state from the core
// of the editor to the installed features
interface EditorFeature {
  EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi: (editor: EditorCoreeditor: EditorCore) => void;
}

const const textFeature: EditorFeaturetextFeature: EditorFeature = {
  EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi(editor: EditorCoreeditor) {
    const const api: Editor_Textapi: Editor_Text = {
      Editor_Text.insertText: (value: string) => voidinsertText(value: stringvalue) {
        editor: EditorCoreeditor.EditorCore.text: stringtext += value: stringvalue
      }
    }
    var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.ObjectConstructor.assign<EditorCore, Editor_Text>(target: EditorCore, source: Editor_Text): EditorCore & Editor_Text (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@paramtarget The target object to copy to.@paramsource The source object from which to copy properties.
assign
(editor: EditorCoreeditor, const api: Editor_Textapi)
} } const const historyFeature: EditorFeaturehistoryFeature: EditorFeature = { EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi(editor: EditorCoreeditor) { const const api: Editor_Historyapi: Editor_History = { Editor_History.undo: () => voidundo() { // Restore the previous document state. }, Editor_History.redo: () => voidredo() { // Reapply the next document state. } } var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.ObjectConstructor.assign<EditorCore, Editor_History>(target: EditorCore, source: Editor_History): EditorCore & Editor_History (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@paramtarget The target object to copy to.@paramsource The source object from which to copy properties.
assign
(editor: EditorCoreeditor, const api: Editor_Historyapi)
} } type
type EditorFeatures = {
    text?: EditorFeature | undefined;
    history?: EditorFeature | undefined;
}
EditorFeatures
= type Partial<T> = { [P in keyof T]?: T[P] | undefined; }

Make all properties in T optional

Partial
<type Record<K extends keyof any, T> = { [P in K]: T; }

Construct a type with a set of properties K of type T

Record
<keyof
type EditorFeatureMap = {
    text: Editor_Text;
    history: Editor_History;
}
EditorFeatureMap
, EditorFeature>>;

Now, given a list of features, we need to pick only the ones we are interested in from the Map

// 1. the names of the features that were registered
type type RegisteredNames<Features extends EditorFeatures> = keyof Features extends keyof EditorFeatureMap ? keyof EditorFeatureMap & keyof Features : neverRegisteredNames<function (type parameter) Features in type RegisteredNames<Features extends EditorFeatures>Features extends 
type EditorFeatures = {
    text?: EditorFeature | undefined;
    history?: EditorFeature | undefined;
}
EditorFeatures
> =
type Extract<T, U> = T extends U ? T : never

Extract from T those types that are assignable to U

Extract
<keyof function (type parameter) Features in type RegisteredNames<Features extends EditorFeatures>Features, keyof
type EditorFeatureMap = {
    text: Editor_Text;
    history: Editor_History;
}
EditorFeatureMap
>
// 2. look those names up in the map, this gives a *union* of feature APIs type type RegisteredApis<Features extends EditorFeatures> = EditorFeatureMap[Extract<keyof Features, keyof EditorFeatureMap>]RegisteredApis<function (type parameter) Features in type RegisteredApis<Features extends EditorFeatures>Features extends
type EditorFeatures = {
    text?: EditorFeature | undefined;
    history?: EditorFeature | undefined;
}
EditorFeatures
> =
type EditorFeatureMap = {
    text: Editor_Text;
    history: Editor_History;
}
EditorFeatureMap
[type RegisteredNames<Features extends EditorFeatures> = keyof Features extends keyof EditorFeatureMap ? keyof EditorFeatureMap & keyof Features : neverRegisteredNames<function (type parameter) Features in type RegisteredApis<Features extends EditorFeatures>Features>]
// 3. the editor must expose *all* of them, so turn the union into an intersection type type UnionToIntersection<T> = (T extends any ? (a: T) => void : never) extends (a: infer R) => void ? R : neverUnionToIntersection<function (type parameter) T in type UnionToIntersection<T>T> = (function (type parameter) T in type UnionToIntersection<T>T extends any ? (a: Ta: function (type parameter) T in type UnionToIntersection<T>T) => void : never) extends (a: Ra: infer function (type parameter) RR) => void ? function (type parameter) RR : never type type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<function (type parameter) Features in type SelectFeatures<Features extends EditorFeatures>Features extends
type EditorFeatures = {
    text?: EditorFeature | undefined;
    history?: EditorFeature | undefined;
}
EditorFeatures
> =
type UnionToIntersection<T> = (T extends any ? (a: T) => void : never) extends (a: infer R) => void ? R : neverUnionToIntersection<type RegisteredApis<Features extends EditorFeatures> = EditorFeatureMap[Extract<keyof Features, keyof EditorFeatureMap>]RegisteredApis<function (type parameter) Features in type SelectFeatures<Features extends EditorFeatures>Features>>

Let's now check what each step is doing:

type Names = type RegisteredNames<Features extends EditorFeatures> = keyof Features extends keyof EditorFeatureMap ? keyof EditorFeatureMap & keyof Features : neverRegisteredNames<{ text: EditorFeaturetext: EditorFeature, history: EditorFeaturehistory: EditorFeature }>
type Names = "text" | "history"
type Apis = type RegisteredApis<Features extends EditorFeatures> = EditorFeatureMap[Extract<keyof Features, keyof EditorFeatureMap>]RegisteredApis<{ text: EditorFeaturetext: EditorFeature, history: EditorFeaturehistory: EditorFeature }>
type Apis = Editor_Text | Editor_History
type Editor = type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<{ text: EditorFeaturetext: EditorFeature, history: EditorFeaturehistory: EditorFeature }>
type Editor = Editor_Text & Editor_History

The first two steps are plain lookup, they convert a feature registration into a feature type. The last one is where it's getting interesting: the map is giving us a union of the features, but we want our editor to expose ALL the features that we enabled. It means that what we really want is an intersection of the features TanStack explains how this type works.

We can now add a constructor and use the types we created:

// we have not implemented this yet
declare function 
function constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): SelectFeatures<TFeatures>
constructEditor
<
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): SelectFeatures<TFeatures>
TFeatures
extends
type EditorFeatures = {
    text?: EditorFeature | undefined;
    history?: EditorFeature | undefined;
}
EditorFeatures
>({features: TFeatures extends EditorFeaturesfeatures}: {features: TFeatures extends EditorFeaturesfeatures:
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): SelectFeatures<TFeatures>
TFeatures
}): type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): SelectFeatures<TFeatures>
TFeatures
>
const const editor: Editor_Texteditor =
function constructEditor<{
    text: EditorFeature;
}>({ features }: {
    features: {
        text: EditorFeature;
    };
}): Editor_Text
constructEditor
({
features: {
    text: EditorFeature;
}
features
: {text: EditorFeaturetext: const textFeature: EditorFeaturetextFeature} // history is not available
}); const editor: Editor_Texteditor.Editor_Text.insertText: (value: string) => voidinsertText("Hello"); const editor: Editor_Texteditor.undo();
Property 'undo' does not exist on type 'Editor_Text'.
const const editorWithHistory: Editor_Text & Editor_HistoryeditorWithHistory =
function constructEditor<{
    text: EditorFeature;
    history: EditorFeature;
}>({ features }: {
    features: {
        text: EditorFeature;
        history: EditorFeature;
    };
}): Editor_Text & Editor_History
constructEditor
({
features: {
    text: EditorFeature;
    history: EditorFeature;
}
features
: {text: EditorFeaturetext: const textFeature: EditorFeaturetextFeature, history: EditorFeaturehistory: const historyFeature: EditorFeaturehistoryFeature} // history IS available
}); const editorWithHistory: Editor_Text & Editor_HistoryeditorWithHistory.Editor_Text.insertText: (value: string) => voidinsertText("Hello"); // still available const editorWithHistory: Editor_Text & Editor_HistoryeditorWithHistory.Editor_History.undo: () => voidundo(); // undo and redo are available

As you can see our types now reflects the feature they were built with. This is more or less everything we need for our implementation at the type level. Note that TanStack Table is a bit more complex (mostly to support plugins and to improve DX).

It's now time to implement constructEditor, that will register all added features and return an object with the correct type:

function 
function constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
constructEditor
<
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures
extends
type EditorFeatures = {
    text?: EditorFeature | undefined;
    history?: EditorFeature | undefined;
}
EditorFeatures
>({features: TFeatures extends EditorFeaturesfeatures}: {features: TFeatures extends EditorFeaturesfeatures:
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures
}): EditorCore & type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures
> {
const
const editor: {
    text: string;
}
editor
= {
EditorCore.text: stringtext: "" } satisfies EditorCore; for(const const feature: EditorFeaturefeature of var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.values<EditorFeature>(o: {
    [s: string]: EditorFeature;
} | ArrayLike<EditorFeature>): EditorFeature[] (+1 overload)

Returns an array of values of the enumerable own properties of an object

@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
values
(features: TFeatures extends EditorFeaturesfeatures)) {
const feature: EditorFeaturefeature?.EditorFeature.constructEditorApi: (editor: EditorCore) => voidconstructEditorApi(
const editor: {
    text: string;
}
editor
)
} return
const editor: {
    text: string;
}
editor
as EditorCore & type SelectFeatures<Features extends EditorFeatures> = (RegisteredApis<Features> extends any ? (a: RegisteredApis<Features>) => void : never) extends (a: infer R) => void ? R : neverSelectFeatures<
function (type parameter) TFeatures in constructEditor<TFeatures extends EditorFeatures>({ features }: {
    features: TFeatures;
}): EditorCore & SelectFeatures<TFeatures>
TFeatures
>;
} const const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory =
function constructEditor<{
    text: EditorFeature;
    history: EditorFeature;
}>({ features }: {
    features: {
        text: EditorFeature;
        history: EditorFeature;
    };
}): EditorCore & Editor_Text & Editor_History
constructEditor
({
features: {
    text: EditorFeature;
    history: EditorFeature;
}
features
: {text: EditorFeaturetext: const textFeature: EditorFeaturetextFeature, history: EditorFeaturehistory: const historyFeature: EditorFeaturehistoryFeature} // history IS available
}); const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory.Editor_Text.insertText: (value: string) => voidinsertText("Hello"); // still available var console: Consoleconsole.Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory.EditorCore.text: stringtext) // "Hello"
const editorWithHistory: EditorCore & Editor_Text & Editor_HistoryeditorWithHistory.Editor_History.undo: () => voidundo();

This line return editor as EditorCore & SelectFeatures<TFeatures>; is where our types meet our implementation, we have to rely on an unsafe as in the constructor, but this lets our public API be (almost) safe.

where the safety stops

There is one more thing worth noticing: the API is selected from the keys of the feature map, never from the values. The values are of type EditorFeature and carry no information about which feature it is. TanStack Table has the exact same property, rowSelectionFeature is exported as a plain TableFeature, so nothing stops us from registering one feature under another feature's name:

const 
const features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}
features
=
tableFeatures<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}>(features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
} & {}): {
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}

A helper function to help define the features that are to be imported and applied to a table instance. Use this utility to make it easier to have the correct type inference for the features that are being imported. Note: It is recommended to use this utility statically outside of a component.

Alongside feature modules, this object carries everything else that is statically stitched into the table:

  • Row model factories (sortedRowModel, filteredRowModel, etc.)
  • Row model function registries (sortFns, filterFns, aggregationFns), whose keys become the valid string values for sortFn, filterFn, globalFilterFn, and aggregationFn with full inference
  • Type-only tableMeta/columnMeta slots for declaring per-table meta types instead of using global declaration merging. The values are phantom (ignored and stripped at runtime); only their types are used.
@example
import {
  columnFilteringFeature,
  createFilteredRowModel,
  createSortedRowModel,
  filterFn_includesString,
  rowSortingFeature,
  sortFn_alphanumeric,
  sortFn_text,
  tableFeatures,
} from '@tanstack/react-table'
const features = tableFeatures({
  columnFilteringFeature,
  rowSortingFeature,
  filteredRowModel: createFilteredRowModel(),
  sortedRowModel: createSortedRowModel(),
  filterFns: { includesString: filterFn_includesString, myCustomFilterFn },
  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
  tableMeta: {} as { updateData: (rowIndex: number, columnId: string, value: unknown) => void },
  columnMeta: {} as { align?: 'left' | 'right' },
});
const table = useTable({ features, columns, data });
tableFeatures
({
coreReactivityFeature: TableReactivityBindingscoreReactivityFeature: function storeReactivityBindings(): TableReactivityBindings

TanStack Store–based reactivity for vanilla / non-framework use of constructTable, with createOptionsStore: true so table.optionsStore is available for subscriptions.

@example
import { constructTable, tableFeatures } from '@tanstack/table-core'
import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings'

const table = constructTable({
  features: tableFeatures({ coreReactivityFeature: storeReactivityBindings() }),
  // ...
})
storeReactivityBindings
(),
// the key says "selection", the value is the sorting feature rowSelectionFeature: TableFeaturerowSelectionFeature: const rowSortingFeature: TableFeature

Feature that adds row sorting state, defaults, and column/table sorting APIs.

rowSortingFeature
,
}) const
const table: Table<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}, {
    name: string;
}>
table
=
constructTable<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}, {
    name: string;
}>(tableOptions: TableOptions<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}, {
    name: string;
}>): Table<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}, {
    name: string;
}>

Constructs a table instance from normalized table internals.

This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations.

constructTable
({
TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }>.features: {
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
} & {}

The feature modules registered on this table instance.

Feature registration controls which state slices, options, and prototype APIs are available. This object also carries the table's row model factories (sortedRowModel, filteredRowModel, etc.), row model function registries (sortFns, filterFns, aggregationFns), and type-only meta slots (tableMeta, columnMeta).

features
,
TableOptions_Table<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }>.data: readonly {
    name: string;
}[]

The data for the table to display. When the data option changes reference, the table will reprocess the data.

data
,
TableOptions_Columns<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }, unknown>.columns: readonly ColumnDef<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}, {
    name: string;
}, unknown>[]

The array of column defs to use for the table.

columns
})
// compiles fine, the key is all the type system looks at...
const table: Table<{
    coreReactivityFeature: TableReactivityBindings;
    rowSelectionFeature: TableFeature;
}, {
    name: string;
}>
table
.
Table_RowSelection<{ coreReactivityFeature: TableReactivityBindings; rowSelectionFeature: TableFeature; }, { name: string; }>.toggleAllRowsSelected: (value?: boolean, opts?: {
    deselectAll?: boolean;
}) => void

Selects/deselects all rows in the table.

Deselecting keeps rows that cannot be selected in the selection map unless opts.deselectAll is true.

toggleAllRowsSelected
(true)
// ...and throws at runtime: // TypeError: table.toggleAllRowsSelected is not a function

At runtime the constructor iterates over Object.values(features), the key is never read and exists purely as a type-level label. TanStack's performance article does not directly discuss this case but Key-based selection is used in both the original and the optimized version of the Table type. My understanding is that checking the feature values as well, with a branded TableFeature<K> for instance, would add a comparison per feature on every constructTable call.