feature: Global search resource opt-in (#19513)

This commit is contained in:
Dan Harrin
2026-03-17 17:22:29 +00:00
committed by GitHub
parent e969c442f9
commit 8916fe5c0d
4 changed files with 102 additions and 1 deletions
+23
View File
@@ -172,6 +172,29 @@ public function panel(Panel $panel): Panel
}
```
## Requiring resources to opt in to global search
By default, all resources with a [title attribute](#setting-global-search-result-titles) are included in global search results. If you'd prefer resources to explicitly opt in, you can use the `globalSearchResourceOptIn()` method in the [configuration](../panel-configuration):
```php
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->globalSearchResourceOptIn();
}
```
Now, only resources that explicitly set `$isGloballySearchable` to `true` will be included in global search results:
```php
protected static bool $isGloballySearchable = true;
```
Resources that do not declare this property will be excluded from global search, even if they have a title attribute set.
## Registering global search key bindings
The global search field can be opened using keyboard shortcuts. To configure these, pass the `globalSearchKeyBindings()` method to the [configuration](../panel-configuration):
@@ -26,6 +26,8 @@ trait HasGlobalSearch
protected string | Closure | null $globalSearchFieldSuffix = null;
protected bool $isGlobalSearchResourceOptIn = false;
public function globalSearch(string | bool $provider = true, GlobalSearchPosition | Closure | null $position = null): static
{
if (is_string($provider) && (! in_array(GlobalSearchProvider::class, class_implements($provider)))) {
@@ -121,6 +123,18 @@ trait HasGlobalSearch
return $this->evaluate($this->globalSearchFieldSuffix);
}
public function globalSearchResourceOptIn(bool $condition = true): static
{
$this->isGlobalSearchResourceOptIn = $condition;
return $this;
}
public function isGlobalSearchResourceOptIn(): bool
{
return $this->isGlobalSearchResourceOptIn;
}
public function getGlobalSearchProvider(): ?GlobalSearchProvider
{
$provider = $this->globalSearchProvider;
@@ -3,6 +3,7 @@
namespace Filament\Resources\Resource\Concerns;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\GlobalSearch\GlobalSearchResult;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Connection;
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use ReflectionProperty;
use function Filament\Support\generate_search_column_expression;
use function Filament\Support\generate_search_term_expression;
@@ -31,7 +33,18 @@ trait HasGlobalSearch
public static function canGloballySearch(): bool
{
return static::$isGloballySearchable && count(static::getGloballySearchableAttributes()) && static::canAccess();
$isGloballySearchable = static::$isGloballySearchable;
if (
$isGloballySearchable &&
Filament::getCurrentOrDefaultPanel()?->isGlobalSearchResourceOptIn()
) {
$isGloballySearchable = (new ReflectionProperty(static::class, 'isGloballySearchable'))
->getDeclaringClass()
->getName() === static::class;
}
return $isGloballySearchable && count(static::getGloballySearchableAttributes()) && static::canAccess();
}
/**
@@ -5,8 +5,11 @@ use Filament\GlobalSearch\GlobalSearchResult;
use Filament\GlobalSearch\GlobalSearchResults;
use Filament\GlobalSearch\Providers\Contracts\GlobalSearchProvider;
use Filament\Livewire\GlobalSearch;
use Filament\Resources\Resource;
use Filament\Tests\Fixtures\Models\Post;
use Filament\Tests\Fixtures\Models\User;
use Filament\Tests\Fixtures\Resources\Posts\PostResource;
use Filament\Tests\Fixtures\Resources\Users\UserResource;
use Filament\Tests\Panels\GlobalSearch\TestCase;
use Illuminate\Database\Eloquent\Factories\Sequence;
use Illuminate\Support\Str;
@@ -78,6 +81,54 @@ it('orders resource global search results by `$globalSearchSort`', function ():
expect($categories[1])->toBe('posts');
});
it('excludes resources without explicit `$isGloballySearchable` when `globalSearchResourceOptIn()` is enabled', function (): void {
Filament::getCurrentOrDefaultPanel()->globalSearchResourceOptIn();
expect(PostResource::canGloballySearch())->toBeFalse();
expect(UserResource::canGloballySearch())->toBeFalse();
});
it('includes resources with explicit `$isGloballySearchable` when `globalSearchResourceOptIn()` is enabled', function (): void {
Filament::getCurrentOrDefaultPanel()->globalSearchResourceOptIn();
expect(OptedInGlobalSearchResource::canGloballySearch())->toBeTrue();
});
it('includes all resources by default without `globalSearchResourceOptIn()`', function (): void {
expect(PostResource::canGloballySearch())->toBeTrue();
expect(UserResource::canGloballySearch())->toBeTrue();
expect(OptedInGlobalSearchResource::canGloballySearch())->toBeTrue();
});
it('does not return search results for resources without explicit `$isGloballySearchable` when `globalSearchResourceOptIn()` is enabled', function (): void {
Filament::getCurrentOrDefaultPanel()->globalSearchResourceOptIn();
$post = Post::factory()->create();
livewire(GlobalSearch::class)
->set('search', $post->title)
->assertDontSee($post->title);
});
class OptedInGlobalSearchResource extends Resource
{
protected static ?string $model = Post::class;
protected static ?string $recordTitleAttribute = 'title';
protected static bool $isGloballySearchable = true;
protected static ?string $slug = 'opted-in-global-search-posts';
/**
* @return array<string>
*/
public static function getGloballySearchableAttributes(): array
{
return ['title'];
}
}
class CustomSearchProvider implements GlobalSearchProvider
{
public function getResults(string $query): ?GlobalSearchResults