Lookup Field
Reference field for linking to other objects/records
The Lookup Field component provides a reference field for creating relationships between objects and records. It supports dynamic data loading from a DataSource with debounced search, loading/error/empty states, keyboard navigation, and optional quick-create entry.
Basic Usage
Basic Lookup
Multiple References
Multi Select Lookup
Field Schema
A lookup field is authored as LookupFieldMetadata (@object-ui/types), which is the
source of truth for the key set: it extends BaseFieldMetadata with the reference
target, the display and id fields, an optional static option list, and the Record
Picker's column and paging configuration. Static options are SelectOptionMetadata,
and picker columns are LookupColumnDef — both exported, both checked here.
import type { LookupFieldMetadata } from '@object-ui/types';
const accountId: LookupFieldMetadata = {
type: 'lookup',
name: 'account_id',
label: 'Account',
placeholder: 'Search accounts…',
required: true,
reference_to: 'accounts',
reference_field: 'name',
descriptionField: 'industry',
idField: '_id',
multiple: false,
searchable: true,
allow_create: true,
// Record Picker dialog (Enterprise): columns accept a field name or a descriptor.
lookup_columns: ['name', { field: 'industry', label: 'Industry', width: '160px' }],
lookup_page_size: 10,
lookupFilters: [{ field: 'active', operator: 'eq', value: true }],
};When no data source is available the field falls back to a static option list:
import type { LookupFieldMetadata } from '@object-ui/types';
const priority: LookupFieldMetadata = {
type: 'lookup',
name: 'priority',
label: 'Priority',
reference_to: 'priorities',
options: [
{ label: 'High', value: 'high' },
{ label: 'Normal', value: 'normal' },
],
};A static option may also carry a description — secondary text the picker's
typeahead searches alongside the label. It is a declared SelectOptionMetadata
member (declared for exactly that consumption —
objectui#6153 — and
aligned with @objectstack/spec's SelectOptionSchema.description). The
dataSource a host injects and the onCreateNew callback it passes are widget props,
not metadata.
The value being edited, and the className / disabled a host supplies, are not
metadata keys — they are runtime widget props. See Field Widget Props.
Dynamic Data Source
When a DataSource is available (via SchemaRendererContext, explicit prop, or field config), the Lookup popup automatically fetches records from the referenced object:
// Automatic — DataSource from SchemaRendererContext
// (works out-of-the-box in ObjectForm, DrawerForm, etc.)
{
type: 'lookup',
name: 'customer',
label: 'Customer',
reference_to: 'customers',
reference_field: 'name', // Display field (default: 'name')
descriptionField: 'industry', // Optional secondary field
}The popup will:
- Fetch records via
dataSource.find(reference_to, { $top: 50 })on open - Send
$searchqueries with 300ms debounce as the user types - Show loading spinner, error state with retry, and empty state
- Display "Showing X of Y" when more records exist than the page size
- Show a "Show All Results" button (inside the popover) to open the full Record Picker dialog when total exceeds page size
Browse All Button
Every Lookup field with a dataSource always renders a "Browse All" button (table icon) next to the quick-select trigger. This button opens the full RecordPickerDialog directly, regardless of dataset size — ensuring enterprise features like multi-column tables, sort/filter bar, and cell renderers are always discoverable.
- Always visible when
dataSourceis configured - Opens the Record Picker dialog without needing to open the popover first
- Keyboard accessible and screen-reader friendly (
aria-label="Browse all records")
Record Picker Dialog (Enterprise)
The full RecordPickerDialog can be opened in two ways:
- "Browse All" button (table icon) — always visible next to the quick-select trigger
- "Show All Results" link inside the popover — shown when total records exceed the page size
// Configure the Record Picker with lookup_columns
{
type: 'lookup',
name: 'order',
label: 'Order',
reference_to: 'orders',
reference_field: 'order_number',
descriptionField: 'customer_name',
lookup_columns: [
{ field: 'order_number', label: 'Order #' },
{ field: 'customer_name', label: 'Customer' },
{ field: 'total_amount', label: 'Amount' },
{ field: 'status', label: 'Status' },
],
lookup_page_size: 15,
}The Record Picker dialog provides:
- Multi-column table with configurable columns via
lookup_columns - Search with debounced server-side querying
- Column sorting via clickable headers (sends
$orderbyto DataSource) - Pagination with page-by-page navigation
- Keyboard navigation — Arrow keys to move between rows, Enter/Space to select
- Single/Multi-select with visual check indicators and confirmation flow
- Responsive layout — Mobile-friendly width (95vw on small screens)
- Loading, error, and empty states
- Auto-inferred columns from
reference_fieldwhenlookup_columnsis not set
Lookup vs Master-Detail
- Lookup: Standard reference field, can be deleted independently
- Master-Detail: Parent-child relationship, deleting parent deletes children
Cell Renderer
In tables/grids, lookup values display the referenced record name:
import { LookupCellRenderer } from '@object-ui/fields';
// Single value: Display name/label
// Multiple values: Multiple chips/badgesUse Cases
- Assignments: Assign tasks to users
- Relationships: Link related records
- Categories: Reference to category objects
- Parent Records: Master-detail relationships
- Team Members: Multi-user references
Features
- Two-Level Interaction: Popover typeahead (Level 1) + full Record Picker dialog (Level 2)
- Record Picker Dialog: Enterprise-grade table with multi-column, pagination, search, sorting
- Inline Popover: Level 1 opens as anchored dropdown (non-modal) for fast typeahead
- Column Sorting: Clickable column headers with
$orderbyserver-side sort - Dynamic DataSource Loading: Automatically fetches records from referenced objects
- Search: Debounced type-ahead search with
$searchparameter - Multi-Select: Support for multiple references with confirmation flow
- Keyboard Navigation: Arrow keys to navigate rows, Enter to select in both levels
- Responsive: Mobile-friendly width, adapts to screen size
- Loading/Error/Empty States: Friendly feedback for all states
- Secondary Field Display: Show description/subtitle per option
- Quick-Create Entry: Optional "Create new" button when no results
- Configurable Columns:
lookup_columnsfor multi-column picker display - Base Filters:
lookupFiltersto restrict selectable records - Pagination: Page-by-page navigation in Record Picker dialog
- Backward Compatible: Falls back to static options when no DataSource