ashiqfardus/laravel-fuzzy-search
illuminate/database and illuminate/support constraints.A powerful, zero-config fuzzy search package for Laravel with fluent API. Works with all major databases without external services. Scales to 10 million records with proper optimization.
$fillable or $searchable->search()->using()->typoTolerance()->get()fuzzy - General purpose with typo tolerance (recommended)levenshtein - Edit distance based, configurable tolerancesoundex - Phonetic matching for similar sounding wordsmetaphone - More accurate phonetic matchingtrigram - N-gram similarity (best with PostgreSQL pg_trgm)similar_text - Percentage-based similaritysimple / like - Basic LIKE matching (fastest, no typo tolerance)['title' => 10, 'body' => 5]_score attributeboostRecent()similar_text() for percentage-based similarity scoringtokenize() with matchAll() or matchAny()café matches cafesuggest() method for search suggestionsdidYouMean() for typo suggestionsFederatedSearch to search across multiple modelsgetAnalytics() for search insightscache(60)take() and skip() for custom paginationhighlight('mark') wraps matches in tagsdebugScore() explains scoring breakdownblog, ecommerce, users, phonetic, exactconfig/fuzzy-search.php$searchable propertyLaravelFuzzySearchException - Base exception with context supportEmptySearchTermException - When search term is emptyInvalidAlgorithmException - When invalid algorithm specifiedInvalidConfigException - When configuration is invalidSearchableColumnsNotFoundException - When no searchable columns foundfuzzy-search:index - Build search indexfuzzy-search:clear - Clear search indexfuzzy-search:benchmark - Performance benchmarkingfuzzy-search:explain - Explain search scoringdocs/GETTING_STARTED.md - Quick start guide with examplesdocs/PERFORMANCE.md - Optimization guide for scaling to millions of recordsdocs/COMPARISON.md - Comparison with Laravel Scout, Meilisearch, Algolia, Elasticsearchv2.0.0 is a major release adding a BM25 inverted index, extended search syntax, in-memory search, Scout integration, and significant scoring improvements. All v1.x behavior is preserved unless noted in the breaking changes below.
useInvertedIndex()) — globally-ranked full-text search; faster than LIKE-pattern search on 500k+ rows'include, =exact, ^prefix, word$, !exclude, | (OR), ( ) grouping, "quoted phrase"SearchBuilder::extended($query) and SearchBuilder::searchBoolean($query) entry points for structured queriesFuzzySearch::on($collection) for searching PHP collections with no DB queries[@fuzzyHighlight](https://github.com/fuzzyHighlight) Blade directive — XSS-safe <mark> rendering; passes all output through e()_matches array on results — column, value, and [start, end] character-offset pairs per match (requires ->highlight())_raw_score — original BM25 float preserved alongside normalized _scoreFuzzySearchEngine — Scout engine adapter bundled in core; activate with SCOUT_DRIVER=fuzzy-search; no extra package requiredphp artisan fuzzy-search:status — show index statistics per modelphp artisan fuzzy-search:rebuild {Model} [--async] — full or queued index rebuildphp artisan fuzzy-search:flush {Model} — remove all index entries for a modelphp artisan fuzzy-search:upgrade-v1 [path] — scan codebase for v1-era API usage; exits 1 when patterns found (CI-safe gate)SearchBuilder::preset() — apply a named config preset (ecommerce, blog, users, phonetic, exact)SearchBuilder::getFacets() — group result counts by field valuesSearchBuilder::didYouMean() — O(1) spell-correction via the term dictionary (no table scan)InMemorySearch method guard — throws BadMethodCallException with a list of supported methodsSearchableIndexingObserver no longer dispatches when no searchable column changed_score normalized to [0, 1] across all search paths. Code comparing _score against an absolute threshold > 1 must be updated. Use _raw_score for the original BM25 float.get() rescores before slicing — fetches up to max_candidates rows, rescores all in PHP, then slices. Top-N results are the most relevant N, not the first N SQL rows. May change result order vs v1.x.using('fuzzy'), using('trigram'), using('simple') now route to their correct drivers instead of falling through to Levenshtein. Rankings may shift for queries using these algorithms.cursorPaginate() on SearchBuilder always throws BadMethodCallException — use simplePaginate() or get()._highlighted is now derived from _matches (still backwards-compatible)FederatedSearch cross-model ranking uses normalized scoresbm25.max_postings_per_term, default 50 000) bounds peak memory per request/^[a-zA-Z_][a-zA-Z0-9_.]*$/ — blocks backtick-injection via searchIn()ORDER BY direction whitelisted to asc/desc[@fuzzyHighlight](https://github.com/fuzzyHighlight) tag argument validated against [a-zA-Z][a-zA-Z0-9-]* — prevents attribute injectionpaginateIndexed() page-size capped at 100! is treated as an operator boundary — =John!!! no longer parses trailing ! characters as part of the termsearchIn(): Duplicate column names are deduplicated — no more triple-binding SQL(term, columns) pairpaginateIndexed() computes the real total via COUNT(DISTINCT model_id) — page counts are accurate for large result setsIndexManager upsert path is race-safe under concurrent indexingavg_doc_length stays accurate across deletes and re-indexinguseIndex() → use useInvertedIndex()ReindexModelJob → use IndexModelJob (per-row) or RebuildIndexJob (bulk). Will be removed in v3.0.0.Searchable::reindex() / Searchable::performReindex() → use php artisan fuzzy-search:rebuildphp artisan migrate)| Migration | What it does |
|---|---|
create_fuzzy_index_terms_table |
Creates fuzzy_index_terms with term varchar(255) and a unique index |
create_fuzzy_index_postings_table |
Creates fuzzy_index_postings with UNIQUE (term_id, model_type, model_id) |
create_fuzzy_index_meta_table |
Creates fuzzy_index_meta |
create_fuzzy_index_documents_table |
Creates fuzzy_index_documents |
These tables are harmless if unused. If you never use BM25 search, simply ignore them.
See the upgrade guide for migration steps from v1.x.
| Key | Default | Purpose |
|---|---|---|
max_candidates |
1000 |
Max SQL rows fetched before PHP rescore |
legacy_dispatch |
false |
Silence InvalidAlgorithmException for unknown algorithm names |
indexing.enabled |
false |
Observer-based auto-indexing on save/delete |
indexing.tokenizer |
WhitespaceTokenizer |
Tokenizer class |
indexing.stemmer |
NullStemmer |
Stemmer class (use PorterStemmer for English) |
indexing.max_tokens_per_doc |
5000 |
Max unique tokens indexed per document |
bm25.k1 |
1.5 |
Term-frequency saturation |
bm25.b |
0.75 |
Length normalisation |
bm25.max_postings_per_term |
50000 |
SQL-side per-term posting cutoff |
query.max_tokens |
32 |
Parser token cap |
query.max_depth |
16 |
Parser nesting depth cap |
query.max_term_length |
128 |
Max character length of a single search term |
in_memory.max_items |
10000 |
Memory ceiling for FuzzySearch::on() |
in_memory.min_similarity |
60 |
Minimum similarity score for in-memory results |
How can I help you explore Laravel packages today?