Open source August 2026
Larameili

Larameili

Larameili

Larameili gives a Meilisearch index an Eloquent-style model, for documents that live in the search engine as their primary store: find, search, save and delete through a model class, with the index settings declared in code.

Installation

One Composer package, and the service provider auto-registers:

composer require edulazaro/larameili

It reads the same MEILISEARCH_HOST and MEILISEARCH_KEY your app already uses, so an app that already talks to Meilisearch needs nothing else.

The model is the index

A model maps to one index. Declare the index name, the fields to filter and sort on, and the settings the index should have.

use EduLazaro\Larameili\Meili;

class Article extends Meili
{
    protected static string $index = 'articles';

    protected static array $searchable = ['title', 'body'];
    protected static array $filterable = ['status', 'author_id'];
    protected static array $sortable   = ['published_at'];
}

find(), save(), delete() and search() all talk to the index. There is no table behind it and no second store to keep in step.

Pushing the settings

The $searchable, $filterable, $sortable, $ranking, $synonyms and $embedders you declare live in code until you sync them. List your models in config/larameili.php and run the command, which creates each index and applies its settings:

php artisan meili:sync

meili:sync creates the index if it does not exist and is safe to run repeatedly.

Reading and writing

It reads like Eloquent.

$article = Article::find('abc');   // by primary key, or null
$article->title = 'New title';
$article->save();                  // insert or update
$article->delete();

Article::create(['id' => 'xyz', 'title' => 'Hello']);

find() returns null only when the document genuinely does not exist. A missing index, a bad key or an unreachable host raise the Meilisearch client's own exception instead of being hidden as a null.

Querying

query() returns a fluent builder that compiles into Meilisearch parameters and hydrates the hits back into models.

$hits = Article::query()
    ->where('status', 'published')
    ->whereIn('author_id', [1, 2, 3])
    ->orderBy('published_at', 'desc')
    ->limit(20)
    ->search('meilisearch');

The builder covers the rest of the search surface too:

Method What it does
where / whereIn / whereNot / whereRaw Filters, compiled to Meilisearch syntax
semantic($ratio) Hybrid keyword + vector search when the index has an embedder
paginate($perPage) A Laravel LengthAwarePaginator with an exact total
near / withinBox / orderByDistance Geo search on a _geo field
count / exists Aggregate helpers

Hybrid search

If the index declares an embedder, semantic() turns the query into a hybrid one: Meilisearch runs the keyword and the vector search together and fuses the rankings. The ratio is 0 for keyword only, 1 for vector only, and 0.5 for an even blend.

Article::query()
    ->where('status', 'published')
    ->semantic(0.5)
    ->search('how do I cancel a subscription');

Relations to Eloquent models

Meilisearch has no joins, so a relation is a resolver: a document holds a foreign key, and the package looks the Eloquent model up by it. Declare one with belongsToEloquent.

use EduLazaro\Larameili\Relations\BelongsToEloquent;

class LawChunk extends Meili
{
    public function law(): BelongsToEloquent
    {
        return $this->belongsToEloquent(Law::class, foreignKey: 'law_id', ownerKey: 'external_id');
    }
}

Read $chunk->law and it resolves lazily; eager-load it on a search with with('law') to batch every hit's lookup into a single Eloquent query instead of one per hit.

Casts

Meilisearch returns raw JSON, so a $casts map turns attributes back into the types you want as you read them: dates into Carbon, JSON into arrays, strings into backed enums.

protected static array $casts = [
    'published_at' => 'datetime',
    'tags'         => 'array',
    'status'       => Status::class,
];

Supported casts: int, float, bool, string, array, collection, date, datetime, and any backed enum class. Casting is applied on read.

built and maintained by Edu Lazaro · MIT license