Key takeaways
- ✓ Global search turns on per resource once you set
$recordTitleAttribute. Every enabled resource is queried on every search. - ✓ Make results useful with
getGloballySearchableAttributes(),getGlobalSearchResultDetails(),getGlobalSearchResultActions()and aCmd+Kshortcut. - ✓ Keep it fast: eager load in
getGlobalSearchEloquentQuery(), lower$globalSearchResultsLimit(default 50), turn off term splitting on big tables, and use the opt-in mode added in Filament 5.4. - ✓
LIKE '%term%'can’t use normal indexes. For large datasets, plug in a Scout-backed customGlobalSearchProvider.
The search box in Filament’s top bar is one of the most-used features in any panel, and one of the most expensive by accident. Out of the box, it runs a LIKE query against every searchable resource each time someone pauses typing. On a demo database, that’s instant. On 3 million orders, it’s a slow query log. This guide makes global search both useful and fast.
How global search works
- The user types. After the debounce (500 ms by default), Livewire sends the query.
- For each resource with a
$recordTitleAttribute, Filament builds a query fromgetGlobalSearchEloquentQuery()and appliesWHERE col LIKE %term%across the searchable attributes. - Results are grouped by resource, up to
$globalSearchResultsLimitper resource (50 by default).
So cost ≈ (number of searchable resources) × (columns searched) × (how unindexable %term% is). Keep that in mind for the rest of this guide.
Step 1: Enable it on a resource
class CustomerResource extends Resource
{
protected static ?string $recordTitleAttribute = 'name';
}That’s the whole setup. Results link to the record’s edit page (or its view page if it has one) when the user is authorised.
Step 2: Search the right columns
public static function getGloballySearchableAttributes(): array
{
return ['name', 'email', 'company.name'];
}Dot notation searches relationships with a whereHas, which is convenient and costs more. Only add relationship columns that people genuinely search.
Step 3: Make results informative
A list of bare names makes users guess. Add context:
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
public static function getGlobalSearchResultTitle(Model $record): string | Htmlable
{
return "{$record->name} ({$record->account_number})";
}
public static function getGlobalSearchResultDetails(Model $record): array
{
return [
'Company' => $record->company?->name,
'Plan' => $record->plan?->getLabel(),
];
}
public static function getGlobalSearchEloquentQuery(): Builder
{
return parent::getGlobalSearchEloquentQuery()->with(['company']);
}The eager load isn’t optional. Without ->with('company'), getGlobalSearchResultDetails() runs one query per result. With the default limit of 50, one search becomes 51 queries for this resource alone.
Step 4: Actions straight from the results
Users can act without opening the record first:
use Filament\Actions\Action;
public static function getGlobalSearchResultActions(Model $record): array
{
return [
Action::make('edit')
->url(static::getUrl('edit', ['record' => $record])),
Action::make('invoices')
->url(InvoiceResource::getUrl('index', ['filters' => ['customer' => ['value' => $record->id]]])),
];
}Result actions can open URLs (in a new tab if you like) or dispatch Livewire events.
Step 5: Panel-level settings
public function panel(Panel $panel): Panel
{
return $panel
->globalSearchKeyBindings(['command+k', 'ctrl+k'])
->globalSearchFieldKeyBindingSuffix() // shows "⌘K" in the field
->globalSearchDebounce('750ms'); // fewer requests while typing
}A longer debounce is the cheapest performance win there is: 750 ms instead of 500 ms often halves the number of requests from fast typists. Turn global search off completely for a panel with ->globalSearch(false).
Performance: seven fixes that matter
1. Opt resources in, not out (Filament 5.4+)
Filament v5.4 added an opt-in mode. Instead of every resource with a title attribute being searchable, you choose explicitly:
// Panel provider
->globalSearchResourceOptIn()
// Only on the resources that should be searched
protected static bool $isGloballySearchable = true;Twelve searchable resources nobody searches means twelve wasted queries per keystroke. Opt-in mode gets you to three or four.
2. Lower the per-resource limit
protected static int $globalSearchResultsLimit = 8;Nobody scrolls past eight customers in a dropdown. They refine the query.
3. Turn off term splitting on large tables
By default, “jane smith” is split into terms, and each is matched against each column, producing a growing chain of OR/AND conditions. On large tables, search the phrase instead:
protected static ?bool $shouldSplitGlobalSearchTerms = false;4. Index what you can
A leading wildcard (LIKE '%smith%') can’t use a B-tree index. Some options:
- Exact-match columns such as order numbers, SKUs and emails: offer a separate, fast path. Many teams override
getGlobalSearchEloquentQuery()to usewhere('number', $term)when the term looks like an order number. - MySQL/MariaDB: add a
FULLTEXTindex and search withwhereFullText()in a custom provider (see below). PostgreSQL: a
pg_trgmGIN index makesILIKE '%term%'indexable:DB::statement('CREATE EXTENSION IF NOT EXISTS pg_trgm'); DB::statement('CREATE INDEX customers_name_trgm ON customers USING gin (name gin_trgm_ops)');
5. Case sensitivity on PostgreSQL
PostgreSQL’s LIKE is case-sensitive, and Filament handles this for you. If you know your collation is case-insensitive (typical on MySQL), you can skip the extra LOWER() work. Filament’s global search docs cover the options for forcing or disabling case-insensitive matching per resource.
6. Don’t let tenancy become a full-table scan
In multi-tenant panels, every search is also filtered by team_id. A composite index that starts with team_id (for example (team_id, name)) keeps the planner working on one tenant’s rows instead of the whole table.
7. Measure, then decide
Use Laravel Debugbar, Telescope or Nightwatch to see the queries one search produces. If a single keystroke produces 30+ queries or anything over ~100 ms, apply the fixes above in order.
Going further: a Scout-backed global search provider
For large or text-heavy datasets, hand search to a real search engine (Meilisearch, Typesense, Algolia or the database driver) through Laravel Scout. Filament lets you replace the whole global search implementation with your own provider:
namespace App\Filament\GlobalSearch;
use App\Filament\Resources\Customers\CustomerResource;
use App\Filament\Resources\Orders\OrderResource;
use App\Models\Customer;
use App\Models\Order;
use Filament\GlobalSearch\GlobalSearchResult;
use Filament\GlobalSearch\GlobalSearchResults;
use Filament\GlobalSearch\Providers\Contracts\GlobalSearchProvider;
class ScoutGlobalSearchProvider implements GlobalSearchProvider
{
public function getResults(string $query): ?GlobalSearchResults
{
$results = GlobalSearchResults::make();
$customers = Customer::search($query)->take(8)->get();
$results->category('Customers', $customers->map(fn (Customer $c) => new GlobalSearchResult(
title: $c->name,
url: CustomerResource::getUrl('edit', ['record' => $c]),
details: ['Email' => $c->email],
)));
$orders = Order::search($query)->take(8)->get();
$results->category('Orders', $orders->map(fn (Order $o) => new GlobalSearchResult(
title: "Order #{$o->number}",
url: OrderResource::getUrl('view', ['record' => $o]),
details: ['Total' => '£' . number_format($o->total, 2)],
)));
return $results;
}
}// Panel provider
->globalSearch(ScoutGlobalSearchProvider::class)Check the exact constructor signature of GlobalSearchResult in vendor/filament/filament/src/GlobalSearch for your installed version before copying this. Because you control the provider, you also control authorisation. Filter results by policy (auth()->user()->can('view', $record)) and by tenant, since your search engine doesn’t know about either.
Deciding what should be searchable
Not every resource belongs in global search. Ask one question for each: would someone type a name or number from this model into a search box to jump to it?
| Resource | Searchable? | Why |
|---|---|---|
| Customers, users, companies | Yes | People search by name and email |
| Orders, invoices, tickets | Yes, by number | Exact identifiers from emails and phone calls |
| Products | Yes, by SKU and name | Common lookups |
| Settings, roles, tax rates | No | Few rows, reached through navigation |
| Logs, activity, events | No | High volume, low jump value, and expensive |
| Pivot or detail models (order items) | No | Search the parent instead |
In practice, three to five searchable resources cover nearly every real search. That’s also the point where global search stays fast without further work.
Authorisation and search results
Filament only shows results that link to pages the user can access: the resource must pass viewAny(), and each result’s URL respects view() or update(). Two things to watch:
- Details can leak data.
getGlobalSearchResultDetails()renders whatever you return, so don’t show a customer’s lifetime value to a role that can’t see revenue. Check the permission inside the method. - Custom providers bypass Filament’s checks. In a Scout provider (below), you’re responsible for filtering results by policy and tenant.
Table search vs global search
They’re separate features. Table search (->searchable() on columns) has its own performance profile. If a list page’s search box is slow, see our 8 fixes for slow Filament tables.
FAQ
How do I enable global search in Filament?
Set protected static ?string $recordTitleAttribute = 'name'; on a resource. Customise the searched columns with getGloballySearchableAttributes().
Why is Filament global search slow?
Each search queries every searchable resource with LIKE '%term%' on several columns, often with relationship lookups and per-result N+1 queries. Eager load, reduce the result limit, opt resources in, disable term splitting and add suitable indexes, or use Scout.
How do I add a keyboard shortcut for global search?
Call ->globalSearchKeyBindings(['command+k', 'ctrl+k']) on the panel. ->globalSearchFieldKeyBindingSuffix() shows the shortcut in the field.
Can I use Laravel Scout with Filament global search?
Yes. Implement Filament’s GlobalSearchProvider interface, run Scout queries inside getResults(), and register it with ->globalSearch(YourProvider::class).
Sources and further reading
- Filament global search documentation
- Filament v5.4.0 release notes
- Laravel Scout
- PostgreSQL pg_trgm
- MySQL full-text search
Related on The Web Tier: Multi-tenant SaaS with Filament
