DEV3 Technologies home

Filament global search with relation fields and quick actions

Configure Filament 4 global search for a resource: searchable columns across relations, result details, eager loading, a result limit and actions.

FilamentSearchfilamentglobal-searchresourcerelations

Code

<?php

namespace App\Filament\Resources\Candidates;

use App\Filament\Resources\Candidates\Pages\ListCandidates;
use App\Models\Candidate;
use Filament\Actions\Action;
use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class CandidateResource extends Resource
{
    protected static ?string $model = Candidate::class;

    // The title of each hit in the search dropdown.
    protected static ?string $recordTitleAttribute = 'name';

    // Search the candidate and two related models. Dotted names go through relations.
    public static function getGlobalSearchAttributes(): array
    {
        return ['name', 'phone', 'job.title', 'tags.name'];
    }

    // Eager load what the details below read, or each hit costs extra queries.
    public static function getGlobalSearchEloquentQuery(): Builder
    {
        return parent::getGlobalSearchEloquentQuery()
            ->with(['job', 'stage'])
            ->where('is_archived', false);   // never surface archived people
    }

    // The grey lines shown under each title.
    public static function getGlobalSearchResultDetails(Model $record): array
    {
        return [
            'Job' => $record->job?->title ?? 'Unassigned',
            'Stage' => $record->stage?->name ?? 'New',
        ];
    }

    // Quick buttons beside every hit.
    public static function getGlobalSearchResultActions(Model $record): array
    {
        return [
            Action::make('profile')
                ->label('Open profile')
                ->url(static::getUrl('view', ['record' => $record])),
            Action::make('edit')
                ->url(static::getUrl('edit', ['record' => $record]))
                ->visible(fn () => auth()->user()->can('update', $record)),
        ];
    }

    // Keep the dropdown short; the full list page still has everything.
    public static function getGlobalSearchResultsLimit(): int
    {
        return 5;
    }

    public static function getPages(): array
    {
        return ['index' => ListCandidates::route('/')];
    }
}

// Panel provider tuning:
//   ->globalSearchDebounce('500ms')
//   ->globalSearchKeyBindings(['command+k', 'ctrl+k'])

Needs: filament/filament@^4.0

How it works

What it solves

Filament's global search box is one of the quickest ways to move around a large panel, but it only works for resources that opt in and describe how a hit should look. This snippet shows the full set of hooks for a recruiting-style record that lives next to jobs, stages and tags.

How it works

Setting $recordTitleAttribute is what registers a resource with global search. getGlobalSearchAttributes() lists the searchable columns; dotted entries such as job.title or tags.name search through relations, so typing a job name finds its candidates.

getGlobalSearchEloquentQuery() starts from the parent query, eager loads the relations used by the result details and filters out archived rows. This one method is also the place to enforce tenant or ownership scoping.

getGlobalSearchResultDetails() returns label-value pairs rendered under the title, and getGlobalSearchResultActions() adds small buttons per hit, here "Open profile" and an edit link that only appears when the policy allows it. getGlobalSearchResultsLimit() caps the dropdown at five hits.

The trailing comment shows two panel-level settings: a debounce to reduce queries while typing and keyboard shortcuts.

Adapting it

Rename the model and the relation paths. Move the details labels to translation keys. Add a getGlobalSearchResultUrl() override if hits should go to a custom page instead of the default view or edit.

Pitfalls

  • Each searchable attribute adds a LIKE clause, so index or limit them on big tables.
  • Relation searching uses subqueries; very deep paths get slow.
  • If your policies hide records, add the same constraint here or users will see titles they cannot open.

How to use it

  1. Make sure the model has job(), stage() and tags() relations (or change the names).
  2. Merge the global search methods into your existing resource class.
  3. Run php artisan optimize:clear, then type in the search box at the top of the panel.
  4. Tune globalSearchDebounce() and globalSearchKeyBindings() in the panel provider.
  5. Enforce visibility rules inside getGlobalSearchEloquentQuery().

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets