muhammad-nawlo / filament-scout-manager
A comprehensive Filament plugin for managing Laravel Scout indexes and search settings
Package info
github.com/Muhammad-Nawlo/filament-scout-manager
pkg:composer/muhammad-nawlo/filament-scout-manager
Fund package maintenance!
Requires
- php: ^8.2
- filament/filament: ^5.0
- laravel/scout: ^10.24
- spatie/laravel-package-tools: ^1.15.0
- spatie/laravel-settings: ^3.7
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.7|^4.0
- pestphp/pest-plugin-arch: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- pestphp/pest-plugin-livewire: ^3.0|^4.0
- rector/rector: ^2.0
- spatie/laravel-ray: ^1.26
README
A Filament plugin to manage your Laravel Scout search setup from an admin panel.
Features
- Discover Scout-searchable models and inspect index/engine metadata.
- Run index actions (import, flush, refresh) per model or in bulk.
- View index health and popular searches with dashboard widgets.
- Log user search queries for analysis.
- Manage search synonyms in the panel.
- Configure behavior with package config/settings.
Requirements
- PHP 8.2+
- Laravel app with Laravel Scout configured
- Filament 5 panel
Installation
Install the package:
composer require muhammad-nawlo/filament-scout-manager
Run the installer:
php artisan filament-scout-manager:install
Or manually publish package files:
php artisan vendor:publish --tag="filament-scout-manager-config" php artisan vendor:publish --tag="filament-scout-manager-migrations" php artisan migrate
If you use a custom Filament theme, add the package views as a Tailwind source:
@source '../../../../vendor/muhammad-nawlo/filament-scout-manager/resources/**/*.blade.php';
Register the plugin
In your Filament panel provider, register the plugin:
use MuhammadNawlo\FilamentScoutManager\FilamentScoutManagerPlugin; public function panel(Panel $panel): Panel { return $panel // ... ->plugins([ FilamentScoutManagerPlugin::make(), ]); }
Configuration
Published config: config/filament-scout-manager.php
return [ 'log_searches' => true, 'log_retention_days' => 30, 'enable_synonyms' => true, 'models' => [ // 'App\\Other\\Model' => [], ], ];
Usage notes
- Ensure each model you want indexed uses Scout's
Searchabletrait. - Configure your Scout driver (
SCOUT_DRIVER) and engine credentials in.env. - The "Searchable Fields" options in the panel are most useful when your model defines a custom
toSearchableArray().
Runtime overrides
Stored model configuration (index name, searchable fields, engine) can be applied at runtime so that Scout uses the values you set in the Filament panel. This is opt-in and fully backward compatible.
- No trait → no override; the model behaves exactly like vanilla Scout.
- With trait → if a config is saved for that model in the panel,
searchableAs(),toSearchableArray(), andsearchableUsing()use the stored values; otherwise defaults are unchanged.
To enable runtime overrides on a model, use the SearchableWithScoutManagerConfig trait (it combines Scout’s Searchable with plugin config and avoids trait method collisions):
use Illuminate\Database\Eloquent\Model; use MuhammadNawlo\FilamentScoutManager\Concerns\SearchableWithScoutManagerConfig; class Product extends Model { use SearchableWithScoutManagerConfig; }
If you prefer to use Searchable and UsesScoutManagerConfig separately, you must resolve the trait collision for searchableUsing, searchableAs, and toSearchableArray with insteadof; see the docblock on UsesScoutManagerConfig for the exact syntax.
Then configure the model in Filament (Search → Searchable Models → Configure). Stored values for index name, searchable fields, and engine will take effect when this trait is used. If no config exists for the model, behavior is identical to Scout’s defaults.
Batch operations without indexing
When you need to run many Eloquent operations (bulk updates, imports, etc.) without syncing each change to the search index, use Scout’s withoutSyncingToSearch() method. This avoids unnecessary index writes and improves performance.
Wrap your bulk logic in a closure:
use App\Models\Product; Product::withoutSyncingToSearch(function () { Product::where('status', 'draft')->update(['status' => 'published']); // ... other bulk operations });
After the batch is done, re-import the affected model so the index is up to date:
php artisan scout:import "App\Models\Product"
This pattern is controlled in your application code; the plugin does not add any global pause or toggle. See Laravel Scout: Pausing Indexing.
Conditionally searchable records
To index only some records (e.g. only published posts), implement shouldBeSearchable() on your model. Scout calls this when syncing via save/create or when using the searchable() method on a query; if it returns false, the record is not added or updated in the index.
public function shouldBeSearchable(): bool { return $this->is_published; }
- This logic must live in your model; the plugin does not override or control it.
- When using Scout’s database engine,
shouldBeSearchable()is not applied (all searchable data is in the database). For similar behavior, use where clauses in your search queries instead. - See Laravel Scout: Conditionally Searchable Model Instances.
Scout queue behavior
Scout’s queue configuration is global, not per model.
- Set
SCOUT_QUEUE=truein.env(or thequeueoption inconfig/scout.php) to queue index operations instead of running them synchronously. - The queue connection and queue name are defined in
config/scout.phpand apply to all Scout indexing jobs. - Per-model queue routing is not supported by Scout out of the box; it would require a custom job implementation that reads your own config. The plugin does not change Scout’s global queue behavior.
- See Laravel Scout: Queueing.
Database engine search strategies
When using Scout’s database engine (SCOUT_DRIVER=database), you can control how each column is searched by adding PHP attributes to your model’s toSearchableArray() method:
SearchUsingFullText– use MySQL/PostgreSQL full-text search for the listed columns (faster and more relevant for text). Requires a full-text index on those columns in a migration.SearchUsingPrefix– use prefix-only matching (e.g.term%) for the listed columns instead of%term%. Helps performance on large tables.
Columns not covered by these attributes keep Scout’s default “where like” behavior. The plugin does not set or change these attributes; you implement them in your model.
Example:
use Laravel\Scout\Attributes\SearchUsingFullText; use Laravel\Scout\Attributes\SearchUsingPrefix; #[SearchUsingFullText(['title', 'description'])] #[SearchUsingPrefix(['sku'])] public function toSearchableArray(): array { return [ 'title' => $this->title, 'description' => $this->description, 'sku' => $this->sku, ]; }
In a migration, add a full-text index for the columns you use with SearchUsingFullText:
$table->fullText(['title', 'description']);
These strategies apply only when the engine is database. See Laravel Scout: Customizing Database Searching Strategies.
Customizing Scout import queries
Scout lets you customize how models are loaded during batch import and how the collection is prepared before indexing. Implement these methods on your model; the plugin does not override them.
Modify the import query (e.g. eager load relations to avoid N+1 during import):
use Illuminate\Database\Eloquent\Builder; protected function makeAllSearchableUsing(Builder $query): Builder { return $query->with(['category', 'tags']); }
Modify the collection before indexing (e.g. load relations on the chunk):
use Illuminate\Database\Eloquent\Collection; public function makeSearchableUsing(Collection $models): Collection { return $models->load('author'); }
- Use these to eager load relationships so
toSearchableArray()can use related data without extra queries. makeAllSearchableUsingis used when you runscout:importorModel::makeAllSearchable(); note that when the import is queued, relationships may not be restored in the job, so prefermakeSearchableUsingfor per-chunk loading when using queues.- See Laravel Scout: Modifying the Import Query and Modifying Records Before Importing.
Algolia user identification
When using the Algolia engine, you can associate search requests with authenticated users for analytics and personalization. Set in your .env:
SCOUT_IDENTIFY=true
This tells Scout to pass the authenticated user’s primary identifier and the request IP to Algolia with each search request. The plugin does not automatically send or capture user data; enabling identification and what is sent remain under your application and Laravel Scout. See Laravel Scout: Identifying Users.
Customizing the indexed model ID
By default, Scout uses the model’s primary key as the unique ID stored in the index. You can override this with getScoutKey() and getScoutKeyName() on your model (e.g. to use UUIDs, emails, or external IDs). Some engines (e.g. Typesense) expect string IDs or specific key names.
public function getScoutKey() { return $this->email; } public function getScoutKeyName() { return 'email'; }
The plugin does not override these methods; implement them in your model when needed. See Laravel Scout: Configuring the Model ID.
Passing engine-specific search options
Some engines support passing extra parameters into the search request. For example, Typesense allows dynamic search parameters via options():
Post::search('laravel') ->options([ 'query_by' => 'title, description', 'filter_by' => 'published:=true', ]) ->get();
Algolia and other engines may support similar options depending on their API. The plugin does not restrict or modify options(); advanced search behavior stays in your application code. See your engine’s documentation (e.g. Typesense search parameters).
Laravel Scout feature coverage
| Feature | Status |
|---|---|
| Index syncing (import, flush, refresh, sync settings) | ✅ |
| Runtime overrides (index name, fields, engine via trait) | ✅ |
| Typesense UI support & safe indexed count | ✅ |
| Custom engines (safe handling, no UI break) | ✅ |
| Indexing control docs (pause, conditional, queue) | ✅ |
| Database engine docs (search strategies, full-text) | ✅ |
| Import customization docs (makeAllSearchableUsing, makeSearchableUsing) | ✅ |
| Algolia user identification docs | ✅ |
| Scout key override docs (getScoutKey, getScoutKeyName) | ✅ |
| Dynamic search options docs (options()) | ✅ |
The plugin does not override Scout core behavior. All engine-specific logic (identify, key, options) remains application-controlled.
Testing
The package uses Pest for PHPUnit-style tests. Run the test suite:
composer test
Test coverage
- Plugin: ID, Filament contract, registration of resources and widgets (SearchableModelResource, SearchQueryLogResource, SynonymResource, IndexStatusWidget, PopularSearchesWidget).
- Actions: Import, Flush, Refresh, Sync Index Settings (labels, icons, confirmation).
- Resources: SearchableModelResource (navigation,
isSearchable, Eloquent query), engine badges, SearchQueryLogResource, SynonymResource. - Widgets: IndexStatusWidget (column span, stats), PopularSearchesWidget (data, logging toggle).
- Services: ScoutModelConfigService, ScoutIndexSettingsService, IndexedCountResolver (Algolia/Meilisearch/Typesense raw response parsing, unknown engine).
- DTO & traits: ScoutModelConfigDTO (properties, readonly), UsesScoutManagerConfig / SearchableWithScoutManagerConfig (searchableAs, toSearchableArray, scoutQueueConnection, searchableUsing).
- Settings & models: FilamentScoutManagerSettings, SearchQueryLog, Synonym.
- Commands: Install command (signature, handle).
- Localization: English and Arabic translation keys.
Changelog
Please see CHANGELOG for recent updates.
Contributing
Please see CONTRIBUTING for details.
Security
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see LICENSE for details.