Query String Queries
Query String Queries leverage Elasticsearch’s powerful
query_string syntax to perform advanced full-text and structured searches directly on your Laravel modelsSearch Query String
Section titled “Search Query String”searchQueryString($query, $fields = null, $options = [])orSearchQueryString($query, $fields = null, $options = [])searchNotQueryString($query, $fields = null, $options = [])orSearchNotQueryString($query, $fields = null, $options = [])
Book::searchQueryString('Eric OR (Lean AND Startup)')->get();{ "index": "books", "body": { "query": { "query_string": { "query": "Eric OR (Lean AND Startup)" } }, "size": 1000 }}You can combine searchQueryString with other eloquent methods, for example:
Book::searchQueryString('Eric OR (Lean AND Startup)') ->orderByDesc('sales')->get();{ "index": "books", "body": { "query": { "query_string": { "query": "Eric OR (Lean AND Startup)" } }, "sort": [ { "sales": { "order": "desc" } } ], "size": 1000 }}Book::searchQueryString('Eric OR (Lean AND Startup)') ->where('sales', '>', 100)->get();{ "index": "books", "body": { "query": { "bool": { "must": [ { "query_string": { "query": "Eric OR (Lean AND Startup)" } }, { "range": { "sales": { "gt": 100 } } } ] } }, "size": 1000 }}Book::searchQueryString('Eric OR (Lean AND Startup)') ->where('sales', '>=', 100)->limit(10)->skip(3)->get();{ "index": "books", "body": { "query": { "bool": { "must": [ { "query_string": { "query": "Eric OR (Lean AND Startup)" } }, { "range": { "sales": { "gte": 100 } } } ] } }, "from": 3, "size": 10 }}Book::searchQueryString('Eric OR (Lean AND Startup)') ->where('published', true)->paginate(10);Book::searchQueryString('Eric OR (Lean AND Startup)') ->where('published', true)->count()Parameter: $fields
Section titled “Parameter: $fields”By default, all fields will be searched through; you can specify which to search through as well as optionally boost certain fields using a caret ^:
People::searchQueryString('John',['name^3','description^2','friends.name'])->get();Search through people for ‘John’ in the name field, description field, and friends.name field. The name field is boosted by 3, and the description field is boosted by 2, which will affect the relevance score used for sorting.
{ "index": "people", "body": { "query": { "query_string": { "query": "John", "fields": [ "name^3", "description^2", "friends.name" ] } }, "size": 1000 }}Parameter: $options
Section titled “Parameter: $options”Allows you to set any options for the query_string clause to use, ex:
- type
-
best_fields (Default), most_fields, cross_fields, phrase, phrase_prefix, bool_prefix - allow_leading_wildcard
- analyze_wildcard
- analyzer
- auto_generate_synonyms_phrase_query
- boost
- default_operator
- fuzziness
- fuzzy_max_expansions
- fuzzy_prefix_length
- fuzzy_transpositions
- fuzzy_rewrite
- lenient
- max_determinized_states
- minimum_should_match
- quote_analyzer
- phrase_slop
- quote_field_suffix
- rewrite
- time_zone
use PDPhilip\Elasticsearch\Query\Options\QueryStringOptions;
Product::searchQueryString('espreso tme', function (QueryStringOptions $options) { // Match behavior and field resolution $options->type('most_fields'); // Default: best_fields $options->analyzer('my_custom_analyzer');
// Fuzziness and tolerance $options->fuzziness(2); // Allow up to 2 edit distances $options->fuzzyPrefixLength(1); $options->fuzzyTranspositions(true); $options->fuzzyMaxExpansions(50); $options->rewrite('constant_score');
// Scoring and matching $options->boost(2.0); $options->defaultOperator('OR'); // Default: OR $options->minimumShouldMatch('2'); $options->autoGenerateSynonymsPhraseQuery(true);
// Wildcards and parsing $options->allowLeadingWildcard(false); $options->analyzeWildcard(true); $options->lenient(true);
// Phrase matching $options->phraseSlop(2); $options->quoteFieldSuffix('.exact');
// Misc $options->timeZone('UTC');})->get();{ "index": "products", "body": { "query": { "query_string": { "query": "espreso tme", "type": "most_fields", "analyzer": "my_custom_analyzer", "fuzziness": 2, "fuzzy_prefix_length": 1, "fuzzy_transpositions": true, "fuzzy_max_expansions": 50, "rewrite": "constant_score", "boost": 2, "default_operator": "OR", "minimum_should_match": "2", "auto_generate_synonyms_phrase_query": true, "allow_leading_wildcard": false, "analyze_wildcard": true, "lenient": true, "phrase_slop": 2, "quote_field_suffix": ".exact", "time_zone": "UTC" } }, "size": 1000 }}Query String: What’s Possible
Section titled “Query String: What’s Possible”1. Basics & Ranges
Section titled “1. Basics & Ranges”User::searchQueryString('age:35')->get();User::searchQueryString('age:>=35')->get();User::searchQueryString('age:<=18')->get();User::searchQueryString('age:(NOT 35)')->get();2. Boolean Logic (AND / OR / NOT)
Section titled “2. Boolean Logic (AND / OR / NOT)”// ANDUser::searchQueryString('(age:35) AND (title:admin)')->get();
// ORUser::searchQueryString('name:(doe OR toe)')->get();
// NOTUser::searchQueryString('name:(NOT doe)')->get();
// AND NOT & OR NOT (composed with helpers)User::searchNotQueryString('name:doe')->searchNotQueryString('name:toe')->get();User::searchNotQueryString('name:doe')->orSearchNotQueryString('age:35')->get();3. Search Scope (All fields vs Specific fields)
Section titled “3. Search Scope (All fields vs Specific fields)”// All fieldsProduct::searchQueryString('sweet')->get();Product::searchQueryString('NOT sweet')->get();
// Specific fieldProduct::searchQueryString('sweet', 'details.type')->get();Product::searchQueryString('false', 'details.gluten_free')->get();
// Fielded in queryProduct::searchQueryString('details.gluten_free:false')->get();4. Fuzziness (typo-tolerant)
Section titled “4. Fuzziness (typo-tolerant)”use PDPhilip\Elasticsearch\Query\Options\QueryStringOptions;
// Default fuzziness (~2 edits)Product::searchQueryString('esprso~')->get();
// Custom fuzziness settingsProduct::searchQueryString('espro~', function (QueryStringOptions $options) { $options->fuzziness(3); // more tolerant})->get();
Product::searchQueryString('espreso~', function (QueryStringOptions $options) { $options->fuzziness(1); // tighter match, fewer results})->get();5. Default Operator & Minimum Should Match
Section titled “5. Default Operator & Minimum Should Match”use PDPhilip\Elasticsearch\Query\Options\QueryStringOptions;
// Default operator (space = OR)Product::searchQueryString('sweet soda')->get();
Product::searchQueryString('sweet soda', function (QueryStringOptions $options) { $options->defaultOperator('AND'); // require both terms})->get();
// Minimum should match on OR groupProduct::searchQueryString('drink OR water OR soda')->get();
Product::searchQueryString('drink OR water OR soda', function (QueryStringOptions $options) { $options->minimumShouldMatch(2); // must match at least two terms})->get();6. Phrases & Slop (ordered proximity)
Section titled “6. Phrases & Slop (ordered proximity)”// Allow gaps between termsProduct::searchQueryString('"fresh juice"', fn($o) => $o->phraseSlop(1))->get();// Exact adjacency onlyProduct::searchQueryString('"fresh juice"', fn($o) => $o->phraseSlop(0))->get();7. Wildcards & Safety
Section titled “7. Wildcards & Safety”use PDPhilip\Elasticsearch\Query\Options\QueryStringOptions;
// Leading wildcard disabled → throws (not allowed by default)Product::searchQueryString('*pro', function (QueryStringOptions $options) { $options->allowLeadingWildcard(false);})->get();
// Leading wildcard enabled → allowedProduct::searchQueryString('*pro', function (QueryStringOptions $options) { $options->allowLeadingWildcard(true);})->get();8. Leniency (parse forgiving on types)
Section titled “8. Leniency (parse forgiving on types)”// Strict → throws when querying numeric field with textProduct::searchQueryString('ABC', 'price')->get();
// Lenient → ignores type errors, returns 0 matchesProduct::searchQueryString('ABC', 'price', fn($o) => $o->lenient(true))->get();9. Query Types (best_fields, cross_fields, phrase, etc.)
Section titled “9. Query Types (best_fields, cross_fields, phrase, etc.)”use PDPhilip\Elasticsearch\Query\Options\QueryStringOptions;
// phrase vs phrase_prefix (single field)Product::searchQueryString('club sand', 'name', function (QueryStringOptions $options) { $options->type('phrase'); // requires exact phrase match})->get();
Product::searchQueryString('club sand', 'name', function (QueryStringOptions $options) { $options->type('phrase_prefix'); // allows partial phrase match ("Club sandwich deluxe")})->get();
// best_fields vs cross_fields (multi-field resolution)Product::searchQueryString('vanilla pizza', ['name', 'details.product'], function (QueryStringOptions $options) { $options->defaultOperator('AND')->type('best_fields'); // both terms must appear in the same field})->get();
Product::searchQueryString('vanilla pizza', ['name', 'details.product'], function (QueryStringOptions $options) { $options->defaultOperator('AND')->type('cross_fields'); // terms can appear across multiple fields})->get();
// bool_prefix (autocomplete-style)Product::searchQueryString('cheese piz', 'name', function (QueryStringOptions $options) { $options->type('bool_prefix'); // prefix matching for partial terms})->get();10. Field Boosting (ranking control)
Section titled “10. Field Boosting (ranking control)”use PDPhilip\Elasticsearch\Query\Options\QueryStringOptions;
// Boost name vs product and compare first() resultProduct::searchQueryString('coffee', ['name^3', 'details.product'], function (QueryStringOptions $options) { $options->type('cross_fields'); // boosts name field more heavily})->first();
Product::searchQueryString('coffee', ['name', 'details.product^3'], function (QueryStringOptions $options) { $options->type('cross_fields'); // boosts product field more heavily})->first();11. Regex (advanced matching)
Section titled “11. Regex (advanced matching)”User::searchQueryString('name:/joh?n(ath[oa]n)/')->get();12. Numeric Ranges (inclusive vs exclusive)
Section titled “12. Numeric Ranges (inclusive vs exclusive)”// Inclusive upper boundProduct::searchQueryString('price:[5 TO 19]')->get();
// Exclusive upper bound (right brace)Product::searchQueryString('price:[5 TO 19}')->get();13. Mixed Boolean Operators (must/forbid/optional)
Section titled “13. Mixed Boolean Operators (must/forbid/optional)”// vanilla optional, +pizza required, -ice forbiddenProduct::searchQueryString('vanilla +pizza -ice', fn($o) => $o->type('cross_fields'))->get();