> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lunarphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Search

> Query Lunar's Scout search indexes with faceted filtering, sorting, and pagination across the database, Meilisearch, and Typesense drivers.

The Search add-on provides a query layer on top of the search indexes described in the [Search reference](/2.x/reference/search). It wraps Laravel Scout and adds support for faceted search, filtering, sorting, and consistent response formatting using [Spatie Laravel Data](https://spatie-laravel-data.com).

## Supported Engines

| Engine      | Driver Name   | Notes                                                                                  |
| ----------- | ------------- | -------------------------------------------------------------------------------------- |
| Database    | `database`    | Uses Scout's database driver directly. No facet support.                               |
| Meilisearch | `meilisearch` | Faceted search and filtering, via a Meilisearch multi-search request per active facet. |
| Typesense   | `typesense`   | Faceted search and filtering, plus highlights and hybrid/vector search.                |

The Meilisearch and Typesense PHP clients ship as dependencies of this add-on, so both drivers are available once the add-on is installed. Meilisearch also has a separate, optional add-on (see [Meilisearch](#meilisearch) below) that configures index settings — it is not required to query Meilisearch through this add-on.

## Installation

### Require the Composer package

```sh theme={null}
composer require lunarphp/search
```

The package auto-discovers its `Lunar\Search\SearchServiceProvider`, so no additional registration is needed.

### Configuration

The add-on ships its own `facets` configuration, merged under the same `lunar.search` key used by the core search config:

```php theme={null}
// packages/search/config/search.php
use Lunar\Core\Models\Product;

return [
    'facets' => [
        Product::class => [
            'brand' => [],
        ],
    ],
];
```

This config is merged, not published — there is no `vendor:publish` tag for it. To override or extend it, create `config/lunar/search.php` in the host application; any keys not present there fall back to the add-on's defaults. The `engine_map` and `models` keys read by the core search config (see the [Search reference](/2.x/reference/search)) live in the same file.

Each key under a model's `facets` entry corresponds to a field in that model's searchable index. The value is an array of per-facet-value configuration, used to attach extra data (such as a hex color) to a specific value:

```php theme={null}
'facets' => [
    Product::class => [
        'brand' => [
            'label' => 'Brand',
        ],
        'colour' => [
            'label' => 'Colour',
            'Red' => [
                'hex_value' => '#FF0000',
            ],
        ],
    ],
],
```

`label` is optional and defaults to the field name. Additional keys per facet value (like `hex_value` above) are merged into the corresponding `SearchFacetValue` in the response.

<Tip>
  `engine_map`, which controls which search driver is used for each model, is defined in the core Lunar search config. See the [Search reference](/2.x/reference/search) for details.
</Tip>

## Usage

### Basic Search

Search models using the `Search` facade. By default, searches are performed against `Lunar\Core\Models\Product`:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')->get();
```

`Search` resolves to `Lunar\Search\SearchManager`, an `Illuminate\Support\Manager`. Query methods such as `query()`, `filter()`, `sort()`, and `get()` are defined on the underlying engine (`Lunar\Search\Engines\AbstractEngine`), not on the manager itself — calling them on the `Search` facade forwards the call to the resolved driver automatically.

To search a different model, use the `model()` method:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::model(\Lunar\Core\Models\Collection::class)
    ->query('Hoodies')
    ->get();
```

The package detects which Scout driver is mapped for the given model via the `engine_map` configuration and performs the search using that driver. Results are not hydrated from the database — the raw indexed data is returned directly from the search provider.

### Specifying a Driver

To explicitly use a specific search driver, call the `driver()` method:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::driver('meilisearch')
    ->query('Hoodies')
    ->get();
```

### Filtering

Apply filters to narrow down search results. Filters are passed as key-value pairs where the key is the field name and the value is the filter value:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')
    ->filter([
        'status' => 'published',
        'brand' => 'Acme',
    ])
    ->get();
```

### Faceted Search

Facets allow users to refine search results by selecting values within categories (e.g., brand, color, size). Set active facet selections using `setFacets()`:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')
    ->setFacets([
        'brand' => ['Nike', 'Adidas'],
        'colour' => ['Red'],
    ])
    ->get();
```

The search response includes updated facet counts that reflect the current selections, so the storefront can show how many results match each facet value.

To remove a specific facet or value:

```php theme={null}
use Lunar\Search\Facades\Search;

$search = Search::query('Hoodies')
    ->setFacets(['brand' => ['Nike', 'Adidas']]);

// Remove a specific value from a facet
$search->removeFacet('brand', 'Nike');

// Remove an entire facet
$search->removeFacet('brand');

$results = $search->get();
```

### Sorting

Sort results by a specific field:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')
    ->sort('created_at:desc')
    ->get();
```

The sort format is `field:direction`, where direction is `asc` or `desc`. The field must be configured as sortable on the model's indexer (see the [Search reference](/2.x/reference/search)).

For Typesense, a raw sort expression can also be used:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')
    ->sortRaw('_text_match:desc,created_at:desc')
    ->get();
```

### Pagination

Control the number of results per page using the `perPage()` method. The default is 50:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')
    ->perPage(24)
    ->get();
```

### Extending Queries

For advanced use cases, extend the search query using `extendQuery()`:

```php theme={null}
use Lunar\Search\Facades\Search;

$results = Search::query('Hoodies')
    ->extendQuery(function ($engine, &$queries) {
        // Modify search queries before execution
    })
    ->get();
```

## Response Format

All search engines return a `Lunar\Search\Data\SearchResults` object with a consistent structure:

| Property        | Type            | Description                                 |
| --------------- | --------------- | ------------------------------------------- |
| `query`         | `?string`       | The search query that was executed          |
| `count`         | `int`           | Total number of matching results            |
| `page`          | `int`           | Current page number                         |
| `perPage`       | `int`           | Number of results per page                  |
| `totalPages`    | `int`           | Total number of pages                       |
| `hits`          | `SearchHit[]`   | Array of search result hits                 |
| `facets`        | `SearchFacet[]` | Array of available facets with counts       |
| `links`         | `View`          | Pagination links (Laravel paginator view)   |
| `sortField`     | `?string`       | The field the results are sorted by, if any |
| `sortDirection` | `?string`       | `asc`, `desc`, or `null`                    |

### SearchHit

Each hit contains the indexed document data and any highlights (Typesense only):

| Property     | Type                   | Description                   |
| ------------ | ---------------------- | ----------------------------- |
| `highlights` | `SearchHitHighlight[]` | Matched field highlights      |
| `document`   | `array`                | The raw indexed document data |

### SearchHitHighlight

| Property  | Type       | Description                        |
| --------- | ---------- | ---------------------------------- |
| `field`   | `string`   | The field that matched             |
| `matches` | `string[]` | The matched tokens                 |
| `snippet` | `?string`  | A highlighted snippet of the match |

### SearchFacet

| Property    | Type                 | Description                        |
| ----------- | -------------------- | ---------------------------------- |
| `label`     | `string`             | Display label for the facet        |
| `field`     | `string`             | The index field name               |
| `values`    | `SearchFacetValue[]` | Available values with counts       |
| `hierarchy` | `bool`               | Whether this facet is hierarchical |

### SearchFacetValue

| Property   | Type                 | Description                              |
| ---------- | -------------------- | ---------------------------------------- |
| `label`    | `string`             | Display label for the value              |
| `value`    | `string`             | The actual value for filtering           |
| `count`    | `int`                | Number of results matching this value    |
| `active`   | `bool`               | Whether this value is currently selected |
| `children` | `SearchFacetValue[]` | Child values for hierarchical facets     |

## Handling the Response

### Displaying Results

```blade theme={null}
@foreach($results->hits as $hit)
    <div>{{ $hit->document['name'] }}</div>
@endforeach
```

### Displaying Facets

```blade theme={null}
@foreach($results->facets as $facet)
    <div>
        <strong>{{ $facet->label }}</strong>
        @foreach($facet->values as $facetValue)
            <label>
                <input type="checkbox" value="{{ $facetValue->value }}" @checked($facetValue->active) />
                <span @class(['text-blue-500' => $facetValue->active])>
                    {{ $facetValue->label }}
                </span>
                ({{ $facetValue->count }})
            </label>
        @endforeach
    </div>
@endforeach
```

### Pagination

The `links` property contains a standard Laravel pagination view:

```blade theme={null}
{{ $results->links }}
```

### Accessing Pagination Metadata

```blade theme={null}
<p>Showing page {{ $results->page }} of {{ $results->totalPages }} ({{ $results->count }} results)</p>
```

## TypeScript Integration

Every response class in `Lunar\Search\Data` is annotated with Spatie's `#[TypeScript]` attribute. If [Spatie TypeScript Transformer](https://spatie.be/docs/typescript-transformer) is being used, add the add-on's data path to the `typescript-transformer.php` config to generate TypeScript types for the search response classes:

```php theme={null}
return [
    // ...
    'auto_discover_types' => [
        // ...
        \Lunar\Search\data_path(),
    ],
];
```

The generated types are available under the `Lunar.Search` namespace:

```ts theme={null}
defineProps<{
    results: Lunar.Search.SearchResults
}>()
```

## Meilisearch

Meilisearch needs to know which indexed fields are filterable and sortable before it can serve faceted queries. This is a one-time (or per-schema-change) setup step, separate from running searches:

```sh theme={null}
composer require lunarphp/meilisearch
```

This add-on has no configuration of its own and provides a single Artisan command:

```sh theme={null}
php artisan lunar:meilisearch:setup
```

It reads `config('lunar.search.models')`, creates any missing Meilisearch indexes, and applies each model's filterable and sortable attributes (as defined by its indexer — see the [Search reference](/2.x/reference/search)) to the corresponding index.

<Info>
  The Meilisearch and Typesense engines used to query results live in the `lunarphp/search` package installed above. The `lunarphp/meilisearch` add-on only configures Meilisearch's index settings; it is not required to run Meilisearch queries through the `Search` facade.
</Info>

## Typesense

Typesense is available as a driver (`Lunar\Search\Engines\TypesenseEngine`) once `lunarphp/search` is installed, with no separate add-on package required. Configure Typesense's own collection schema and search parameters through Scout's `config/scout.php` (the `typesense.model-settings` key), which controls field types for filtering, highlight and hybrid search settings, and query defaults.
