Skip to main content
← Back to Table of Contents

Summary

Select one value or build a searchable multiselect with chips, checklists, images and descriptions. Large local catalogs use virtualized lists; remote catalogs can load one page at a time. On small screens, the dropdown becomes a bottom sheet.

Choose the presentation

Sizes, surfaces, rich rows, grids and custom views.

Work with large catalogs

Local virtualization or paginated server search.

Save valid selections

Allowed values, limits, relationships and database casts.
Works with all standard Filament field APIs: required(), disabled(), hidden(), live(), afterStateUpdated(), validation rules, etc. This guide covers the FLEX-specific configuration methods and practical Select workflows. Shared form, schema and action methods remain available through inheritance; see the Filament Select reference for the wider inherited API.

Basic usage

Standard select with rich options

Multi-select with chips

By default, multi-select removes chosen values from the dropdown list (pick-from-remaining / chip mode).

Email recipients (two-line options + chip labels)

Use chipLabel (or chip_label) on rich options when the dropdown should show name + email, but selected chips should show only the email:
The dropdown row renders the full two-line layout; selected chips use the compact chipLabel text.

Custom value (optionView())

Use optionView() when you need full control over option HTML — custom Blade per row and trigger (server-side equivalent of render props). Pair with optionTriggerView() when the closed trigger should differ from the dropdown row.
Each view receives $option, $layout (list, trigger, grid, chip), $field, $value, $label, $description, and any data from optionViewData(). Here the custom views can read $showEmail. Pass a closure to optionViewData() when that extra data depends on the current record or form. Your custom view is responsible for displaying its own markup; keep application text escaped with Blade’s {{ }} output. Use richListTriggerDisplay() to keep the full list row (avatar + name + email) in the closed trigger instead of the compact trigger layout.
Closure form — return HTML, Htmlable, View, or a nested view name:
optionView() automatically enables allowHtml() and sanitizes output through the package HTML sanitizer.

Rich relationship options

Use getRichOptionFromRecordUsing() to return a rich option array for an Eloquent relationship. FLEX renders the dropdown, search results and saved selection without application calls to transformRichOptionsForJs():
Relationship owner keys other than id are supported; the option value follows the native relationship key. Cached searches retain the same rich metadata after rendering a selected value. The callback accepts the same rich shape as options(), including icons, images, badges and chip_label. It can be configured before or after relationship(). Default selected labels remain compact; richListTriggerDisplay() preserves the full list row. Text remains escaped unless you explicitly enable allowHtml(). This is an alternative to the string-returning getOptionLabelFromRecordUsing() callback. Relationship query constraints, validation and persistence remain owned by Filament and the application. Existing getOptionLabelUsing() callbacks still return strings.

Multi-select checklist (keep options visible)

Use keepSelectedOptionsInDropdown() when selected options should stay in the list with checkmarks (dropdown stays open while toggling):
On single-select fields this setting has no effect.

State & validation

Stored value

State is the option key from options().

Default state

The field defaults to null (single) or [] (multiple).

Validation rules


Configuration API

The signatures below show which settings accept closures. Closures use Filament’s utility injection where supported.

variant()

optionLayout()

Use grid for a multi-column visual picker:

inlineSearch()

Recommended for single-select searchable fields to keep the UI compact — the search field is the trigger, not a separate label swap:
When closed, the trigger input shows the selected label. When focused or open on desktop, the same input stays editable and keeps that label until you type or clear it; clearing the input clears the selection. On mobile (bottom sheet / drawer), search moves into the sheet header — the trigger cannot accept keyboard input while the drawer covers it. Use the default field clear (×) next to the chevron to reset the value — inline mode does not render a separate search-query × on desktop (that control exists in the dropdown/sheet search header). Ignored with multiple(): hasInlineSearch() is false when the field is multi-select, even if you chain inlineSearch(). Multi-select fields should use dropdown / sheet header search (searchable() without relying on inlineSearch()). For RTL layouts, set extraAttributes(['dir' => 'rtl']) on the field — the teleported panel and mobile sheet (bottom drawer) both copy the trigger writing direction onto the menu (dir="rtl"), so search icons, checkmarks (inset-inline-end), and option text mirror correctly. Search inputs use dir="auto" so Hebrew/Arabic queries get an RTL caret while Latin queries keep a normal LTR caret. Works with entityMentions() — type @ in the inline search input or press @ on the closed trigger to start a mention query.

Field label, accent and rounding

Keep the label inside the field and choose its surface, accent and corner shape independently:
inlineFieldLabel(false) restores the outer label. color(null) restores the default accent. rounding(null) uses the global ui.field_rounding setting; rounding('full') creates a pill. The outer focus outline is enabled by default; focusOutline(false) disables it.

Secondary text beside the option label

The default below places the description beneath the label. trailing puts it at the logical end of the row, including RTL layouts. This configures the shared rich-option renderer; custom optionView() markup controls its own layout.

Smart suggest (recentOptions, suggestedOptions, allowCreateOption)

Pin curated rows at the top of the dropdown and optionally allow creating a new value from the current search:
Requires searchable(). When the query has no exact label match, a Create “…” row appears at the top of the list (label from filament-flex-fields::default.select_field.smart_suggest.create). Choosing that row commits the trimmed search string as the field value — same string for both value and label. No modal opens and no PHP callback runs at click time. With allowCreateOption() enabled, a new text value does not have to match an existing option. The server still validates the selection structure. Add application rules for the text and its length:
Persist created values after your form has been validated. Inline creation changes the field value; it does not insert a database record. This example limits each label and the number of selections:
In your application’s save action, authorize the operation and persist the validated labels within the transaction used for the form. If you need a numeric model key, prefer the modal flow below.

Create option: inline vs modal (which API?)

Choose inline creation for a text value, or a modal for a record with its own fields and validation:
A create modal does not require extra fields. This example asks only for a name, validates it and returns the new author’s key. Author is your application’s model.
The model must allow creation with only name (including any required database columns). The newly created record becomes the selected option.
Use the inline flow for free-text values. Use a modal when a new record needs validated fields, permissions or a database-generated key.

Entity mentions (entityMentions())

Async people/entity picker triggered with @ in the closed trigger, inline search input, or dropdown search:
Selected mention chips show the mention prefix. Use getOptionLabelsUsing() to resolve saved selections in a multiselect, or getOptionLabelUsing() for a single selection.

Real-world examples

User select with avatars

Multi-select tags in a Section


Grouped sections

Nest option groups in options() to render section headers in the dropdown. Enable explicit dividers between groups (default on):
Use optionGroupSeparators(false) to fall back to header-only dividers.

Disabled options

Disable specific keys while keeping them visible in the list:
disableOptionWhen() remains available for dynamic rules.

Async search with load more

For large remote datasets, paginate search results and append rows as the user scrolls:
Define CharacterRepository::search() in your application. Each item contains a stable value and its display label. Rich rows may also contain description, image, icon and disabled. For example, the final page can return:
When more pages exist, return hasMore => true and a nonempty next cursor. Keep that cursor opaque, stable and advancing; never return the previous cursor. Return at most $pageSize option records. Group records may use ['label' => 'Group name', 'options' => [...]]; their option leaves share the same page-size limit. The selected-label resolver must authorize and resolve the same catalog. The trigger shows a loading indicator while the first page loads or while a new query is debounced. A footer spinner appears while additional pages load. Closure-based options() are resolved when the menu opens. A saved selection may need a label before opening: provide getOptionLabelUsing() or getOptionLabelsUsing() rather than fetching an entire catalog just to display its selected value.
Pagination note: true “load more” paging requires getSearchResultsPageUsing(). Without it, the field may still call a non-paginated search callback and will set hasMore=false after the first page so the client does not spin forever.
Search rate limit: Livewire search endpoints for Select / Tags / IconPicker are capped by filament-flex-fields.select.search_rate_limit_per_minute (default 60/min per actor+field). The limiter keys on the authenticated user id when present, otherwise Request::ip() — configure Laravel TrustedProxies correctly behind a load balancer so spoofed X-Forwarded-For headers are not trusted.

FlexFieldFormBuilder / Studio config

SelectFieldConfigurator (Studio / FlexFieldFormBuilder) maps these config keys onto the fluent API: PHP-only (not configured from Studio): async search (getSearchResultsUsing(), getSearchResultsPageUsing(), paginatedSearchResults()), relationship(), dependsOn(), create/edit option modals, and other Livewire search contracts. Wire those on the field in PHP. variant('item-card') defaults clearable off unless clearable is set explicitly in config or via clearable().

Playground

/admin/flex-fields/select-field Open the live Select demo, or follow Installation to enable the local playground.

Filament Select parity

SelectField extends Filament\Forms\Components\Select. Every public Select API from the Filament docs is available with the same method names. The headless Alpine/Livewire UI wires behaviour that Filament’s JS select would otherwise handle. Out of scope for this field (separate Filament components): MorphToSelect, ModalTableSelect. Use those classes when you need morph type pickers or table-in-modal selection.

Coverage matrix (Filament Select docs → SelectField)

Basic options & JS select

Searching & custom messages

Every searchable dropdown receives a translated placeholder by default (Start typing to search... in English). Use searchPrompt() to replace the search text. placeholder() separately controls the closed field when no option is selected; it does not replace the dropdown’s search placeholder.
The same setting works with remote results and inlineSearch(), including the mobile search header. A closure may supply a context-dependent prompt. An explicitly empty searchPrompt('') hides the text intentionally. To search by keys as well as labels:

Multi-select, reorder, min/max items

For custom async multi-select labels use getOptionLabelsUsing() (plural), same as Filament.

Grouped options

Relationship (+ preload, create/edit modals, pivot)

Inline smart-suggest create (allowCreateOption()) is documented under Create option: inline vs modal: one-field create from the search string, no modal, no automatic DB insert. Use createOptionForm() / createOptionUsing() when the new record needs multiple fields or a server-side primary key. Call disabled() before relationship() on multi relationship selects (Filament requirement).

HTML labels, wrap, placeholder, disabled options, affixes, boolean, position

Cascading options (dependsOn)

Until the parent has a value, the region list is correctly empty. Choosing Poland → Mazowieckie and then changing the country to United States clears the region immediately through resetOnDependencyChange(). The next region list contains California and Texas. dependsOn() refreshes dependent choices; it does not decide whether an existing selection should be preserved. Enable resetOnDependencyChange() on the child for an immediate browser reset, including a non-clearable field. It clears a single selection to null and a multiselect to []. It is off by default so dependencies that only change labels or ordering do not unexpectedly clear data. Keep the parent’s explicit PHP reset callback for server-driven state updates and the server-side form lifecycle. For a dependent multiselect, use $set('region', []) instead of null. Server-side Select validation also rejects a submitted region that is not in the current country’s allowed options. A client reset is not a replacement for validation or authorization. Use dependsOn(['country', 'service'], fn (?string $country, ?string $service): array => ...) for several dependencies; callback arguments follow the path order. Paths are relative to the field’s schema, including repeater items. Reset a child’s previous selection when it no longer belongs to the parent’s allowed choices. If only particular fields need a refresh, use Filament’s partial-render configuration for those fields. Skip rendering only when no other visible content depends on the changed value.

Large catalogs and virtualization

Use options() when the catalog is already available locally. Virtualization reduces mounted rows; it does not avoid sending the catalog to the browser.
$productOptions is an application-provided key/label map. The default threshold is 100 projected rows. Rows are measured as they appear, and the visible window follows the viewport. A grid uses measured grid-row geometry as well.optionsLimit(50) caps the display of a small nonvirtualized result set. Once virtualization is active, the full matching collection stays navigable; the mounted window controls the DOM budget.
Menus fit within the viewport. Automatic placement chooses above or below the trigger according to available space. position('top') and position('bottom') request a particular side. Width and height settings are bounded; unsupported values produce a configuration error rather than an oversized panel. On mobile, the sheet owns its own responsive layout.

Search limits and cache

Publish/configure config/filament-flex-fields.php when you need different limits:
The server cache belongs to one field and one request. It avoids repeated provider work within that request; it is not a cross-request cache. Cache identities include the field, record, configured dependencies and provider. For context that can change within a request, supply its revision:
This adds cache context; it does not apply tenant authorization to the query. Changing options() invalidates the field’s request cache. invalidateSelectSearchCache() is available when your application changes a provider’s result within the same request. Page payloads must be JSON-compatible and remain within the configured size. Cursors must progress: repeated/cyclic cursors and malformed responses are rejected. On a failed load, already displayed options remain available and the menu provides retry feedback.

Icons and compact controls

Icons use the package’s shared icon rendering. Passing null to an icon setter restores its configured default.
Use plainTrigger() when embedding a small select inside another control. selectionIndicator(false) removes the selected-row marker without changing the selected value.

Keyboard, mobile and RTL

Home and End inside a text input retain their normal caret behavior. Disabled rows cannot be selected. Multi-select checklists remain open while items are toggled; chips can be removed individually. On narrow viewports, the menu becomes a sheet with its own search area. Open lists respond to window resizing and use touch-sized rows. The menu carries the field’s writing direction:

Saving selections to your database

Save a single selection to a suitable scalar column. The option key is saved, not its display label. A multiselect without a relationship can be saved in a JSON column with an array cast:
For relationship('tags', 'name')->multiple(), Filament saves the relationship; a JSON column is unnecessary. pivotData() can supply additional pivot attributes. Always authorize related records in your application. Use the form’s validated state when saving. For a constrained multiselect:
Minimum counts are checked on submit; maximum counts are checked on the server and block additional picks in the menu. A disabled control does not replace server authorization.

Troubleshooting

Server search uses searchDebounce() (1000 ms by default) before sending a request. Lower it deliberately, for example to 250 ms. Bound database queries and check their indexes. Remote results also depend on network and server response time.
For remote options, provide getOptionLabelUsing() for a single value or getOptionLabelsUsing() for a multiselect. These resolvers must also be able to resolve saved records outside the current result page.
Check whether the option is now disabled, no longer belongs to the dependent catalog, or is excluded by your relationship scope. Reset invalid child selections when their parent changes.
allowCreateOption() accepts a new text identity; createOptionForm() creates a record through a validated modal. Use the flow that matches your database column. Additional application rules still apply.
The builder configuration table lists the supported declarative keys. Callbacks, relationships, pagination and dependencies belong in PHP. Do not assume every fluent method has a JSON key.
For shared loading behavior, see Asset loading.
Last modified on October 29, 2026