icetalker/filament-picker
Filament Picker adds a powerful picker form field to Filament, letting users search, browse, and select records from a table or modal instead of a plain dropdown. Great for large datasets and relation fields, with a smooth, native Filament look and feel.
Install
composer require icetalker/filament-picker
The service provider is auto-discovered on Laravel 10/11, so no manual registration is normally required. If the README lists a php artisan vendor:publish step for config or assets, run it before rendering the field for the first time:
php artisan vendor:publish --tag="filament-picker-config"
php artisan vendor:publish --tag="filament-picker-assets"
# or the generic alias if the package ships one
Where to look first
options(), multiple(), searchable(), etc.); the API surface is small but has changed between minor releases.vendor/icetalker/filament-picker/src/ — the field class (usually Picker.php) and its base class show every fluent method in one file.resources/views/ and resources/dist/ — the Blade view and the compiled JS/CSS tell you what is customizable without forking.config/filament-picker.php, if present, for defaults like grid columns or asset loading.First use case
The fastest win is replacing a Select that has visually-distinct options (colors, icons, statuses, plan tiers, avatars) with a tile/grid picker so users can see the choices rather than read them:
use Icetalker\FilamentPicker\Forms\Components\Picker;
Picker::make('status')
->options([
'draft' => 'Draft',
'review' => 'In review',
'published' => 'Published',
])
->required(),
Save it, load the form, and you should see the options rendered as pickable tiles instead of a dropdown. Confirm the stored value matches the option key — that is the single most common first-run surprise.
Single-select from a static array
The bread-and-butter case. Keys become the stored value; labels become the visible tile. Keep keys as strings — numeric keys get coerced to ints by Livewire's hydration in ways that surprise assertFormSet tests.
Picker::make('plan')
->options(Plan::query()->pluck('name', 'slug')->all())
->required(),
Multi-select
If the field exposes multiple(), the stored value becomes an array. Cast the model attribute accordingly (protected $casts = ['tags' => 'array']) and use the same method inside ->formatStateUsing() on table columns when the value is read back.
Picker::make('tags')
->options(Tag::pluck('name', 'id')->all())
->multiple()
->required(),
Live-driven dependent fields
Hook the picker into Filament's reactive form system the same way you would a Select:
Picker::make('category')
->options(Category::pluck('name', 'id')->all())
->live()
->afterStateUpdated(fn (Set $set) => $set('subcategory', null)),
Select::make('subcategory')
->options(fn (Get $get) => Subcategory::where('category_id', $get('category'))->pluck('name', 'id')->all()),
Because the picker is just another form field, $get() / $set() work identically to the built-in components. That makes migration from Select mostly mechanical.
Model-backed options with rich labels
When labels need more than a string (icons, colors, thumbnails), most picker packages let you pass a closure or a custom view. Two patterns work:
options() as [id => label] and override the tile rendering via ->view('filament-picker::custom-tile'), passing extra data through ->extraAttributes() or a public property.options() entirely and pass a pre-built array of ['value' => ..., 'label' => ..., 'icon' => ...] if the package supports structured options. Check the source for the exact shape.Table and infolist integration
Render stored values on ListRecords and Infolist by reusing the field with ->disabled() or by formatting to the label:
Picker::make('status')
->options([...])
->disabled(),
For tables, TextColumn::make('status')->formatStateUsing(fn ($state) => $options[$state] ?? $state) often reads better than embedding a form field in a cell.
Testing with Livewire
Because the picker ultimately writes a scalar (or array) to form state, tests look exactly like Select tests:
livewire(CreatePost::class)
->fillForm(['status' => 'draft'])
->call('create')
->assertHasNoFormErrors();
// and read back
livewire(EditPost::class, ['record' => $post->id])
->assertFormSet(['status' => 'published']);
If the field's JS prevents programmatic setting, fall back to call('$form.status = "draft") — but in practice fillForm works because the underlying state is just Livewire data.
resources/dist. After every composer update, if the layout looks wrong, run php artisan filament:assets and php artisan optimize:clear, then hard-reload the browser. Check config('filament.assets_path') and APP_URL — a trailing-slash mismatch breaks asset URLs inside SPA mode.options() is built with pluck(), force keys to strings with ->mapWithKeys(fn ($v, $k) => [(string) $k => $v]) to avoid "saved as int, loaded as string" bugs that only show up in Edit forms.getSearchResultsUsing() or searchable(), wire it to the server; otherwise filter the options query in the closure passed to options() based on $get('search').->options() closures re-evaluate on every render. Don't put heavy queries inside; cache with Cache::remember() keyed on the parent field value if the list is big.composer.json ("filament/filament": "^3.0") before adopting it in a v4 panel — the low star count means the maintainer may lag on v4 compatibility. Pin the version ("icetalker/filament-picker": "1.*") in composer.json so a patch release doesn't silently change the API.x-data or an Alpine ReferenceError from the picker's JS is far more common than a PHP error. Then inspect the DOM: if you see <select> markup, ->view() was overridden somewhere; if you see unstyled divs, the CSS didn't load.->view('vendor/...') to restyle tiles without forking; ->extraAttributes() to pass data-* for hooks; ->rules() / ->required() behave like any form component, so validation lives in your standard FormRequest or closure.App\Forms\Components\AppPicker that extends the package class — one place to patch if the upstream API drifts, and one place to swap in your own implementation later.How can I help you explore Laravel packages today?