Full-Text Search in Laravel with Meilisearch and Scout

Khimananda Oli 8 min read DevOps
Full-Text Search in Laravel with Meilisearch and Scout

By Khimananda Oli | Last reviewed: August 2026

Implementing full-text search in Laravel with Meilisearch and Scout solves the performance bottlenecks inherent in traditional database LIKE queries while delivering typo-tolerant, instant results. As your application data grows beyond a few thousand records, MySQL or PostgreSQL full-text indexes often fail to provide sub-second latency or relevance tuning without significant operational overhead. This guide walks you through a production-grade integration that balances developer experience with the rigorous performance demands of modern web applications.

How do you configure full-text search in Laravel with Meilisearch and Scout?

Setting up full-text search in Laravel with Meilisearch and Scout requires coordinating three distinct layers: the Laravel application, the Scout abstraction package, and the Meilisearch engine itself. A common mistake I see in audits is treating this as a simple composer install; in production, you must consider how these components communicate securely and efficiently. Before diving into code, ensure your infrastructure supports this architecture. If you are deploying to a VPS, refer to our guide on deploying Laravel on Ubuntu with Nginx to ensure your server has adequate resources for both PHP-FPM and the Meilisearch binary.

Laravel AppLaravel ScoutMeilisearchSearch()HTTP APIInverted Index
Data flows from Laravel through Scout to Meilisearch, which maintains an optimized inverted index for full-text search in Laravel with Meilisearch and Scout.

Installation and environment configuration

Begin by installing the required Composer dependencies. In 2026, Meilisearch v1.12+ and Scout v10.x are the stable production targets:

composer require laravel/scout meilisearch/meilisearch-php http-interop/http-factory-guzzle

Publish the Scout configuration file to customize driver behavior:

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

In your .env file, set the driver and connection details. Never commit master keys to version control; use read-only API keys for application-level searching:

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=your-master-key-or-search-key

Making models searchable

Add the Searchable trait to any Eloquent model you want indexed. For a Product model, define which attributes should be included in the index via the toSearchableArray method. This is where you control data shape and reduce index size:

use Laravel\Scout\Searchable;

class Product extends Model
{
    use Searchable;

    public function toSearchableArray()
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'description' => strip_tags($this->description),
            'sku' => $this->sku,
            'category' => $this->category?->name,
            'price' => $this->price,
            'created_at' => $this->created_at->timestamp,
        ];
    }
}

How does Meilisearch compare to Elasticsearch for Laravel projects?

Choosing between Meilisearch and Elasticsearch is a frequent architectural decision when implementing full-text search in Laravel with Meilisearch and Scout. While Elasticsearch remains the industry standard for massive-scale analytics and complex aggregations, Meilisearch has carved out a definitive niche for application-level search where developer velocity and predictable relevance matter more than raw cluster scale.

CriteriaMeilisearchElasticsearch
Setup ComplexitySingle binary, zero-config defaultsMulti-node cluster, JVM tuning required
Relevance TuningBuilt-in typo tolerance, synonymsManual scoring scripts, analyzers
Resource Usage~200MB RAM baseline2GB+ RAM minimum per node
Laravel IntegrationNative Scout driver, first-partyCommunity drivers, config drift
Analytics & AggregationsLimited facet supportFull aggregation pipeline
Best ForE-commerce, docs, SaaS searchLog analysis, observability, big data

In my experience helping Nepali startups and global teams alike, Meilisearch wins for 90% of Laravel applications. The operational savings are substantial: no JVM heap management, no shard rebalancing headaches, and updates that don't require rolling restarts. Reserve Elasticsearch for cases where you need to aggregate millions of log entries or perform geospatial analytics at scale.

How do you optimize indexing performance for large datasets?

Indexing strategy separates toy implementations from production systems. When building full-text search in Laravel with Meilisearch and Scout, naive bulk imports can overwhelm your database and delay search availability. Understanding the synchronization pipeline helps you avoid these pitfalls.

Model EventScout ObserverQueue JobMeilisearchBatch Processing (500 records)Reduces HTTP overheadPrevents timeout errorsInitial Import Commandphp artisan scout:import "App\Models\Product" --chunk=500
Scout batches model changes through queues before syncing to Meilisearch, preventing API throttling during full-text search in Laravel with Meilisearch and Scout.

Chunked imports and queue configuration

Always enable queue-based syncing in config/scout.php. Synchronous indexing blocks user requests and creates cascading failures under load:

'queue' => [
    'connection' => 'redis',
    'queue' => 'scout-indexing',
],

For initial imports of existing data, use chunked processing to manage memory and respect Meilisearch's payload limits. The default chunk size of 500 works well for most schemas, but adjust based on your document size:

php artisan scout:import "App\Models\Product" --chunk=500

If you're containerizing this workflow, our Docker for Laravel guide covers running Meilisearch alongside your app in development. In production, I recommend dedicated Meilisearch Cloud instances or self-hosted deployments with persistent volumes—never ephemeral containers for search indexes.

Customizing searchable data and filters

Reduce index size and improve query speed by excluding unnecessary fields. Use conditional logic in toSearchableArray to skip soft-deleted or unpublished records:

public function shouldBeSearchable()
{
    return $this->is_published && !$this->trashed();
}

public function toSearchableArray()
{
    $data = $this->toArray();
    
    // Remove heavy relationships not needed for search
    unset($data['full_description'], $data['metadata']);
    
    // Add computed fields for filtering
    $data['in_stock'] = $this->inventory_count > 0;
    $data['price_range'] = match(true) {
        $this->price < 1000 => 'budget',
        $this->price < 5000 => 'mid',
        default => 'premium',
    };
    
    return $data;
}

Basic text matching is table stakes. Real-world full-text search in Laravel with Meilisearch and Scout requires combining relevance with structured filters. Meilisearch distinguishes between filterable attributes (exact match, range) and sortable attributes, requiring explicit configuration.

Configuring filterable and sortable attributes

Create a dedicated Artisan command or migration to set index settings. Relying on auto-detection leads to inconsistent behavior across environments:

use Meilisearch\Client;

$client = new Client(config('scout.meilisearch.host'), config('scout.meilisearch.key'));
$index = $client->index('products');

$index->updateFilterableAttributes([
    'category',
    'in_stock',
    'price_range',
    'created_at',
]);

$index->updateSortableAttributes([
    'price',
    'created_at',
]);

Executing filtered searches in controllers

Scout's builder exposes Meilisearch's filter syntax directly. Combine text queries with structured constraints for precise results:

$results = Product::search($request->input('q'))
    ->where('in_stock', true)
    ->where('category', 'electronics')
    ->orderBy('price', 'asc')
    ->paginate(20);

// Advanced filter syntax for ranges/OR conditions
$results = Product::search($request->input('q'))
    ->raw() // Access underlying Meilisearch client
    ->search([
        'filter' => 'price >= 1000 AND price <= 5000 AND (category = "electronics" OR category = "gadgets")',
        'facets' => ['category', 'price_range'],
    ]);

This hybrid approach gives you the simplicity of Scout's Eloquent integration with the power of Meilisearch's native filtering when needed. For teams managing multiple services, integrating this with a robust CI/CD pipeline ensures index settings stay synchronized across staging and production. See our GitLab CI for Laravel tutorial for automation patterns.

Basic Text Search❌ Out-of-stock items shown❌ Irrelevant categories mixed❌ No price range control❌ Poor conversion rateFaceted + Filtered Search✅ In-stock only✅ Category-scoped results✅ Price range applied✅ Higher conversion & UXvs
Advanced filtering transforms generic results into actionable, conversion-ready outcomes in full-text search in Laravel with Meilisearch and Scout.

What are the best practices for monitoring and maintaining search indexes?

Search infrastructure fails silently. Without observability, you won't know your index is stale or your queries are degrading until users complain. Treat full-text search in Laravel with Meilisearch and Scout as a first-class production service requiring the same rigor as your primary database.

  • Monitor index lag: Track the time between model updates and index reflection. Scout provides events like ModelsImported; log these to your observability stack. If you use Prometheus, our monitoring setup guide covers custom metric exporters.
  • Set up health checks: Meilisearch exposes /health and /stats endpoints. Integrate these into your load balancer health checks and alerting. A 5xx on search should page on-call just like a database failure.
  • Version your index settings: Store filterable/sortable attribute configurations in version-controlled migrations or deployment scripts. Drift between environments causes subtle bugs that surface only in production.
  • Plan for reindexing: Schema changes require full reimports. Schedule these during low-traffic windows and use Meilisearch's swap feature to avoid downtime: create a new index, import data, then atomically swap aliases.
  • Audit search queries: Enable Meilisearch's analytics or forward query logs to understand what users actually search for. Missing synonyms and typos reveal gaps in your relevance configuration.

Security matters equally. Restrict Meilisearch access via network policies; never expose port 7700 publicly. Use scoped API keys with tenant tokens for multi-tenant applications, ensuring users can only search their own data. This aligns with SOC 2 and ISO 27001 controls around data isolation and least privilege.

Implementing full-text search in Laravel with Meilisearch and Scout delivers immediate UX improvements, but long-term success depends on treating search as an engineered system, not an afterthought. Start with the basics outlined here, measure real query performance, and iterate on relevance based on actual user behavior rather than assumptions. If your team needs help architecting scalable search infrastructure, passing compliance audits, or optimizing cloud costs around search workloads, reach out to discuss your specific requirements.

Frequently Asked Questions

Run composer require laravel/scout meilisearch/meilisearch-php via CLI. Then publish the scout config file and set SCOUT_DRIVER=meilisearch in your env file. Finally, start a local Meilisearch instance using Docker or the official binary before running migrations.

Yes, the core engine is open source and free for self-hosted production use. Cloud hosting via Meilisearch Cloud incurs monthly fees based on instance size, but self-managed deployments on your own infrastructure have zero licensing costs for full-text search functionality.

Meilisearch offers simpler setup, lower memory usage, and native Laravel Scout integration without complex cluster management. Elasticsearch provides deeper aggregation features and horizontal scaling for massive datasets, but Meilisearch typically delivers faster relevance tuning and easier maintenance for standard application search requirements.

Verify your model uses the Searchable trait and has been imported via php artisan scout:import. Check that MEILISEARCH_HOST and MEILISEARCH_KEY match your running instance. Also confirm the index name in searchableAs matches what exists in the Meilisearch dashboard.

Yes, define a toSearchableArray method on your model to include related attributes as flat fields. Meilisearch cannot join tables natively, so denormalize relationship data into the searchable array during indexing to enable filtering and faceting on associated values.

Update the rankingRules array in your Meilisearch index settings via the API or Scout configuration. Prioritize attributes like title over description, adjust typo tolerance thresholds, and add custom scoring expressions to refine relevance without modifying application code or reindexing data.

Yes, Scout listens to Eloquent created, updated, and deleted events when SCOUT_QUEUE is disabled. For high-traffic apps, enable queue-based syncing to prevent blocking HTTP requests during indexing operations and ensure consistent search index updates across distributed environments.

The default limit is 16MB per document in Meilisearch 1.12. Exceeding this causes indexing failures. Reduce payload size by excluding large text blobs from toSearchableArray or splitting content into multiple documents linked by a shared identifier for retrieval.

Use tenant-scoped API keys generated via the Meilisearch key management API with index-level permissions. Never expose the master key in frontend code. Combine scoped keys with Laravel policies to ensure users only query indexes containing their own organization’s data.

Yes, configure filterableAttributes and faceting settings in your index. Return facet counts alongside results using Scout’s raw search method. This enables dynamic category filters, price ranges, and tag navigation without additional database queries or external aggregation services.

Run php artisan scout:flush followed by php artisan scout:import for each searchable model. Alternatively, use the Meilisearch API to delete and recreate the index with updated settings before reimporting. Always test schema changes in staging before applying to production indexes.

Yes, define synonyms via the Meilisearch synonyms API endpoint or through Scout configuration. Map equivalent terms like laptop and notebook to improve recall without duplicating data. Synonyms apply at query time and do not require reindexing existing documents.

Scout throws an exception by default. Implement a fallback by catching search exceptions and querying the database with LIKE clauses temporarily. Configure health checks and alerts to detect outages quickly, and consider running Meilisearch in high-availability mode for critical production workloads.

Allocate at least 4GB RAM for indexes under one million documents. Memory usage scales with dataset size and query complexity. Monitor heap usage via the /stats endpoint and provision additional resources before hitting limits to avoid OOM kills during peak search traffic.

Yes, point SCOUT_HOST to your managed Meilisearch Cloud endpoint since Vapor lacks persistent storage. Store credentials in environment variables via AWS Secrets Manager. Ensure your VPC allows outbound HTTPS to the Meilisearch Cloud region to maintain low-latency search responses.