Skip to main content

ObjectTable

A comprehensive guide for using the ObjectTable component from @osdk/react-components.

Prerequisites

Before using ObjectTable, make sure you have completed the library setup described in the README, including:

  • Installing the required dependencies
  • Wrapping your app with OsdkProvider2
  • Adding the CSS imports

Table of Contents

Import

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import type {
ColumnDefinition,
EditFieldConfig,
} from "@osdk/react-components/experimental/object-table";

Basic Usage

Minimal Example

The simplest way to use ObjectTable is with just an object type:

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Office } from "@YourApp/sdk";

function OfficesPage() {
return (
<ObjectTable
objectType={Office}
/>
);
}

This displays all properties of the Office object type in a table with default settings.

With Selection

Add selection mode to enable row selection:

<ObjectTable
objectType={Office}
selectionMode="single" // or "multiple" or "none" (default)
/>;

Props Reference

Core Props

PropTypeRequiredDefaultDescription
objectTypeQ-The OSDK object type to display
classNamestring-CSS class for custom styling
rowHeightnumber40Height of each row in pixels

Column Management

PropTypeDefaultDescription
columnDefinitionsArray<ColumnDefinition>-Ordered list of columns. If omitted, shows all properties
onColumnVisibilityChanged(newStates) => void-Called when column visibility changes
onColumnsPinnedChanged(newStates) => void-Called when column pinning changes
onColumnResize(columnId, newWidth) => void-Called when a column is resized

Filtering

Note: The table filtering UI is not yet supported. However, you can still pass a filter prop to programmatically filter the objects displayed in the table.

PropTypeDefaultDescription
enableFilteringbooleantrueWhether filtering menu items are shown in the column header menu
filterWhereClause<Q, RDPs>-Current where clause filter (controlled mode)
onFilterChanged(newWhere) => void-Required when filter is provided

Sorting

PropTypeDefaultDescription
enableOrderingbooleantrueWhether sorting menu items are shown
defaultOrderByArray<{property, direction}>-Initial sort order (uncontrolled)
orderByArray<{property, direction}>-Current sort order (controlled)
onOrderByChanged(newOrderBy) => void-Required when orderBy is provided

Column Features

PropTypeDefaultDescription
enableOrderingbooleantrueWhether sorting menu items are shown
enableColumnPinningbooleantrueWhether pinning menu items are shown
enableColumnResizingbooleantrueWhether resize menu item is shown
enableColumnConfigbooleantrueWhether column configuration menu item is shown

Hiding Header Menu Items

Each column header has a menu with items for sorting, filtering, pinning, resizing, and column configuration. You can hide specific menu items by setting the corresponding enable... prop to false:

<ObjectTable
objectType={Employee}
enableFiltering={false} // Hides "Filter" menu items from column headers
enableOrdering={false} // Hides "Sort" menu items from column headers
enableColumnPinning={false} // Hides "Pin" menu items from column headers
enableColumnResizing={false} // Hides "Resize" menu item from column headers
enableColumnConfig={false} // Hides "Column configuration" menu item from column headers
/>;

Row Selection

PropTypeDefaultDescription
selectionMode"single" | "multiple" | "none""none"Selection mode. "multiple" shows checkboxes
selectedRowsPrimaryKeyType<Q>[]-Selected rows (controlled mode)
isAllSelectedboolean-Indicates all rows are selected (controlled mode only)
onRowSelection(selectedRowIds, isSelectAll?) => void-Required when selectedRows is provided

Interactions

PropTypeDescription
onRowClick(object) => voidCalled when a row is clicked
renderCellContextMenu(row, cellValue) => ReactNodeCustom context menu for right-click on cells

Cell Editing

The editable feature allows inline editing with validation and bulk submission capabilities. Editable cells support text inputs, number inputs, and dropdown selectors.

PropTypeDescription
editMode"always" | "manual"Controls edit mode behavior. "always": Table is always in edit mode. "manual": User toggles edit mode on/off. Default: "manual"
onCellValueChanged(info: CellEditInfo) => voidCalled when a cell value is edited. The info object contains rowId, columnId, newValue, oldValue, and originalRowData
onSubmitEdits(edits: CellEditInfo[]) => Promise<boolean>When provided, shows a "Submit Edits" button that calls this function with all pending edits. Return true on success

Column Definitions

Column Definition Structure

type ColumnDefinition<Q, RDPs, FunctionColumns> = {
locator: ColumnDefinitionLocator<Q, RDPs, FunctionColumns>;
isVisible?: boolean; // default: true
pinned?: "left" | "right" | "none"; // default: "none"
width?: number; // Fixed width in pixels
minWidth?: number; // Minimum width
maxWidth?: number; // Maximum width
resizable?: boolean; // Allow column resizing
orderable?: boolean; // Allow column sorting
filterable?: boolean; // Allow column filtering
editable?: boolean; // Allow inline editing for this column
editFieldConfig?: EditFieldConfig; // Optional editor component config (e.g. dropdown)
validateEdit?: (value: unknown) => Promise<string | undefined>; // Custom validation function for cell edits
renderCell?: (object, locator) => React.ReactNode; // Custom cell renderer
columnName?: string; // Custom column name for the header
renderHeader?: () => React.ReactNode; // Custom header renderer (takes precedence over columnName)
};

editFieldConfig

When editable is true, columns default to a text or number input (auto-detected from the property type). Use editFieldConfig to specify a different editor component.

Supported editor components:

fieldComponentDescriptionRenders
"DROPDOWN"A select dropdown or searchable comboboxSelect (default) or Combobox (when isSearchable: true)

Without editFieldConfig, editable columns use a text input for string properties and a number input for numeric properties (double, integer, long, float, decimal, byte, short).

Dropdown fieldComponentProps:

PropTypeDefaultDescription
itemsV[](required)Available items for the dropdown
isSearchablebooleanfalseRenders a searchable combobox instead of a select
placeholderstring-Placeholder text when no value is selected
itemToStringLabel(item: V) => stringString(item)Converts an item to a display string
itemToKey(item: V) => string-Returns a unique key for each item (used as React key)
isItemEqual(a: V, b: V) => booleanObject.isCustom equality check (required when items are objects)
isMultiplebooleanfalseWhether multiple values can be selected

columnName vs renderHeader

  • columnName: If provided, this string is used as the column header text. If not provided, property columns default to the property's displayName, and other column types default to the id.
  • renderHeader: If provided, this function renders the header component. When both columnName and renderHeader are provided, renderHeader takes precedence in the table header, but columnName is still used in other places where the column name is displayed (e.g., the column configuration dialog, multi-sort dialog).

Column Locator Types

1. Property Column

Displays a property from the object type:

{
type: "property",
id: "propertyName" // Must be a valid property key
}

2. Derived Property (RDP) Column

Displays a computed property:

{
type: "rdp",
id: "customPropertyName",
creator: DerivedProperty.creator(/* ... */)
}

3. Function Column

Displays values computed by an OSDK function (query). This is equivalent to Workshop's function-backed columns. The function must accept an ObjectSet parameter and return a map of results keyed per object.

{
type: "function",
id: "columnKey", // Key in your FunctionColumns type map
queryDefinition: myQuery, // The OSDK query definition to execute
getFunctionParams: (objectSet) => ({ objectSetKey: objectSet }),
getKey: (object) => `${object.$objectType}:${object.$primaryKey}`, // The key to index the value of an object
getValue: (cellData) => cellData?.status, // Getter to extract the value from the raw data
dedupeIntervalMs: 5 * 60 * 1_000, // The stale time of your data, if multiple requests happen within this interval, no new network call will be made
}
PropertyTypeRequiredDescription
type"function"YesIdentifies this as a function column
idkeyof FunctionColumnsYesKey in the FunctionColumns type map
queryDefinitionQueryDefinitionYesThe OSDK query definition to execute
getFunctionParams(objectSet) => paramsYesComputes function parameters from the current ObjectSet
getKey(object) => stringYesGenerates a lookup key for each object in the result map
getValue(cellData?) => unknownNoExtracts a display value from the raw function result per object. cellData is undefined when the object has no result (e.g., loading or missing from the function output)
dedupeIntervalMsnumberNoMinimum time (ms) between re-fetches of the same function with the same parameters. Defaults to 300_000 (5 minutes)

4. Custom Column

Displays header and cell with the provided custom renderers.

{
type: "custom",
id: "columnName"
}

Examples

Example 1: Basic Table with Custom Column Definitions

import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
pinned: "left",
width: 200,
},
{
locator: { type: "property", id: "email" },
width: 250,
},
{
locator: { type: "property", id: "jobTitle" },
},
{
locator: { type: "property", id: "department" },
},
];

function EmployeesTable() {
return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
/>
);
}

Example 2: Table with Multiple Selection

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

function EmployeesTable() {
return (
<ObjectTable
objectType={Employee}
selectionMode="multiple"
/>
);
}

Example 3: Table with Default Sorting

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

function EmployeesTable() {
return (
<ObjectTable
objectType={Employee}
defaultOrderBy={[{
property: "firstFullTimeStartDate",
direction: "desc",
}]}
/>
);
}

Example 4: Custom Cell Rendering

import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
renderCell: (employee) => (
<strong style={{ color: "blue" }}>
{employee.fullName}
</strong>
),
},
{
locator: { type: "property", id: "firstFullTimeStartDate" },
renderCell: (employee) => {
const date = employee.firstFullTimeStartDate;
return date ? new Date(date).toLocaleDateString() : "-";
},
},
];

function EmployeesTable() {
return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
/>
);
}

Example 5: Custom Header Rendering

import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
renderHeader: () => (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span>👤</span>
<span>Employee Name</span>
</div>
),
},
];

function EmployeesTable() {
return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
/>
);
}

Example 6: Hidden Columns

Use isVisible: false to define columns that are hidden by default but can be toggled visible by the user:

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
},
{
locator: { type: "property", id: "email" },
},
{
locator: { type: "property", id: "jobTitle" },
isVisible: false, // Hidden by default
},
];

Example 7: Context Menu on Cell Right-Click

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

function EmployeesTable() {
const renderCellContextMenu = (employee: Employee, cellValue: unknown) => (
<div
style={{
background: "white",
border: "1px solid #ccc",
borderRadius: "4px",
padding: "8px",
}}
>
<div onClick={() => console.log("View", employee.fullName)}>
View Details
</div>
<div onClick={() => console.log("Edit", employee.fullName)}>
Edit Employee
</div>
<div onClick={() => navigator.clipboard.writeText(String(cellValue))}>
Copy Value
</div>
</div>
);

return (
<ObjectTable
objectType={Employee}
renderCellContextMenu={renderCellContextMenu}
/>
);
}

Example 8: Row Click Handler

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";
import { useRouter } from "next/router";

function EmployeesTable() {
const router = useRouter();

const handleRowClick = (employee: Employee) => {
router.push(`/employees/${employee.$primaryKey}`);
};

return (
<ObjectTable
objectType={Employee}
onRowClick={handleRowClick}
/>
);
}

Example 9: Filtering on Object Properties and Derived Properties (RDPs)

You can filter by object properties and derived properties by including them in the WhereClause:

import { DerivedProperty } from "@osdk/client";
import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

type RDPs = {
managerName: string | undefined;
};

const columnDefinitions: Array<ColumnDefinition<typeof Employee, RDPs>> = [
{
locator: { type: "property", id: "fullName" },
},
{
locator: {
type: "rdp",
id: "managerName",
creator: DerivedProperty.creator<typeof Employee, string | undefined>(
(base) =>
base.lead.select({
fullName: true,
}),
(pivot) => pivot?.fullName,
),
},
renderHeader: () => <span>Manager</span>,
},
];

function EmployeesWithManagerTable() {
return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
filter={{
fullName: { $containsAnyTerm: "Paul" },
managerName: { $eq: "Jane Smith" },
}}
/>
);
}

Example 10: Controlled Sorting

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";
import { useState } from "react";

function EmployeesTable() {
const [orderBy, setOrderBy] = useState<
Array<{
property: keyof Employee;
direction: "asc" | "desc";
}>
>([
{ property: "fullName", direction: "asc" },
]);

return (
<ObjectTable
objectType={Employee}
orderBy={orderBy}
onOrderByChanged={setOrderBy}
/>
);
}

Example 11: Controlled Row Selection

import { ObjectTable } from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";
import { useState } from "react";

function EmployeesTable() {
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const [isAllSelected, setIsAllSelected] = useState(false);

const handleRowSelection = (
selectedRowIds: string[],
isSelectAll?: boolean,
) => {
if (isSelectAll) {
if (selectedRowIds.length === 0) {
setIsAllSelected(false);
setSelectedRows([]);
} else {
setIsAllSelected(true);
setSelectedRows([]);
}
} else {
setIsAllSelected(false);
setSelectedRows(selectedRowIds);
}
};

return (
<div>
<div>Selected: {selectedRows.length} employees</div>
<ObjectTable
objectType={Employee}
selectionMode="multiple"
selectedRows={selectedRows}
isAllSelected={isAllSelected}
onRowSelection={handleRowSelection}
/>
</div>
);
}

Key points about select all behavior:

  • The isSelectAll parameter in onRowSelection indicates whether the change was triggered by the "select all" checkbox
  • When isAllSelected is true, the table shows all rows as selected regardless of the selectedRows array content
  • This allows efficient handling of "select all" without loading all object IDs
  • Individual row selections automatically set isAllSelected to false

Example 12: Custom Column Type

In a custom column type, you can render anything in the column by passing in renderHeader and renderCell props.

import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: {
type: "custom",
id: "Custom Column",
},
renderHeader: () => "Custom",
renderCell: (object: Osdk.Instance<Employee>) => {
return (
<button onClick={() => alert(`Clicked ${object["$title"]}`)}>
Click me
</button>
);
},
orderable: false,
},
];

function EmployeesTable() {
return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
/>
);
}

Example 13: Editable Table

Enable inline editing with validation, dropdown selectors, and bulk submission:

import { useOsdkAction } from "@osdk/react";
import {
type CellEditInfo,
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee, updateMultipleEmployees } from "@YourApp/sdk";

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
editable: true, // Default text input
},
{
locator: { type: "property", id: "email" },
editable: true,
validateEdit: async (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value as string)
? undefined
: "Please enter a valid email address";
},
},
{
locator: { type: "property", id: "department" },
editable: true,
editFieldConfig: {
fieldComponent: "DROPDOWN",
fieldComponentProps: {
items: [
"Engineering",
"Product",
"Design",
"Sales",
"Marketing",
"Finance",
"Human Resources",
],
},
},
},
{
locator: { type: "property", id: "jobTitle" },
editable: true,
editFieldConfig: {
fieldComponent: "DROPDOWN",
fieldComponentProps: {
items: [
"Software Engineer",
"Senior Software Engineer",
"Staff Engineer",
"Engineering Manager",
"Product Manager",
"Designer",
],
isSearchable: true, // Renders a searchable combobox
placeholder: "Search job titles…",
},
},
},
];

function EditableEmployeesTable() {
const { applyAction } = useOsdkAction(updateMultipleEmployees);

const handleCellValueChanged = (
info: CellEditInfo<Employee>,
) => {
console.log("Cell edited:", {
rowId: info.rowId,
columnId: info.columnId,
oldValue: info.oldValue,
newValue: info.newValue,
originalRowData: info.originalRowData,
});
};

// When onSubmitEdits is provided, a "Submit Edits" button appears in the table
const handleSubmitEdits = async (edits: CellEditInfo<Employee>[]) => {
try {
// Transform edits array into format expected by your action
const updates = edits.map(edit => ({
employeeId: edit.rowId,
field: edit.columnId,
value: edit.newValue,
}));

await applyAction({ updates });

// Return true to indicate successful submission
return true;
} catch (error) {
console.error("Failed to save edits:", error);
// Return false or throw to indicate failure
return false;
}
};

return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
editMode="manual" // User can toggle edit mode on/off
onCellValueChanged={handleCellValueChanged}
onSubmitEdits={handleSubmitEdits} // Shows "Submit Edits" button
/>
);
}

Key features of editable tables:

  1. Edit Modes:

    • manual (default): User clicks "Edit Table" button to enter edit mode
    • always: Table is always in edit mode
  2. Editor Components:

    • Text input (default): For string properties
    • Number input (auto-detected): For numeric properties (double, integer, long, float, decimal, byte, short)
    • Dropdown (Select): Fixed list of options via editFieldConfig with fieldComponent: "DROPDOWN"
    • Dropdown (Combobox): Searchable list via isSearchable: true in fieldComponentProps
  3. Validation:

    • Use validateEdit on columns for async validation
    • Validation errors are shown with an error icon and tooltip
    • Works with all editor types including dropdowns
  4. Edit State Management:

    • Edits are tracked locally until submitted
    • Modified cells are visually highlighted
    • "Cancel" button discards all pending edits
  5. Bulk Submission:

    • When onSubmitEdits is provided, a "Submit Edits" button appears
    • All edits are submitted together
    • Return true from onSubmitEdits to clear edits after successful submission

Example 14: Custom Column Configuration Dialog

Use the ColumnConfigDialog component to create a custom column configuration experience:

import {
ColumnConfigDialog,
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";
import { useCallback, useMemo, useState } from "react";

const initialColumnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
columnName: "Full Name",
},
{
locator: { type: "property", id: "emailPrimaryWork" },
columnName: "Email",
},
{
locator: { type: "property", id: "jobTitle" },
columnName: "Job Title",
},
{
locator: { type: "property", id: "department" },
columnName: "Department",
},
{
locator: { type: "property", id: "businessTitle" },
columnName: "Business Title",
isVisible: false, // Hidden by default
},
];

function EmployeesTable() {
const [isColumnConfigOpen, setIsColumnConfigOpen] = useState(false);
const [columnDefinitions, setColumnDefinitions] = useState(
initialColumnDefinitions,
);

// Build column options for the dialog
const columnOptions = useMemo(
() =>
initialColumnDefinitions.map((colDef) => ({
id: colDef.locator.id,
name: colDef.columnName || colDef.locator.id,
})),
[],
);

// Track current visibility state
const currentVisibility = useMemo(() => {
const visibility: Record<string, boolean> = {};
initialColumnDefinitions.forEach((colDef) => {
visibility[colDef.locator.id] = columnDefinitions.some(
(def) => def.locator.id === colDef.locator.id,
);
});
return visibility;
}, [columnDefinitions]);

// Track current column order
const currentColumnOrder = useMemo(
() => columnDefinitions.map((colDef) => colDef.locator.id),
[columnDefinitions],
);

const handleApplyColumnConfig = useCallback(
(columns: Array<{ columnId: string; isVisible: boolean }>) => {
const newColumnDefinitions: Array<ColumnDefinition<typeof Employee>> = [];

// Apply the new visibility and order
columns.forEach(({ columnId, isVisible }) => {
if (isVisible) {
const colDef = initialColumnDefinitions.find(
(def) => def.locator.id === columnId,
);
if (colDef) {
newColumnDefinitions.push(colDef);
}
}
});

setColumnDefinitions(newColumnDefinitions);
setIsColumnConfigOpen(false);
},
[],
);

return (
<>
<div style={{ marginBottom: "16px" }}>
<button
onClick={() => setIsColumnConfigOpen(true)}
>
Configure Columns
</button>
</div>

<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
enableColumnConfig={false} // Disable built-in config since we're using custom
/>

<ColumnConfigDialog
isOpen={isColumnConfigOpen}
onClose={() => setIsColumnConfigOpen(false)}
columnOptions={columnOptions}
currentVisibility={currentVisibility}
currentColumnOrder={currentColumnOrder}
onApply={handleApplyColumnConfig}
/>
</>
);
}

This example demonstrates:

  • Using the ColumnConfigDialog component for custom column management
  • Tracking column visibility and order in component state
  • Providing a custom button to open the dialog
  • Disabling the built-in column configuration to avoid conflicts
  • Managing hidden columns that can be toggled visible by users

Example 15: Function-Backed Columns

Display values computed by OSDK functions (queries) alongside regular property columns. Function columns automatically handle loading states, caching, and deduplication.

import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee, getEmployeeMetrics } from "@YourApp/sdk";

// Define a type map for your function columns
type EmployeeFunctionColumns = {
metrics: typeof getEmployeeMetrics;
};

const columnDefinitions: Array<
ColumnDefinition<
typeof Employee,
Record<string, never>,
EmployeeFunctionColumns
>
> = [
{
locator: { type: "property", id: "fullName" },
pinned: "left",
width: 200,
},
{
locator: { type: "property", id: "department" },
},
{
locator: {
type: "function",
id: "metrics",
queryDefinition: getEmployeeMetrics,
// Pass the current object set as a parameter to the function
getFunctionParams: (objectSet) => ({ employees: objectSet }),
// Generate a unique key for each object to look up its result
getKey: (employee) => `${employee.$objectType}:${employee.$primaryKey}`,
// Extract the specific value to display from the function result
getValue: (cellData) =>
(cellData as { score: number } | undefined)?.score,
// Cache results for 1 minute instead of the default 5
dedupeIntervalMs: 60_000,
},
columnName: "Performance Score",
},
];

function EmployeesWithMetricsTable() {
return (
<ObjectTable
objectType={Employee}
columnDefinitions={columnDefinitions}
/>
);
}

Multiple function columns sharing the same queryDefinition are automatically deduplicated into a single API call. Use different getValue functions to extract different fields from the same result:

const columnDefinitions: Array<
ColumnDefinition<
typeof Employee,
Record<string, never>,
EmployeeFunctionColumns
>
> = [
{
locator: {
type: "function",
id: "metrics",
queryDefinition: getEmployeeMetrics,
getFunctionParams: (objectSet) => ({ employees: objectSet }),
getKey: (emp) => `${emp.$objectType}:${emp.$primaryKey}`,
getValue: (cellData) => (cellData as { score: number })?.score,
},
columnName: "Score",
},
{
locator: {
type: "function",
id: "metrics",
queryDefinition: getEmployeeMetrics,
getFunctionParams: (objectSet) => ({ employees: objectSet }),
getKey: (emp) => `${emp.$objectType}:${emp.$primaryKey}`,
getValue: (cellData) => (cellData as { rank: string })?.rank,
},
columnName: "Rank",
},
];

Column Pinning

Pin columns to the left or right side of the table:

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
pinned: "left", // Stays visible when scrolling horizontally
},
{
locator: { type: "property", id: "email" },
pinned: "right",
},
];

Column Resizing

Control whether columns can be resized:

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
resizable: true,
minWidth: 150,
maxWidth: 500,
},
];

Listen to resize events:

<ObjectTable
objectType={Employee}
onColumnResize={(columnId, newWidth) => {
console.log(`Column ${columnId} resized to ${newWidth}px`);
}}
/>;

Disable Filtering or Sorting

Disable filtering or sorting globally:

<ObjectTable
objectType={Employee}
enableColumnPinning={false}
enableColumnResizing={false}
enableColumnConfig={false}
enableOrdering={false}
/>;

Or per column:

const columnDefinitions: Array<ColumnDefinition<typeof Employee>> = [
{
locator: { type: "property", id: "fullName" },
orderable: false,
filterable: false,
},
];

Custom Row Height

Adjust row height for better readability:

<ObjectTable
objectType={Employee}
rowHeight={56} // Larger rows for more content
/>;

Loading and Empty States

The ObjectTable automatically handles:

  • Loading state: Shows skeleton rows while data is loading
  • Empty state: Shows appropriate message when no data matches filters
  • Error state: Displays error messages if data fetching fails

No additional configuration needed - these states are built-in!

Infinite Scrolling

The ObjectTable automatically implements infinite scroll pagination, with page size of 50. As users scroll down, more data is loaded seamlessly. No configuration required!

TypeScript Tips

Type-Safe Column Definitions

Use TypeScript generics to ensure type safety:

import {
type ColumnDefinition,
ObjectTable,
} from "@osdk/react-components/experimental/object-table";
import { Employee } from "@YourApp/sdk";

type RDPs = {
managerName: string | undefined;
yearsOfService: number;
};

const columnDefinitions: Array<ColumnDefinition<typeof Employee, RDPs>> = [
// TypeScript will validate property names and types
{
locator: { type: "property", id: "fullName" }, // ✅ Valid
},
{
locator: { type: "property", id: "invalidProp" }, // ❌ Type error
},
];

Inferring Types from Object Type

Let TypeScript infer types from your OSDK object type:

import type { PropertyKeys } from "@osdk/client";
import { Employee } from "@YourApp/sdk";

// PropertyKeys gives you all valid property names
type EmployeeProps = PropertyKeys<typeof Employee>;

Troubleshooting

Table not displaying data

  • Ensure your OSDK client is properly configured
  • Check that the object type is imported correctly
  • Verify network requests in browser DevTools

Type errors with columnDefinitions

  • Ensure you're using the correct type parameters: ColumnDefinition<typeof YourObjectType, RDPs, FunctionColumns>
  • Property IDs must exactly match property names from your object type

Selection not working

  • Ensure selectionMode is set to "single" or "multiple"
  • For controlled mode, provide both selectedRows and onRowSelection

Custom rendering not appearing

  • Ensure renderCell returns valid React elements
  • Check browser console for errors in your render function

Table has no styling or looks broken

  • Ensure you've imported @osdk/react-components/styles.css in your main CSS file
  • Check that the CSS import is in the correct location (application entry point)
  • Check browser DevTools to confirm CSS custom properties are loaded

Theming

The ObjectTable (and all OSDK components) can be themed using CSS custom properties included in @osdk/react-components/styles.css.

Understanding Token Scopes

OSDK Tokens (--osdk-*)

  • All tokens used in OSDK components are prefixed with --osdk-
  • Any Blueprint token used in OSDK components is mapped to an --osdk-* token
  • Override these to theme OSDK components only
  • Safe to customize without affecting other Blueprint components in your app

Blueprint Tokens (--bp-*)

  • Core design tokens from Blueprint design system
  • Override these to theme both Blueprint and OSDK components
  • Use this for consistent theming across your entire application

Customization Strategies

1. Override OSDK Tokens Only

Change OSDK component styling without affecting other Blueprint components in your app:

@layer osdk.styles, user.theme;

@import "@osdk/react-components/styles.css" layer(osdk.styles);

@layer user.theme {
:root {
/* Only affects OSDK table headers */
--osdk-table-header-bg: #f0f0f0;
--osdk-table-border-color: #e0e0e0;
--osdk-table-row-hover-bg: #f9fafb;

/* Only affects OSDK components using primary intent */
--osdk-intent-primary-rest: #2563eb;
--osdk-intent-primary-hover: #1d4ed8;
}
}

2. Override Blueprint Tokens

Change both Blueprint and OSDK components for consistent theming:

@layer osdk.styles, user.theme;

@import "@osdk/react-components/styles.css" layer(osdk.styles);

@layer user.theme {
:root {
/* Affects ALL components (Blueprint + OSDK) using primary intent */
--bp-intent-primary-rest: #2563eb;
--bp-intent-primary-hover: #1d4ed8;
--bp-intent-primary-active: #1e40af;

/* Affects all spacing and borders across the design system */
--bp-surface-spacing: 8px;
--bp-surface-border-radius: 8px;
}
}

3. Scoped Overrides for Specific Tables

Apply custom styles to specific ObjectTable instances using the className prop:

// Component
<ObjectTable
objectType={Employee}
className="custom-employee-table"
/>;
/* Styles */
.custom-employee-table {
--osdk-table-header-bg: #1e40af;
--osdk-table-header-text-color: white;
--osdk-table-row-hover-bg: #dbeafe;
}

Common Theming Examples

Dark Mode

@layer user.theme {
[data-theme="dark"] {
--osdk-table-header-bg: #1f2937;
--osdk-table-border-color: #374151;
--osdk-table-row-hover-bg: #374151;
--osdk-surface-bg: #111827;
--osdk-text-primary: #f9fafb;
}
}

Compact Table

.compact-table {
--osdk-surface-spacing: 4px;
--osdk-table-cell-padding: 8px;
}
<ObjectTable
objectType={Employee}
className="compact-table"
rowHeight={32}
/>;

Custom Brand Colors

@layer user.theme {
:root {
/* Use your brand's primary color */
--bp-intent-primary-rest: #7c3aed;
--bp-intent-primary-hover: #6d28d9;
--bp-intent-primary-active: #5b21b6;
}
}

Available CSS Variables

For a complete reference of all available CSS tokens for theming, see:

Accessibility Note

When overriding theme tokens, ensure your custom colors meet accessibility standards:

  • Color contrast ratios (WCAG AA): 4.5:1 for normal text, 3:1 for large text
  • Readable text on all background colors
  • Clear visual distinction between interactive states (rest, hover, active, disabled)

The default tokens are designed to meet WCAG AA standards.

Additional Resources