Saltar al contenido principal

Introducción

Laravel Scout es una solución sencilla basada en drivers para añadir búsqueda de texto completo a los modelos Eloquent. Utiliza observers para mantener sincronizados los registros Eloquent con los índices de búsqueda. Scout incluye un motor database integrado que utiliza índices full-text de MySQL/PostgreSQL y la cláusula LIKE para buscar directamente sobre la base de datos, sin necesidad de servicios externos. En entornos de producción grandes en los que necesitas tolerancia a erratas, faceting o búsqueda geográfica, los motores externos son de gran ayuda.

Motores compatibles

MotorCaracterísticasUso
databaseÍndices full-text de MySQL/PostgreSQLLa mayoría de aplicaciones
collectionFiltrado en PHP (compatible con SQLite)Desarrollo local y tests
MeilisearchOpen source, tolerante a erratas, rápidoProducción
AlgoliaSaaS en la nube, con funciones avanzadasProducción
TypesenseOpen source, con búsqueda vectorialProducción

Instalación

Instala el paquete con Composer.
composer require laravel/scout
Después publica el archivo de configuración con vendor:publish. Se generará config/scout.php.
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Por último, añade el trait Laravel\Scout\Searchable a los modelos que quieres hacer buscables. El trait registra los observers y activa la sincronización automática con el driver de búsqueda.
<?php

namespace App\Models;

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

class Post extends Model
{
    use Searchable;
}

Configuración de la cola

Si usas cualquier motor distinto de database o collection, es muy recomendable configurar un driver de colas antes de utilizar Scout. Con un worker corriendo, la sincronización del índice se ejecuta en segundo plano y la interfaz web es mucho más ágil. Pon queue a true en config/scout.php.
'queue' => true,
También puedes indicar conexión y nombre de cola.
'queue' => [
    'connection' => 'redis',
    'queue' => 'scout'
],
Después arranca un worker dedicado.
php artisan queue:work redis --queue=scout

Usar jobs únicos

En aplicaciones con mucha escritura te puede interesar evitar encolar varias veces el mismo job para el mismo modelo. Registra las clases MakeSearchableUniquely y RemoveFromSearchUniquely en config/scout.php (normalmente en el método boot de un service provider).
use Laravel\Scout\Jobs\MakeSearchableUniquely;
use Laravel\Scout\Jobs\RemoveFromSearchUniquely;
use Laravel\Scout\Scout;

Scout::makeSearchableUsing(MakeSearchableUniquely::class);
Scout::removeFromSearchUsing(RemoveFromSearchUniquely::class);
Estos jobs se apoyan en los locks de jobs únicos de Laravel para no despachar operaciones duplicadas sobre el mismo modelo.

Requisitos por driver

Algolia

Configura las credenciales id y secret en config/scout.php e instala el SDK PHP de Algolia.
composer require algolia/algoliasearch-client-php
En .env:
ALGOLIA_APP_ID=your-app-id
ALGOLIA_SECRET=your-secret-key

Configuración del índice

Con Algolia puedes gestionar los ajustes del índice desde config/scout.php.
use App\Models\User;

'algolia' => [
    'id' => env('ALGOLIA_APP_ID', ''),
    'secret' => env('ALGOLIA_SECRET', ''),
    'index-settings' => [
        User::class => [
            'searchableAttributes' => ['id', 'name', 'email'],
            'attributesForFaceting' => ['filterOnly(email)'],
        ],
    ],
],
Después ejecuta scout:sync-index-settings para aplicarlos.
php artisan scout:sync-index-settings

Meilisearch

Meilisearch es un motor de búsqueda open source muy rápido. En desarrollo local lo más cómodo es la imagen Docker de Laravel Sail.
# Con Laravel Sail
./vendor/bin/sail up -d meilisearch
Sin Sail puedes arrancarlo directamente con Docker.
docker run -it --rm \
    -p 7700:7700 \
    getmeili/meilisearch:latest \
    meilisearch --master-key="masterKey"
Instala el SDK PHP.
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
Configura el driver y el host en .env.
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
Al actualizar Scout revisa siempre los cambios que rompen la compatibilidad del propio Meilisearch.

Configuración del índice (Meilisearch)

Con Meilisearch necesitas declarar de antemano en filterableAttributes las columnas por las que vas a filtrar con where(), y en sortableAttributes las que usarás con orderBy().
use App\Models\User;

'meilisearch' => [
    'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
    'key' => env('MEILISEARCH_KEY', null),
    'index-settings' => [
        User::class => [
            'filterableAttributes' => ['id', 'name', 'email'],
            'sortableAttributes' => ['created_at'],
        ],
    ],
],
Presta atención a los tipos numéricos: Meilisearch solo puede aplicar operadores (>, <, etc.) sobre datos con el tipo correcto.
public function toSearchableArray(): array
{
    return [
        'id' => (int) $this->id,
        'name' => $this->name,
        'price' => (float) $this->price,
    ];
}
Después ejecuta scout:sync-index-settings.
php artisan scout:sync-index-settings

Typesense

Typesense es un motor open source rápido que soporta búsqueda por palabra clave, semántica, geo y vectorial.
composer require typesense/typesense-php
Configura la conexión en .env.
SCOUT_DRIVER=typesense
TYPESENSE_API_KEY=masterKey
TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108
TYPESENSE_PATH=
TYPESENSE_PROTOCOL=http
Con Typesense, en toSearchableArray debes convertir la clave primaria a string y la fecha de creación a timestamp UNIX.
public function toSearchableArray(): array
{
    return array_merge($this->toArray(), [
        'id' => (string) $this->id,
        'created_at' => $this->created_at->timestamp,
    ]);
}

Motores database y collection

Ideales si quieres añadir búsqueda sin servicios externos. El motor database aprovecha los índices full-text y LIKE de MySQL/PostgreSQL. Suficiente para la mayoría de aplicaciones.
SCOUT_DRIVER=database
El motor collection filtra en PHP, así que funciona con cualquier base de datos soportada por Laravel, incluida SQLite. Ideal para desarrollo, tests y conjuntos pequeños.
SCOUT_DRIVER=collection
Con el motor database no necesitas gestionar índices manualmente: busca directamente contra la tabla.

Trait Searchable

Personalizar toSearchableArray()

Por defecto se indexa todo lo que devuelve toArray(). Para personalizarlo, sobrescribe toSearchableArray.
<?php

namespace App\Models;

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

class Post extends Model
{
    use Searchable;

    /**
     * Obtiene el array indexable del modelo.
     *
     * @return array<string, mixed>
     */
    public function toSearchableArray(): array
    {
        $array = $this->toArray();

        // Personaliza los datos...

        return $array;
    }
}

Personalizar el nombre del índice

Por defecto se usa el nombre de la tabla (en plural). Sobrescribe searchableAs para cambiarlo.
public function searchableAs(): string
{
    return 'posts_index';
}

Estrategias de búsqueda para el motor database

En el motor database puedes indicar, por columna, la estrategia más eficiente mediante atributos PHP.
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;

#[SearchUsingPrefix(['id', 'email'])]
#[SearchUsingFullText(['bio'])]
public function toSearchableArray(): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'bio' => $this->bio,
    ];
}
Antes de usar SearchUsingFullText, asegúrate de tener un índice full-text sobre esa columna.

Hacer buscable de forma condicional

Para que un modelo solo sea buscable bajo cierta condición, define shouldBeSearchable.
/**
 * Decide si el modelo debe ser buscable.
 */
public function shouldBeSearchable(): bool
{
    return $this->isPublished();
}
shouldBeSearchable no funciona con el motor database. Para conseguir el mismo comportamiento allí, utiliza cláusulas where.

Gestión del índice

Los comandos de esta sección son relevantes principalmente para motores externos (Algolia, Meilisearch, Typesense…). En el motor database no hace falta gestionar el índice.

Importar registros existentes

Al añadir Scout a un proyecto ya existente, importa los registros con scout:import.
php artisan scout:import "App\Models\Post"
Puedes hacerlo en segundo plano mediante la cola.
php artisan scout:queue-import "App\Models\Post" --chunk=500

Vaciar el índice

Para eliminar del índice todos los registros de un modelo, usa scout:flush.
php artisan scout:flush "App\Models\Post"

Pausar la sincronización

Para desactivar temporalmente la sincronización durante operaciones Eloquent, usa withoutSyncingToSearch.
use App\Models\Order;

Order::withoutSyncingToSearch(function () {
    // Las operaciones dentro no sincronizan con el índice
});

Añadir y eliminar registros manualmente

Puedes indexar colecciones a partir de una query.
// Indexa los resultados
Order::where('price', '>', 100)->searchable();

// A través de una relación
$user->orders()->searchable();
Para quitar registros del índice, unsearchable.
Order::where('price', '>', 100)->unsearchable();
Cuando eliminas un modelo con delete, también se elimina del índice automáticamente.

Búsqueda

Con search haces búsquedas. Encadena get para obtener una colección de modelos Eloquent.
use App\Models\Order;

$orders = Order::search('Star Trek')->get();
Si lo devuelves desde un controlador o ruta, se serializa a JSON automáticamente.
use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return Order::search($request->search)->get();
});
Para los resultados en crudo, raw.
$orders = Order::search('Star Trek')->raw();

Paginación

Con paginate obtienes resultados paginados igual que con una query Eloquent normal.
$orders = Order::search('Star Trek')->paginate();

// Cambia el tamaño de página
$orders = Order::search('Star Trek')->paginate(15);
En el motor database puedes usar simplePaginate, que es más eficiente en grandes volúmenes porque no calcula el total.
$orders = Order::search('Star Trek')->simplePaginate(15);
Ejemplo en Blade:
<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>

{{ $orders->links() }}

Filtros y orden

Con where añades filtros a la búsqueda.
use App\Models\Order;

// Igualdad
$orders = Order::search('Star Trek')->where('user_id', 1)->get();

// Alguno de los valores
$orders = Order::search('Star Trek')->whereIn('status', ['open', 'paid'])->get();

// Ninguno de los valores
$orders = Order::search('Star Trek')->whereNotIn('status', ['closed'])->get();
Con Meilisearch necesitas declarar los atributos filtrables antes de usar where.
También puedes personalizar la query Eloquent con query.
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with('invoices'))
    ->get();

Eager loading

Scout obtiene los IDs del motor y luego consulta Eloquent. Para evitar el problema N+1, indica el eager loading en query.
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with(['invoices', 'user']))
    ->get();
Para cargar relaciones durante la importación masiva, define makeAllSearchableUsing.
use Illuminate\Database\Eloquent\Builder;

protected function makeAllSearchableUsing(Builder $query): Builder
{
    return $query->with('author');
}
makeAllSearchableUsing puede no aplicarse durante importaciones por cola: cuando el job procesa la colección de modelos, las relaciones no se restauran.

Soft delete

Si el modelo indexado usa soft delete y quieres poder buscar también registros eliminados, pon soft_delete a true en config/scout.php.
'soft_delete' => true,
Con esto puedes usar withTrashed y onlyTrashed.
// Incluir eliminados
$orders = Order::search('Star Trek')->withTrashed()->get();

// Solo eliminados
$orders = Order::search('Star Trek')->onlyTrashed()->get();

Motores personalizados

Si ninguno de los motores integrados encaja, puedes implementar el tuyo. Debes extender la clase abstracta Laravel\Scout\Engines\Engine e implementar los ocho métodos siguientes.
use Laravel\Scout\Builder;

abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map(Builder $builder, $results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);
Como referencia, revisa la clase Laravel\Scout\Engines\AlgoliaEngine. Registra tu motor en el método boot de App\Providers\AppServiceProvider.
use App\ScoutExtensions\MySqlSearchEngine;
use Laravel\Scout\EngineManager;

public function boot(): void
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}
Después indícalo como driver en config/scout.php.
'driver' => 'mysql',

Páginas relacionadas

Eloquent ORM

Repasa el uso básico de los modelos Eloquent.

Relaciones Eloquent

Cómo definir relaciones y aplicar eager loading.

Colas

Scout puede actualizar el índice en segundo plano usando colas.
Última modificación el 13 de julio de 2026