ObjectUIObjectUI
Fields

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:

  1. Fetch records via dataSource.find(reference_to, { $top: 50 }) on open
  2. Send $search queries with 300ms debounce as the user types
  3. Show loading spinner, error state with retry, and empty state
  4. Display "Showing X of Y" when more records exist than the page size
  5. 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 dataSource is 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:

  1. "Browse All" button (table icon) — always visible next to the quick-select trigger
  2. "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 $orderby to 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_field when lookup_columns is 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/badges

Use 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 $orderby server-side sort
  • Dynamic DataSource Loading: Automatically fetches records from referenced objects
  • Search: Debounced type-ahead search with $search parameter
  • 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_columns for multi-column picker display
  • Base Filters: lookupFilters to restrict selectable records
  • Pagination: Page-by-page navigation in Record Picker dialog
  • Backward Compatible: Falls back to static options when no DataSource

On this page