api/.agents/skills/scout-development/SKILL.md

8.7 KiB

name description license metadata
scout-development Develops full-text search with Laravel Scout. Activates when installing or configuring Scout; choosing a search engine (Algolia, Meilisearch, Typesense, Database, Collection); adding the Searchable trait to models; customizing toSearchableArray or searchableAs; importing or flushing search indexes; writing search queries with where clauses, pagination, or soft deletes; configuring index settings; troubleshooting search results; or when the user mentions Scout, full-text search, search indexing, or search engines in a Laravel project. Make sure to use this skill whenever the user works with search functionality in Laravel, even if they don't explicitly mention Scout. MIT
author
laravel

Scout Full-Text Search

Documentation First

Always use search-docs before writing Scout code. The documentation covers every engine, configuration option, and edge case in detail. This skill teaches you how to navigate Scout — the docs have the implementation specifics.

search-docs(queries: ["Scout installation"], packages: ["laravel/framework@12.x"])

The Scout docs live under the laravel/framework package — not laravel/scout.

Effective search patterns:

  • Installation & setup: "Scout installation", "Scout queueing"
  • Engine setup: "Scout Algolia", "Scout Meilisearch", "Scout Typesense"
  • Model configuration: "Scout configuring searchable data", "Scout configuring model indexes"
  • Searching: "Scout searching", "Scout where clauses", "Scout pagination"
  • Indexing: "Scout batch import", "Scout adding records", "Scout removing records"
  • Advanced: "Scout soft deleting", "Scout customizing engine searches", "Scout custom engines"

The docs are organized into these main sections: Installation, Driver Prerequisites, Configuration, Database/Collection Engines, Indexing, Searching, Custom Engines. Use these section names as search anchors.

When to Apply

Activate this skill when:

  • Installing or configuring Scout
  • Choosing a search engine for a Laravel application
  • Making Eloquent models searchable
  • Customizing indexed data or index names
  • Writing search queries, filters, or pagination
  • Importing or flushing search indexes
  • Troubleshooting search results or indexing issues
  • Choosing between search engines

Installation

Before installing, check if Scout is already in the project — look for laravel/scout in composer.json and config/scout.php. If already installed, skip to the relevant section (engine configuration, model setup, or searching).

1. Install Scout

composer require laravel/scout
vendor/bin/sail artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

2. Add the Searchable trait

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;
}

This registers a model observer that automatically keeps your search index in sync with Eloquent records.

Choosing an Engine

Before presenting engine options, check if Scout is already configured in the application:

  1. Check .env for SCOUT_DRIVER — if set, the application already has a configured engine.
  2. Check config/scout.php for the driver key and any engine-specific settings.
  3. Check composer.json for engine SDKs (algolia/algoliasearch-client-php, meilisearch/meilisearch-php, typesense/typesense-php).

If an engine is already configured, skip the engine selection step and work with the existing setup. Only present the engine comparison if Scout is not yet installed or the user explicitly wants to switch engines.

When no engine is configured, present these options and let the user decide — never choose for them.

Engine Type Best For Tradeoffs
Database Built-in Typical applications, simple search No external deps. MySQL/PostgreSQL only. LIKE + full-text indexes. No typo tolerance.
Collection Built-in Local dev, tiny datasets (<500 records) Loads all records into memory. Most portable but least efficient.
Algolia Hosted SaaS Advanced search without managing infra Typo tolerance, analytics, faceting. Paid service. No self-hosting.
Meilisearch Self-hosted / Cloud Teams wanting infrastructure control Fast, open-source. Self-hostable or cloud. Requires filterable attribute config.
Typesense Self-hosted / Cloud Keyword, semantic, geo, vector search Open-source. Self-hostable or cloud. Strict schema requirements.

After the user chooses, use search-docs for that engine's prerequisites and configuration.

Set the engine in .env:

SCOUT_DRIVER=database

For third-party engines (Algolia, Meilisearch, Typesense): install their PHP SDK and strongly consider enabling queue support in config/scout.php for production.

Model Configuration

Use search-docs for full configuration details. Key customization points:

// Control what data gets indexed
public function toSearchableArray(): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'body' => $this->body,
    ];
}

// Custom index name (no effect with database engine)
public function searchableAs(): string
{
    return 'posts_index';
}

For Algolia/Meilisearch/Typesense: configure index settings (filterable, sortable, searchable attributes) in config/scout.php, then sync:

vendor/bin/sail artisan scout:sync-index-settings

Searching

Basic patterns:

// Simple search
$results = Model::search('query')->get();

// With filtering
$results = Model::search('query')->where('status', 'active')->get();

// Paginated
$results = Model::search('query')->paginate(15);

// Eager load relationships on results
$results = Model::search('query')
    ->query(fn ($q) => $q->with('category'))
    ->get();

// Raw engine results
$results = Model::search('query')->raw();

Use search-docs for advanced querying — whereIn, whereNotIn, soft deletes, custom indexes, and engine-specific options.

Key Artisan Commands

Command Purpose
vendor/bin/sail artisan scout:import "App\Models\Post" Import model records into search index
vendor/bin/sail artisan scout:queue-import "App\Models\Post" Import via queued jobs (large datasets)
vendor/bin/sail artisan scout:flush "App\Models\Post" Remove all model records from search index
vendor/bin/sail artisan scout:sync-index-settings Sync index settings to the search engine
vendor/bin/sail artisan scout:index posts Create a search index
vendor/bin/sail artisan scout:delete-index posts Delete a search index

Testing

Scout does not have a Scout::fake() method. Available approaches:

  • NullEngine — set SCOUT_DRIVER=null to disable all indexing in tests
  • CollectionEngine — set SCOUT_DRIVER=collection for in-memory search without external services
  • Model::withoutSyncingToSearch(fn() => ...) — temporarily pause indexing in a callback
  • Model::disableSearchSyncing() — globally disable syncing in test setUp

Use search-docs for detailed testing patterns.

Common Pitfalls

  • Meilisearch filterable attributes — you must configure filterable attributes in config/scout.php under meilisearch.index-settings and run scout:sync-index-settings before using where, whereIn, or whereNotIn. Without this, filtering silently returns wrong results.
  • Meilisearch data type castingtoSearchableArray() must return properly cast values: integers as (int), floats as (float). Wrong types cause silent filter failures.
  • Database engine limitationssearchableAs(), getScoutKey(), getScoutKeyName(), and shouldBeSearchable() have no effect with the database engine. It queries your actual tables directly.
  • Global scopes break pagination — search engines are not aware of Eloquent global scopes. Recreate scope constraints using Scout where clauses instead.
  • query() is not for filtering — with third-party engines, the query() callback runs after results are already retrieved. Use Scout where clauses for filtering; use query() only for eager-loading or customizing the Eloquent hydration query.
  • Missing queue configuration — third-party engines should always have scout.queue enabled in production. Without it, indexing runs synchronously and slows down requests.
  • Typesense schema requirementsid must be cast as (string) and timestamps as Unix integers ($this->created_at->timestamp).
  • shouldBeSearchable() bypass — this method only applies via save(), create(), queries, or relationships. Calling searchable() directly on a model or collection bypasses it entirely.