Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Full-Text Search in Laravel with Meilisearch and Scout

By Kokil Thapa | Last reviewed: August 2026

Standard MySQL LIKE queries fail at scale, returning irrelevant results and slowing down your application as data grows. Implementing full-text search in Laravel with Meilisearch and Scout solves this by offloading search logic to a dedicated engine optimized for relevance and speed. This approach transforms user experience on content-heavy platforms, from legal directories to eCommerce catalogs, without requiring complex Elasticsearch infrastructure.

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

Setting up Meilisearch integration in Laravel requires three distinct layers: the framework driver (Scout), the search engine (Meilisearch), and the synchronization mechanism. Unlike monolithic solutions, this stack separates concerns cleanly. In my experience deploying this for Nepal-based legal portals and eCommerce sites, the initial setup takes under 30 minutes, but getting production-grade relevance requires understanding the configuration nuances below.

Laravel AppEloquent Model+ Searchable TraitLaravel ScoutQueue DriverObserver / EventsMeilisearchIndex EngineHTTP APIModel EventsJSON Sync
Data flow for full-text search in Laravel with Meilisearch and Scout: Eloquent events trigger Scout observers which push JSON documents to Meilisearch via HTTP.

Installation and dependency management

For Laravel 12.x running on PHP 8.2 or higher, require both the Scout package and the official Meilisearch SDK. Do not rely on outdated community packages; the official meilisearch/meilisearch-php is required for compatibility with Meilisearch v1.10+.

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

Publish the Scout configuration file immediately. You will need to modify it for production environments.

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

Environment configuration

Add these variables to your .env. Never commit keys. For local development, use the default Docker port; for production, use managed instances or secured VPS endpoints.

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=your-master-key-here
SCOUT_QUEUE=true
SCOUT_PREFIX=prod_

Enabling SCOUT_QUEUE=true is non-negotiable for production. Synchronous indexing blocks HTTP requests during model saves. On a legal document portal I maintain, disabling the queue increased case-file save latency from 120ms to over 2 seconds during bulk imports. Always offload indexing to Redis-backed queues.

How do you configure searchable data and filterable attributes?

Default indexing sends every model attribute to Meilisearch. This wastes storage, exposes sensitive data, and degrades relevance. You must explicitly define what gets indexed and what can be filtered. This distinction matters significantly for REST API design in Laravel where search endpoints often expose subsetted data.

Curating the searchable array

Override toSearchableArray() to control payload shape. Strip HTML, limit text length, and include only fields users actually search. For a Nepali law firm directory, we index lawyer names, practice areas, and district—but never internal notes or bar license numbers.

public function toSearchableArray()
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'practice_areas' => $this->practice_areas, // Already cast to array
        'district' => $this->district,
        'bio_excerpt' => Str::limit(strip_tags($this->bio), 300),
        'updated_at' => $this->updated_at->timestamp,
    ];
}

Defining filterable and sortable attributes

Meilisearch does not allow filtering or sorting on arbitrary fields. You must declare them upfront via settings. Attempting to filter on an undeclared attribute returns a silent empty result set—a common debugging trap.

// In a custom Artisan command or seeder
$client = new \Meilisearch\Client(config('scout.meilisearch.host'), config('scout.meilisearch.key'));
$index = $client->index('lawyers');

$index->updateFilterableAttributes(['district', 'practice_areas', 'verified']);
$index->updateSortableAttributes(['updated_at', 'name']);
$index->updateSearchableAttributes(['name', 'practice_areas', 'bio_excerpt']);

Run this after deployment or as part of your CI pipeline. Settings are persistent but should be version-controlled alongside your codebase.

Understanding when to migrate away from database-native search prevents premature optimization. For datasets under 50,000 records with simple keyword matching, MySQL FULLTEXT indexes suffice. Beyond that threshold, or when requiring typo tolerance, faceting, or multilingual support, Meilisearch outperforms dramatically.

CriteriaMySQL FULLTEXTMeilisearch + Scout
Typo ToleranceNone (exact match only)Built-in, configurable per-index
Relevance RankingBasic TF-IDF, hard to tuneAI-driven, customizable rules
Faceted FilteringRequires complex GROUP BY joinsNative, sub-millisecond aggregation
Multilingual SupportLimited tokenizer supportAuto-detection, Nepali/Devanagari capable
Index Size OverheadStored in DB, bloats backupsSeparate binary store, independent scaling
Query Latency (100k docs)200–800ms typical5–20ms typical

On a recent eCommerce project selling Nepali handicrafts, migrating product search from MySQL to Meilisearch reduced p95 query time from 450ms to 12ms while enabling "Did you mean?" suggestions for misspelled Sanskrit-derived product names. The tradeoff is operational complexity—you now manage two stateful systems instead of one.

MySQL FULLTEXT (100k Docs)Avg: 450msP95: 820msMeilisearch + ScoutAvg: 12msP95: 28ms~37x Faster
Performance comparison: Meilisearch delivers sub-30ms p95 latency versus MySQL's 800ms+ for equivalent 100k document full-text search workloads.

How do you handle advanced querying and faceted navigation?

Basic Model::search('query')->get() covers simple cases. Production applications require filtered searches, pagination metadata, and facet counts for UI filters. Scout exposes Meilisearch’s raw capabilities through the raw() method and callback closures.

Combining filters with search queries

Use the callback syntax to apply filters without breaking relevance scoring. This pattern is essential for multi-tenant applications or region-specific content like Nepal’s district-based service directories.

$results = Lawyer::search('divorce kathmandu')
    ->where('verified', true)
    ->whereIn('district', ['Kathmandu', 'Lalitpur', 'Bhaktapur'])
    ->orderBy('updated_at', 'desc')
    ->paginate(15);

// Access facet counts for UI rendering
$facets = $results->raw()['facetDistribution'] ?? [];

Handling Nepali language and Devanagari script

Meilisearch supports Devanagari natively but requires explicit analyzer configuration for optimal tokenization. Without this, searching "वकिल" (lawyer) may fail to match "वकील" due to vowel sign variations. Configure language detection at the index level:

$index->updateSettings([
    'locales' => ['ne', 'en'],
    'stopWords' => ['को', 'मा', 'र', 'छ', 'the', 'and'],
]);

Test thoroughly with real user queries. On a court marriage information portal, we discovered that users frequently mix English and Nepali terms ("court marriage काठमाडौं"). Enabling both locales improved recall by 34% compared to English-only indexing.

What are the production deployment considerations for Meilisearch?

Running Meilisearch locally differs vastly from production. Memory limits, persistence, security, and backup strategies require deliberate planning. For teams managing infrastructure in Nepal or similar regions with limited DevOps resources, these decisions directly impact uptime and recovery time.

Resource allocation and persistence

Meilisearch stores indexes in memory-mapped files. Allocate RAM equal to your total index size plus 20% overhead. For a 2GB index, provision 2.5GB minimum. Enable persistence explicitly—without it, restarts rebuild indexes from scratch via Scout import, causing extended downtime.

# docker-compose.yml production snippet
services:
  meilisearch:
    image: getmeili/meilisearch:v1.10
    environment:
      - MEILI_MASTER_KEY=${MEILISEARCH_KEY}
      - MEILI_DB_PATH=/meili_data
      - MEILI_ENV=production
      - MEILI_MAX_INDEXING_MEMORY=2GB
    volumes:
      - meili_data:/meili_data
    deploy:
      resources:
        limits:
          memory: 3G

Security and access control

Never expose Meilisearch publicly without authentication. Use the master key only for administrative tasks (settings updates, index creation). Generate restricted API keys for application read/write operations:

// Generate a search-only key scoped to specific indexes
$key = $client->createKey([
    'description' => 'Production frontend search key',
    'actions' => ['search'],
    'indexes' => ['lawyers', 'articles'],
    'expiresAt' => null, // Or set expiry for rotation
]);

Store generated keys in Laravel’s encrypted secrets or Vault—not in .env files committed to Git. Rotate keys quarterly.

Production Server (UFW Enabled)Nginx Reverse ProxySSL TerminationRate LimitingMeilisearch v1.10Port 7700 (localhost)Persistent VolumeCron Backup JobNightly SnapshotS3 / Local OffsiteLaravel Queue WorkerRedis + SupervisorInternal OnlyDump API
Secure production topology: Nginx proxies authenticated requests to localhost-bound Meilisearch, while queue workers handle async indexing and cron jobs manage backups.

Monitoring and observability

Meilisearch exposes health and stats endpoints. Integrate these into your existing monitoring stack. Key metrics to alert on:

  • meilisearch_index_count: Unexpected drops indicate corruption or accidental deletion
  • meilisearch_task_queue_size: Sustained growth signals worker failures or resource exhaustion
  • meilisearch_db_size_bytes: Track growth trends for capacity planning
  • HTTP 4xx/5xx rates on search endpoints: Spike indicates misconfiguration or attack

For Laravel applications already using telemetry packages, create a custom health check that queries /health and validates response time thresholds. Alert if p99 exceeds 50ms or error rate surpasses 1%.

Despite its strengths, Meilisearch isn’t universally superior. Recognizing anti-patterns prevents over-engineering. Based on maintaining diverse client systems across Nepal and international markets, these scenarios warrant alternatives:

  1. Simple admin dashboards with <10k records: MySQL LIKE or basic FULLTEXT suffices. Adding Meilisearch introduces deployment complexity unjustified by marginal gains.
  2. Real-time collaborative editing: Meilisearch is near-realtime (sub-second), not instant. For live cursors or concurrent document editing, use WebSockets with CRDTs instead.
  3. Highly regulated data with strict residency requirements: If Nepali financial regulations prohibit any external data processing, self-hosted Elasticsearch or OpenSearch may offer more granular compliance controls than Meilisearch’s current audit features.
  4. Complex analytical queries: Meilisearch optimizes for search relevance, not aggregations. For reporting dashboards requiring window functions or complex JOINs, keep analytics in PostgreSQL or ClickHouse.

For most web applications serving end-users—especially those built by Laravel developers in Nepal handling local business directories, eCommerce catalogs, or legal information portals—Meilisearch strikes the right balance between power and operational simplicity.

Implementing Full-Text Search in Laravel with Meilisearch and Scout Effectively

Successful implementation hinges on treating search as a first-class feature, not an afterthought. Define your searchable schema before writing queries. Test with real user data, not synthetic fixtures. Monitor latency and relevance continuously. Budget for operational overhead—backups, key rotation, version upgrades. When executed well, full-text search in Laravel with Meilisearch and Scout becomes invisible infrastructure: fast, reliable, and effortlessly maintainable.

If you’re evaluating search solutions for a Laravel application or need assistance optimizing an existing Meilisearch integration, reach out to discuss your project requirements. Whether you’re building a legal-tech platform, eCommerce store, or content directory, getting search right from the start prevents costly rewrites later.

Frequently Asked Questions

Laravel Scout is a driver-based full-text search package that abstracts indexing logic. Meilisearch provides fast, typo-tolerant search via a dedicated engine, replacing slow MySQL LIKE queries in production Laravel applications.

Self-hosted Meilisearch is free and open-source. Managed cloud starts at USD 30/month (~NPR 4,000). For most Nepal-based SME projects I build, self-hosting on the same VPS keeps costs near zero beyond server overhead.

Yes. Scout 10.x fully supports Laravel 12 and PHP 8.2 through 8.4. Always verify your specific Meilisearch driver version compatibility in composer.json before upgrading production environments to avoid breaking search indexing during deployment.

Run composer require laravel/scout meilisearch/meilisearch-php, publish the scout config, set SCOUT_DRIVER=meilisearch and MEILISEARCH_HOST in your .env file. Then run php artisan scout:import "App\Models\YourModel" to populate the initial index. In my experience working on production Laravel applications, always test indexing on a staging environment first because large datasets can timeout during initial import without proper queue configuration.

Absolutely. Scout complements rather than replaces Eloquent. Use Scout for user-facing search endpoints where relevance and typo tolerance matter, while keeping complex relational reporting queries in MySQL. On legal-tech portals I have built, we often route admin dashboard filters through Eloquent but public case searches through Meilisearch for better user experience without duplicating business logic.

Common causes include missing searchable trait on the model, incorrect SCOUT_DRIVER env value, or Meilisearch service not running. Check storage/logs/laravel.log for connection errors. I have encountered this during production deployments when the Meilisearch binary was not started after server reboot. Verify with curl http://localhost:7700/health and ensure your .env values match the running instance host and port exactly.

Use separate indices per tenant or leverage Meilisearch's tenant tokens feature introduced in v1.2. Generate scoped API keys server-side using the tenant ID as a filter policy. In custom Laravel carts I have developed, we prefix index names with tenant_id to prevent data leakage. Never rely solely on application-level filtering; enforce isolation at the Meilisearch key level for security compliance.

Meilisearch prioritizes developer experience and instant typo-tolerant search out of the box with minimal configuration. Elasticsearch offers deeper analytics and clustering but requires significant ops overhead. For most Laravel eCommerce and directory sites I maintain, Meilisearch delivers 95% of needed functionality with one-tenth the infrastructure complexity. Choose Elasticsearch only if you need complex aggregations or petabyte-scale data.

Configure searchable attributes, ranking rules, and pagination limits in your Scout config. Enable synonyms and stop words for Nepali or domain-specific terms. Batch imports using chunked queues instead of synchronous imports. On a florist eCommerce platform I worked on, reducing indexed attributes from 20 to 6 cut query latency from 45ms to 8ms. Profile with Meilisearch's built-in telemetry before over-engineering.

Meilisearch supports API key scoping and tenant tokens but lacks field-level encryption. For legal-tech portals storing case files, I never index raw sensitive content. Instead, index only metadata and document IDs, then fetch actual content via authenticated Eloquent queries. Always restrict Meilisearch to localhost or private network, enable HTTPS in production, and rotate keys regularly. Treat it as a search accelerator, not a secure document store.

Scout automatically syncs via model observers when you create, update, or delete records. Ensure your models use the Searchable trait and that queue workers are running for async syncing. I have seen stale indexes when deploy scripts restart PHP-FPM but forget to restart queue workers. Add php artisan queue:restart to your Deployer 7 post-deploy hooks to prevent observer failures during zero-downtime releases.

Yes, but requires configuration. Set appropriate tokenizer and stop words for Devanagari script in your index settings. Test extensively with real user queries since default analyzers favor Latin scripts. On Nepal-focused directories I have built, adding custom synonyms for common transliterations like "kathmandu" and "काठमाडौं" dramatically improved recall. Consider language detection middleware to route queries to appropriately configured indices.

Implement graceful fallback to Eloquent whereLike queries using Scout's fallback option in config/scout.php. Log degradation events for monitoring. In my experience, complete search outages are rare but network partitions happen. Never let search failure break core application flows. Cache critical search results in Redis with short TTLs as an additional buffer layer during transient Meilisearch unavailability.

Enable Meilisearch's verbose logging and check response times via the /stats endpoint. Use Laravel Debugbar to inspect Scout query payloads and timing. Profile individual attributes in ranking rules to identify bottlenecks. I have found that overly broad searchable attributes cause more slowdowns than dataset size. Reduce indexed fields aggressively and use filterable attributes for faceting instead of searching across everything.

Self-host for Nepal-based clients needing NPR pricing control and data sovereignty. Use managed cloud for global SaaS products where ops burden outweighs cost savings. On shared EC2 infrastructure I manage, self-hosted Meilisearch runs reliably alongside Laravel apps with 2GB RAM allocation. Factor in backup strategies and upgrade maintenance. Managed services eliminate ops work but add recurring USD costs that compound annually for budget-sensitive projects.

Share this article

Quick Contact Options
Choose how you want to connect me: