PHPackages                             illuma-law/laravel-hybrid-search - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. illuma-law/laravel-hybrid-search

ActiveLibrary[Search &amp; Filtering](/categories/search)

illuma-law/laravel-hybrid-search
================================

Portable Full-Text Search (FTS) schema macros and the Reciprocal Rank Fusion (RRF) algorithm.

v1.0.7(1mo ago)060[1 PRs](https://github.com/illuma-law/laravel-hybrid-search/pulls)MITPHPPHP ^8.3CI passing

Since Apr 20Pushed 1mo agoCompare

[ Source](https://github.com/illuma-law/laravel-hybrid-search)[ Packagist](https://packagist.org/packages/illuma-law/laravel-hybrid-search)[ Docs](https://github.com/illuma-law/laravel-hybrid-search)[ GitHub Sponsors](https://github.com/illuma-law)[ RSS](/packages/illuma-law-laravel-hybrid-search/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (16)Versions (10)Used By (0)

Laravel Hybrid Search
=====================

[](#laravel-hybrid-search)

[![Tests](https://github.com/illuma-law/laravel-hybrid-search/actions/workflows/run-tests.yml/badge.svg)](https://github.com/illuma-law/laravel-hybrid-search/actions)[![Packagist License](https://camo.githubusercontent.com/e60623f508586f049d48cfb8396ee411b0c9bc3be174381a1893c37462a3c1e5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e63652d4d49542d626c7565)](http://choosealicense.com/licenses/mit/)[![Latest Stable Version](https://camo.githubusercontent.com/4308a001c4aa5671668302b04b7b387f5a0b574328955607c9687812e16de41e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696c6c756d612d6c61772f6c61726176656c2d6879627269642d7365617263683f6c6162656c3d56657273696f6e)](https://packagist.org/packages/illuma-law/laravel-hybrid-search)

Portable Full-Text Search macros and Reciprocal Rank Fusion for Laravel.

This package provides portable Full-Text Search (FTS) schema macros and the **Reciprocal Rank Fusion (RRF)** algorithm for Laravel applications. It enables seamless text searching across PostgreSQL, MySQL, SQL Server, and SQLite, abstracting away the database-specific syntax. It also provides an elegant way to merge and re-rank traditional keyword search results with AI vector search results.

Features
--------

[](#features)

- **Database Portability:** Write one migration and one query that works across all supported databases.
- **SQLite FTS5 Support:** Automatically creates Virtual Tables and database triggers to keep SQLite full-text indexes synchronized.
- **Reciprocal Rank Fusion:** Mathematically combine multiple ranked lists (e.g., BM25 + Vector Similarity) into a single optimized result set.
- **Scout Key Trait:** Ensure Scout-indexed models always return a string primary key — required for Typesense and other engines that reject integer keys.
- **Scout Health Check:** Optional `spatie/laravel-health` check that probes Meilisearch, Typesense, and Algolia endpoints.

Database Support Matrix
-----------------------

[](#database-support-matrix)

DatabaseSchema MacroQuery Builder MacroUnderlying Syntax**PostgreSQL**NativeNative`whereFullText`**MySQL**NativeNative`whereFullText`**SQL Server**Manual Instructions\*Native`CONTAINS`**SQLite**Virtual Table + TriggersNative`MATCH`*\*SQL Server requires manual creation of the Full-Text Catalog.*

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require illuma-law/laravel-hybrid-search
```

The service provider will automatically register the `Blueprint` and `Builder` macros.

Usage &amp; Integration
-----------------------

[](#usage--integration)

### Schema Migrations

[](#schema-migrations)

Use the `hybridFullText` macro in your migrations.

On PostgreSQL and MySQL, this directly uses Laravel's native full-text index generation. On SQLite, it creates an `FTS5` virtual table (e.g., `articles_fts`) and sets up `INSERT`, `UPDATE`, and `DELETE` database triggers. This ensures your SQLite virtual table automatically stays synchronized with your main table without requiring any PHP-side application logic.

```
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });

        // Creates native FTS index on pg/mysql, or FTS5 virtual table + triggers on sqlite
        Schema::table('articles', function (Blueprint $table) {
            $table->hybridFullText(['title', 'body'], 'articles_search_index');
        });
    }

    public function down(): void
    {
        Schema::table('articles', function (Blueprint $table) {
            // Safely drops native indexes or SQLite virtual tables/triggers
            $table->dropHybridFullText('articles_search_index');
        });

        Schema::dropIfExists('articles');
    }
};
```

### Full-Text Searching

[](#full-text-searching)

Use the `whereHybridFullText` macro on any Query Builder or Eloquent Builder. It automatically handles the complex `MATCH` syntax for SQLite FTS5 and the `CONTAINS` syntax for SQL Server, while using native `whereFullText` on PostgreSQL and MySQL.

```
use App\Models\Article;

// Search for articles containing "laravel macros"
$results = Article::query()
    ->whereHybridFullText(['title', 'body'], 'laravel macros')
    ->get();
```

You can also invert the search to exclude matches:

```
// Find articles that DO NOT contain the word "outdated"
$results = Article::query()
    ->whereHybridFullText(['title', 'body'], 'outdated', not: true)
    ->get();
```

### Reciprocal Rank Fusion (RRF)

[](#reciprocal-rank-fusion-rrf)

When building advanced search systems, you often want to retrieve the top results using traditional keyword search (BM25) and semantic vector search (Cosine Similarity), then combine them.

The `ReciprocalRankFusion` class merges these disparate result sets by assigning an RRF score to each item based on its position in the original ranked lists.

```
use IllumaLaw\HybridSearch\ReciprocalRankFusion;
use App\Models\Article;

// 1. Get the top 50 IDs from Keyword Search
$keywordIds = Article::query()
    ->whereHybridFullText(['title', 'body'], 'authentication')
    ->limit(50)
    ->pluck('id');

// 2. Get the top 50 IDs from Vector Search
$vectorIds = Article::query()
    ->orderByVectorSimilarity('embedding', $queryVector) // Example syntax
    ->limit(50)
    ->pluck('id');

// 3. Combine and re-rank the IDs using RRF
$rankedScores = ReciprocalRankFusion::combine(
    [
        'keyword' => $keywordIds,
        'vector' => $vectorIds
    ],
    k: 60 // The RRF constant (default is 60)
);

// $rankedScores is a Collection of [id => score], sorted descending by score.
$topIds = $rankedScores->keys();

// 4. Fetch the final ordered models
$finalResults = Article::whereIn('id', $topIds)
    ->orderByRaw('FIELD(id, ' . $topIds->implode(',') . ')')
    ->get();
```

Scout Key Trait
---------------

[](#scout-key-trait)

Some search engines (e.g. Typesense) require Scout keys to be strings. Add the `EnsuresScoutKeyIsString` trait to any model that uses integer or UUID primary keys alongside the Scout `Searchable` trait.

```
use IllumaLaw\HybridSearch\Concerns\EnsuresScoutKeyIsString;
use Laravel\Scout\Searchable;

class Article extends Model
{
    use EnsuresScoutKeyIsString, Searchable {
        EnsuresScoutKeyIsString::getScoutKey insteadof Searchable;
        EnsuresScoutKeyIsString::getScoutKeyName insteadof Searchable;
    }
}
```

Scout Health Check
------------------

[](#scout-health-check)

An optional `spatie/laravel-health` check that pings the configured Scout engine's health endpoint. Supports Meilisearch, Typesense, and Algolia. Non-remote drivers (`database`, `collection`, `null`) are automatically skipped.

Install the optional dependency first:

```
composer require spatie/laravel-health
```

Then register the check in your application:

```
use IllumaLaw\HybridSearch\HealthChecks\ScoutEngineCheck;
use Spatie\Health\Facades\Health;

Health::checks([
    ScoutEngineCheck::new(),
]);
```

The check reads standard Scout configuration keys (`scout.driver`, `scout.meilisearch.*`, `scout.typesense.*`, `scout.algolia.*`). You can adjust the request timeout via `config('health.scout.timeout_seconds')` (default: 5).

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance91

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 85.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

8

Last Release

44d ago

Major Versions

v0.1.0 → v1.0.52026-04-20

### Community

Maintainers

![](https://www.gravatar.com/avatar/2affac64f2726a640084b203503518ca01f582536d60a0a299b614486ed95aaa?d=identicon)[miguelenes](/maintainers/miguelenes)

---

Top Contributors

[![miguelenes](https://avatars.githubusercontent.com/u/1568086?v=4)](https://github.com/miguelenes "miguelenes (18 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

laravelilluma-lawlaravel-hybrid-search

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/illuma-law-laravel-hybrid-search/health.svg)

```
[![Health](https://phpackages.com/badges/illuma-law-laravel-hybrid-search/health.svg)](https://phpackages.com/packages/illuma-law-laravel-hybrid-search)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

436834.4k1](/packages/clickbar-laravel-magellan)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)[harris21/laravel-fuse

Circuit breaker for Laravel queue jobs. Protect your workers from cascading failures.

24740.3k](/packages/harris21-laravel-fuse)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
