spatie/elasticsearch-search-string-parser
Convert custom search strings like "foo bar status:active @john.doe" into ElasticSearch requests. Add directives via regex and the spatie/elasticsearch-query-builder, with grouping and autocomplete suggestions.
Architecture fit. This package is a thin parsing layer, not a search platform. Its responsibility is narrow and well-bounded: take a user-supplied string, dispatch regex-matched fragments to developer-defined PatternDirective classes, strip those fragments, and hand the remainder to a single BaseDirective; then execute the assembled query through spatie/elasticsearch-query-builder and return a SearchResults object with hits plus directive suggestions. It fits cleanly where an application already has (or is committing to) Elasticsearch 8 as its query backend and wants a key:value search grammar without building a tokenizer/parser. It does not fit if the team expects faceted-search infrastructure, relevance tuning, indexing strategy, or pagination semantics to come out of the box — none of that is in scope.
Integration feasibility. Low-to-moderate effort, high clarity. Installation is one Composer require; there is no service provider, no published config, no Artisan commands, no migrations. Laravel "support" in this package is strictly Composer version constraints (Laravel 9 through 13 tracked in the changelog), not framework integration — so all wiring is explicit application code: construct the ES client, register directives, call ->search(), hydrate the returned _id list against Eloquent or your read model. That explicitness is a feature for a TPM: there is no hidden framework magic to reason about during upgrades.
Technical risk.
## Usage block is an unedited Spatie skeleton stub (echoPhrase('Hello, Spatie!')) and has nothing to do with the package. The only load-bearing documentation is the "How it works: directives" section. Abstract directive method signatures (getPattern, the apply hook, the suggestion method) are undocumented outside src/. Budget engineering time for reading source, not docs.spatie/elasticsearch-query-builder call. The parser only routes strings; clause shapes, boosting, filters, and aggregations come from the builder. Adopting this package means adopting and understanding both. Treat that as a scope increase in the design review.^2.0 requires ES8 and the elasticsearch/elasticsearch 8.x client; ES7 clusters require the 1.x line. A mismatch surfaces as a client-layer error that reads nothing like a parser bug — confirm cluster version before pinning.Key questions for the team.
spatie/elasticsearch-query-builder semantics, or do we need a wrapper to insulate our directives from it?_id → application model, preserving relevance order) our responsibility? It is; decide the pattern before the first PR.Stack fit. Strong for a Laravel application on PHP 8.x with Elasticsearch 8 already in the stack. The package is framework-agnostic, so it composes without fighting Laravel: directives are plain classes (autowirable, testable), and the surrounding endpoint is a normal controller with a Form Request validating q length and charset. There is no opinion about tenancy, authorization, or index naming — those are ours to enforce, and they should be enforced before the string reaches the parser, not inside directives.
Migration path. Greenfield adoption is trivial. For teams currently doing naive multi_match on a request parameter, the migration is additive and reversible: introduce the parser behind a feature flag on one internal screen, keep the old query path live, and compare result sets on real queries before cutting over. Because directives are our own classes, rollback means disabling a config flag and falling back to the previous query construction — no data migration, no schema change, no reindex. That reversibility is the main argument for trying it.
Compatibility. Pin the major to the cluster: ^2.0 for ES8, 1.x for ES7. Verify the Laravel constraint against the app's current version — the changelog shows sequential Laravel 9→13 support, so an app on an older LTS may be stuck on an older parser tag with correspondingly older tooling. Watch the spatie/elasticsearch-query-builder constraint as a transitive pin; a lock-file bump there can change generated clause shapes without any parser release. Also confirm PHP version alignment: the 2.0 line modernized tooling to PHP 8.5/Pest 4/PHPStan level 5, which implies a fairly current runtime baseline.
Sequencing.
status:, sort:, @user, group_by:); each keeps its regex, apply logic, and suggestion copy in a single file. Register most-specific patterns first — the parser iterates directives in registration order and strips matches as it goes, so a greedy early pattern will eat input intended for later directives.group_by aggregations and suggestion metadata until after the plain filter/search path is stable in production — they are the parts most likely to need bespoke work.Maintenance. The steady-state maintenance burden is low but non-zero, and it is mostly ours, not upstream's. Upstream moves slowly and in large steps (the 2.0 line was a major-version jump with a tooling modernization); the practical cadence is "review once or twice a year, expect a breaking bump when the ES client majors." Our recurring cost is the directive registry: every product request for a new filter becomes a class, a regex, an ordering decision, and a test. Plan for that as a small standing line item rather than a one-off build. The documentation gap does not heal itself over time — write an internal ADR capturing directive conventions, because the README will not.
Support. Support surface is the search grammar, not the package. The expensive support tickets are "why did status:active return nothing" — which are index/analyzer/data problems masquerading as parser bugs. Mitigations: expose returned suggestions to the UI (the package supplies them), log the parsed query string alongside the ES request, and keep an internal directive reference doc. Escalation to upstream is effectively not a plan: 51 stars and zero dependents means no community backstop, so assume we debug our own issues from source. The upside is that the source is small enough to read fully.
Scaling. The parser is stateless and CPU-cheap per request under sane input; the bottleneck is Elasticsearch itself, not this package. Two caveats. First, cost scales with directive count × input length because the parser loops over directives repeatedly stripping matches — with a dozen directives and unfiltered long input, this becomes measurable. Bound it with a max-length Form Request rule and a directive count that stays in the single digits per endpoint. Second, the package executes the query directly, so there is no natural seam for caching or for async/deferred execution — if you need result caching or a query gateway, wrap the call site, don't try to extend the parser.
Failure modes.
_ids in relevance order; a naive whereIn re-sorts and silently degrades relevance. This is the most common production bug in this integration pattern and is entirely on us.composer update moves elasticsearch-query-builder and changes clause shapes; mitigated by lock-file discipline and a result-set regression test.Ramp-up. Fast for anyone comfortable with Elasticsearch query DSL — roughly an afternoon to a working spike, one to two weeks to a hardened internal screen with tests. The learning curve is almost entirely in two places: the query builder's clause semantics (read that README concurrently) and the undocumented directive abstract-class signatures (read src/, trust it over any blog post). Engineers without prior ES experience will need more time, because debugging parser output requires being able to read the generated query. Do not treat this as a shortcut for teams that have not yet internalized Elasticsearch relevance and analysis behavior — the package routes strings, it does not make search quality decisions for you.
How can I help you explore Laravel packages today?