Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Resource Picker Laravel Package

wotz/filament-resource-picker

A Filament form component that lets users pick records from any resource through a searchable picker. Drop it into your forms for clean relation and reference fields, with sensible defaults, simple configuration, and a smooth search experience. Lightweight and quick to set up.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and pin versions.

    composer require wotz/filament-resource-picker
    

    Because the package is tiny (2 stars, 0 dependents), lock the Filament major you are on in composer.json before installing. Check composer.json of the package for its filament/filament constraint — a ^3.0 constraint will not save you from a breaking Filament minor if the package itself is not actively maintained.

  2. Look at the source first, docs second. Open src/ and find the form component class (the thing you will use in your form schema). The README is likely thin; the component's public methods are the real API. Note:

    • the component namespace (Wotz\FilamentResourcePicker\...),
    • whether it is a form field (usable inside ->schema([...])) or a plugin that must be registered in a panel provider,
    • whether it publishes config/views (php artisan vendor:publish --provider="Wotz\...").
  3. Register the plugin if required. In the relevant panel provider:

    ->plugins([
        \Wotz\FilamentResourcePicker\FilamentResourcePickerPlugin::make(),
    ])
    

    If the package is only a form component, skip this and go straight to step 4.

  4. First use case: replace a plain Select with a modal record picker. The canonical day-one use is an owner_id / author_id field on a form where a Select would be unusable because the related table has thousands of rows:

    use Wotz\FilamentResourcePicker\Forms\Components\ResourcePicker;
    
    ResourcePicker::make('owner_id')
        ->label('Owner')
        ->resource(UserResource::class)
        ->required();
    

    Verify the exact method name for pointing at a resource (could be ->resource(), ->using(), or a constructor argument) in the component source before wiring it up.

  5. Smoke test in the browser, not just php artisan test. Open the create page, click the field, confirm the modal table loads with the target resource's columns and that selecting a row writes the correct key into the form state. This is the fastest way to discover whether the package expects an ID, a model instance, or an array of IDs.

Implementation Patterns

Single vs. multiple selection. Most pickers expose a multiple mode for BelongsToMany/HasMany relations:

ResourcePicker::make('members')
    ->resource(UserResource::class)
    ->multiple()
    ->relationship('members', 'name'); // if supported

If ->relationship() is not supported, bind to the pivot manually via a ->saveRelationshipsUsing() hook or a mutateFormDataBeforeSave() on the page, and hydrate defaults with ->default(fn ($record) => $record?->members->pluck('id')).

Scoping the picker table. The picker almost certainly builds a Filament table internally. Look for a ->modifyQueryUsing() / ->tableQuery() / ->query() callback. This is where you enforce tenancy and soft-delete rules:

ResourcePicker::make('client_id')
    ->resource(ClientResource::class)
    ->modifyQueryUsing(fn (Builder $q) => $q
        ->where('tenant_id', filament()->getTenant()?->id)
        ->where('is_active', true));

Do this explicitly even if the target resource has a getEloquentQuery() — do not assume the picker honours it.

Search and display. If the picker search does not find records, the target resource's global search attributes are usually the culprit. Either add the column to the resource's getGloballySearchableAttributes() or pass an explicit searchable column list to the picker. For display labels, the picker typically uses the resource's getRecordTitle() ($titleAttribute on the model); if it renders a raw ID, override the model's getFilamentName().

Polymorphic pickers. For a morph-to relation, use two picker fields — one for the type (a normal Select of allowed resource classes) and one for the ID — or a single picker whose ->resource() is computed from another field via ->live() / ->afterStateUpdated(). Rebuilding the schema on type change is the reliable pattern:

Select::make('pickable_type')->options([...])->live(),
ResourcePicker::make('pickable_id')
    ->resource(fn (Get $get) => $get('pickable_type'))
    ->hidden(fn (Get $get) => blank($get('pickable_type'))),

Reuse beyond forms. If the component is table-filter compatible, you can use it in a Filament\Tables\Filters\Filter or a custom bulk action that needs a record reference. If it is form-only, wrap it in a Form inside an Action::make()->form([...]) — this is the cleanest way to get a picker into table headers.

Pair with a "create new" flow. The usual UX gap is that the picker only selects. Combine it with the target resource's CreateAction / a modal action so users can add a record without leaving the form. If the package does not ship this, add it via a ->suffixAction().

Testing. Picker interactions are Livewire round-trips, so test with Livewire, not HTTP:

livewire(CreatePost::class)
    ->fillForm(['owner_id' => $user->id])
    ->call('create')
    ->assertHasNoFormErrors();

Test the modal path with ->call('mountAction') / ->call('callMountedAction') only if the package exposes named actions — otherwise an end-to-end Dusk/Playwright test is more honest than faking the internals.

Gotchas and Tips

Maturity risk is the headline issue. 2 stars, 0 dependents, and a last release date in the future relative to typical package lifecycles. Treat it as a single-maintainer package: read every line of src/ before shipping to production, and be prepared to vendor it ("repositories": [{"type": "path", ...}]) if the author disappears. Budget a half-day to fork-and-own if you use it in more than one place.

Authorization is not automatic. Verify that the picker applies the target resource's viewAny policy and, more importantly, the tenant scope. A picker that ignores canViewAny() becomes a data-leak vector the moment it is used by a non-admin. Test with a low-privilege user explicitly. If the package does not enforce it, wrap it:

ResourcePicker::make('owner_id')
    ->resource(UserResource::class)
    ->visible(fn () => auth()->user()->can('viewAny', User::class));

Panel and tenancy context. The picker resolves the current panel. If the target resource lives in a different panel (e.g. an admin-only resource picked from a tenant panel), the modal may 404 or render without navigation. Confirm by testing the picker in each panel you deploy, not just the default one.

N+1 on labels. Rendering a list of picked records typically hydrates each model to get its title. If you allow multiple selection on a large set, eager-load in the picker's query (->with('...')) or cache the label lookup. Watch the query log on a form with 50 pre-selected tags.

Version drift. Pin to a specific Filament minor in composer.lock and add a note in your upgrade runbook. Filament's form/table APIs move fast; an unmaintained package is the first thing to break on a Filament upgrade. If you fork, add a CI matrix against the Filament majors you actually run.

Config vs. convention. If the package publishes a config file, publish it early (vendor:publish) so upgrades do not silently change behaviour. If it publishes views, do the same and treat the published view as your override point — that is usually the only practical extension point for changing the modal layout or the table columns shown inside the picker.

Debugging checklist when the modal is empty or selection does not save:

  1. ->resource() receives a class that actually extends Filament\Resources\Resource.
  2. The query callback is not applying a tenant scope that resolves to null in the current request.
  3. The form field name matches the model attribute exactly (owner_id vs owner).
  4. $record->owner_id is hydrated — check fillForm output in Livewire.
  5. The target resource has getGloballySearchableAttributes() (or the picker-level equivalent) populated; otherwise search silently returns nothing.
  6. Run with APP_DEBUG=true and open the Livewire component payload in the browser devtools — the picker's modal state is visible there, which is often faster than adding dd().

Escape hatch. Because the API surface is small, the lowest-risk long-term move for anything non-trivial is to copy the component into app/Filament/Forms/Components/ and depend on Filament directly. You keep the modal-picker UX and drop the maintenance risk of a 2-star dependency — worth doing if this appears in more than two forms.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin