Weave 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

Scout Lunr Laravel Package

illuma-law/scout-lunr

Laravel Scout driver that brings Lunr-powered full-text search to Eloquent models, letting you index and query records locally without external search services while using Scout’s familiar model APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a pre-1.0 package (v0.1.x), so treat the API as unstable and pin the version in composer.json.

  1. Install and publish config:
composer require illuma-law/scout-lunr
php artisan vendor:publish --tag=scout-lunr-config
  1. Deploy the Cloudflare Worker separately (per the README). The PHP package is only the client — nothing works until the Worker is reachable and the index exists.

  2. Wire up configuration. Point Scout at the Lunr driver and give it the Worker endpoint/credentials:

// config/scout.php
'driver' => env('SCOUT_DRIVER', 'lunr'),

'lunr' => [
    'endpoint' => env('SCOUT_LUNR_ENDPOINT'),
    // auth/worker options per config/scout-lunr.php
],

Then php artisan config:clear (or config:cache in deploy) and confirm the driver resolves.

  1. Make a model searchable and run your first query:
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    public function toSearchableArray(): array
    {
        return ['id' => $this->id, 'title' => $this->title, 'body' => $this->body];
    }
}
php artisan scout:import "App\Models\Post"
Post::search('laravel')->get();

First use case to validate end-to-end: index a small table, run a keyword search, and confirm results hydrate back into Eloquent models with correct ordering. If that round-trip works, the Worker wiring is correct.

Where to look first: the published config/scout-lunr.php (worker/query defaults), then the README's Worker deployment section, then the query builder source under src/ — the builder is where Lunr's semantics leak through and where you'll spend most of your debugging time.

Implementation Patterns

Keep the Scout surface, drop to the builder when needed. The engine is meant to be used through standard Scout calls (search(), searchable(), where(), paginate()), so existing Scout code should mostly port over. Use the bundled query builder when you need Lunr-specific behavior:

Post::search('release notes')
    ->where('status', 'published')
    ->query(fn ($q) => $q->where('tags', 'php'))
    ->paginate(20);

Prefer this over raw Worker calls — you get Scout's model hydration, pagination, and (if enabled) soft-delete/shouldBeSearchable() handling for free.

Treat indexing as an explicit pipeline step, not a side effect. Lunr indexes are typically rebuilt rather than mutated in place, so the practical workflow looks like:

  • scout:import on deploy/first setup
  • queued indexing (SCOUT_QUEUE=true) for writes, with a worker running
  • a scheduled scout:flush + scout:import for full rebuilds after schema or toSearchableArray() changes

If your data changes frequently, batch your rebuilds off-peak — a full reindex is a Worker-side CPU/memory cost, not a cheap incremental update.

Keep toSearchableArray() lean and flat. Lunr is a JavaScript index; dumping huge HTML bodies or deeply nested relations bloats the index and hurts query latency. Curate fields: short text fields for filtering, one or two for full-text, IDs for hydration.

Isolate the engine behind your own service. Because v0.1.x is pre-stable, wrap search in a small application-level class (App\Search\PostSearch) rather than scattering Post::search(...) across controllers. When the package changes shape or you migrate to Meilisearch/Typesense, you change one file.

Integration tips:

  • Configure the Worker client once via config/env and inject it — don't construct clients ad hoc in jobs.
  • Use a dedicated queue for indexing so a slow Worker doesn't back up user-facing jobs.
  • In tests, avoid hitting a real Worker: either fake Scout (Scout::fake()) for behavior, or point the endpoint at a stub. Assert on your query building, not on Lunr's ranking.
  • For multi-tenant apps, verify whether the driver supports per-index separation; if not, namespace fields/indexes yourself.

Gotchas and Tips

The Worker is the single point of failure. Every search is a network hop to Cloudflare. Add timeouts, retries with backoff, and a fallback path (e.g., a LIKE-based database search or a cached result set) for when the Worker is down. Never let a search failure take down a page render — catch and degrade.

Lunr is not Meilisearch or Algolia. Expect different relevance behavior: no fuzzy typo tolerance by default, different tokenization/stemming, and no faceting/geo/similarity features unless the query builder exposes them. Do not assume Scout features that work on the official engines work here — verify whereIn, orderBy, whereNot, range filters, and take/paginate individually against this driver.

"Laravel 12 and 13 on PHP 8.2–8.5 where supported by the framework" — read that qualifier literally. The matrix was expanded in v0.1.1, but combinations are only supported insofar as Laravel supports them. Check your exact PHP/Laravel pair against the CI matrix before upgrading a production app, and run your suite on the target PHP version, not just locally.

Version pinning is not optional. 0.1.x means minor bumps can break. Pin to ^0.1.1 at minimum, and read the diff before any bump. The v0.1.1 changes are dependency/test-matrix work plus the Worker client — no migration steps documented, so upgrading from 0.1.0 should be safe, but re-run vendor:publish into a temp path and diff the config rather than overwriting a customized config/scout-lunr.php (the publish tag overwrites silently).

Config caching trips people up. Worker credentials read via env() outside a config file return null once config:cache runs. Ensure all endpoints/keys are read inside config/scout-lunr.php and referenced via config(), then rebuild the cache in deploy.

Index staleness is silent. If the queue worker isn't running, writes are accepted but never indexed — searches just quietly miss new records. Add a health check or a scheduled reconciliation count (DB count vs. index count) so you notice drift before users do.

Debugging order of operations: (1) SCOUT_DRIVER actually resolving to lunr, (2) toSearchableArray() output shape, (3) raw Worker response before Scout hydration, (4) hydration/ordering. Most "no results" reports are the Worker returning hits for IDs that don't exist in the current DB (stale index), not a query bug.

Extension points to lean on: the configurable Worker client (swap in a wrapped client with logging/telemetry), the query builder (subclass if you need custom filter translation), and shouldBeSearchable() to keep unpublished/deleted records out of the index entirely rather than filtering them post-query.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager