Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Searchindex Laravel Package

spatie/searchindex

Opinionated Laravel package to index and search objects via a unified API. Supports Elasticsearch and Algolia, with simple upsert and query methods for any model implementing the Searchable interface.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require spatie/searchindex
    

    Publish the config file (if needed):

    php artisan vendor:publish --provider="Spatie\SearchIndex\SearchIndexServiceProvider"
    
  2. Configure the Driver Edit config/searchindex.php to specify either elasticsearch or algolia as the default driver. Configure credentials and connection details (e.g., Elasticsearch host, Algolia app ID/secret).

  3. Implement the Searchable Interface For a model (e.g., Product), implement the required methods:

    use Spatie\SearchIndex\Searchable;
    
    class Product implements Searchable
    {
        public function getSearchableData()
        {
            return [
                'title' => $this->title,
                'description' => $this->description,
                'price' => $this->price,
                'tags' => $this->tags,
            ];
        }
    
        public function getSearchableId()
        {
            return $this->id;
        }
    }
    
  4. First Indexing Use the SearchIndex facade to index a model:

    use Spatie\SearchIndex\Facades\SearchIndex;
    
    $product = Product::find(1);
    SearchIndex::upsertToIndex($product);
    
  5. First Search Retrieve results from the search index:

    $results = SearchIndex::getResults('laptop');
    // $results is a collection of indexed models
    

Implementation Patterns

Core Workflows

  1. Indexing Models

    • Bulk Indexing: Use SearchIndex::upsertToIndex($model) for single models or loop through collections:
      Product::all()->each(fn ($product) => SearchIndex::upsertToIndex($product));
      
    • Queue Jobs: For large datasets, dispatch a job (e.g., IndexProductsJob) to avoid timeouts:
      dispatch(new IndexProductsJob($products));
      
      class IndexProductsJob implements ShouldQueue
      {
          use Dispatchable, InteractsWithQueue, Queueable;
      
          public function handle()
          {
              foreach ($this->products as $product) {
                  SearchIndex::upsertToIndex($product);
              }
          }
      }
      
  2. Searching

    • Basic Search:
      $results = SearchIndex::getResults('query', ['limit' => 10]);
      
    • Filtered Search: Pass filters as an array:
      $results = SearchIndex::getResults('query', [
          'filters' => ['price' => ['min' => 100]],
          'limit' => 5,
      ]);
      
    • Faceted Search: Use getResultsWithFacets for Algolia or custom Elasticsearch aggregations.
  3. Deleting from Index

    • Remove a single model:
      SearchIndex::deleteFromIndex($product);
      
    • Clear the entire index (use cautiously):
      SearchIndex::clearIndex();
      
  4. Customizing Search Behavior

    • Override Searchable Data: Extend the model to modify getSearchableData() dynamically:
      public function getSearchableData()
      {
          return array_merge(parent::getSearchableData(), [
              'is_featured' => $this->isFeatured,
          ]);
      }
      
    • Custom Search Queries: Use the underlying driver directly for complex queries:
      $client = SearchIndex::getClient();
      $response = $client->search([...]); // Algolia/Elasticsearch raw query
      

Integration Tips

  1. Event-Based Indexing Listen to model events (e.g., saved, deleted) to auto-index:

    Product::saved(function ($product) {
        SearchIndex::upsertToIndex($product);
    });
    
  2. Soft Deletes Handle soft-deleted models by checking deleted_at in getSearchableData():

    public function getSearchableData()
    {
        return $this->deleted_at ? [] : [...];
    }
    
  3. Testing Use SearchIndex::fake() in tests to mock the search index:

    public function test_search()
    {
        SearchIndex::fake();
    
        SearchIndex::shouldReceive('getResults')
            ->once()
            ->andReturn([$fakeProduct]);
    
        // Test your search logic
    }
    
  4. Multi-Tenant Indexes Scope the index by tenant in getSearchableId():

    public function getSearchableId()
    {
        return "tenant_{$this->tenant->id}_product_{$this->id}";
    }
    

Gotchas and Tips

Pitfalls

  1. Outdated Package

    • Issue: Last release was in 2017; may not support Laravel 8/9+ or newer PHP versions.
    • Workaround: Fork the package or use a maintained alternative (e.g., scout or laravel-elasticsearch).
  2. Elasticsearch Version Mismatch

    • Issue: The package may not support Elasticsearch 7+/8+ out of the box.
    • Fix: Check the elasticsearch.php config for deprecated settings (e.g., typedoc_type).
  3. Algolia Rate Limits

    • Issue: Free Algolia plans have strict rate limits (e.g., 1000 ops/day).
    • Tip: Monitor usage via Algolia dashboard and implement caching for frequent searches.
  4. Data Serialization

    • Issue: Complex data (e.g., relationships, nested objects) may not serialize correctly.
    • Solution: Flatten data in getSearchableData() or use toArray():
      public function getSearchableData()
      {
          return $this->toArray();
      }
      
  5. Case Sensitivity

    • Issue: Searches may be case-sensitive by default (varies by driver).
    • Fix: Configure analyzers in Elasticsearch or use Algolia’s typoTolerance:
      $results = SearchIndex::getResults('query', [
          'typoTolerance' => 'min',
      ]);
      

Debugging

  1. Check Raw Queries Enable logging for the underlying client:

    // config/searchindex.php
    'log_queries' => env('SEARCH_INDEX_LOG_QUERIES', false),
    

    Or inspect the client directly:

    $client = SearchIndex::getClient();
    $client->getLogger()->setLevel(\Monolog\Logger::DEBUG);
    
  2. Validate Indexed Data Use the driver’s API to inspect the index:

    • Elasticsearch:
      curl -XGET 'http://localhost:9200/your_index/_search?pretty'
      
    • Algolia: Use the Algolia Dashboard to browse indices.
  3. Handle Missing Fields If a field is missing during search, use null_as_default in filters:

    $results = SearchIndex::getResults('query', [
        'filters' => ['category' => ['exists' => true]],
    ]);
    

Extension Points

  1. Custom Drivers Extend the package by creating a custom driver (e.g., for OpenSearch or Meilisearch):

    class CustomDriver implements DriverContract
    {
        public function upsert($model)
        {
            // Custom logic
        }
    
        public function search($query, array $options)
        {
            // Custom logic
        }
    }
    

    Register it in SearchIndexServiceProvider.

  2. Modify Search Results Override the SearchIndex facade to transform results:

    class CustomSearchIndex extends Facade
    {
        protected static function getFacadeAccessor()
        {
            return 'searchIndex';
        }
    
        public static function getResults($query, array $options = [])
        {
            $results = parent::getResults($query, $options);
            return $results->map(fn ($model) => $model->load('relationship'));
        }
    }
    
  3. Add Synonyms or Stop Words Configure the driver to include synonyms (Elasticsearch) or custom stop words:

    // config/searchindex.php (Elasticsearch)
    'elasticsearch' => [
        'settings' => [
            'analysis' => [
                'filter' => [
                    'english_stop' => ['type' => 'stop', 'stopwords' => '_english_'],
                ],
                'analyzer' => [
                    'custom_analyzer' => [
                        'tokenizer' => 'standard',
                        'filter' => ['english_stop', 'lowercase'],
                    ],
                ],
            ],
        ],
    ];
    
  4. Batch Processing For large indices, implement chunked processing:

    Product::chunk(100, function ($products) {
        foreach
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport