
fuzzy-search package is a JavaScript autocomplete library that performs fuzzy, substring, and prefix matching against indexed entities.
You provide an array of objects with an identifier and one or more text terms. The library builds an internal index and returns ranked matches for a query string in well under 10 milliseconds.
Features:
- Indexes objects through stable IDs and searchable terms.
- Combines prefix, substring, and typo-tolerant matching.
- Prioritizes direct prefix matches in result ranking.
- Supports local autocomplete and live search interfaces.
- Updates indexed records through insert and update operations.
- Removes records through individual or bulk entity IDs.
- Normalizes casing, diacritics, spaces, and Latin character variants.
- Supports custom normalization for non-Latin scripts.
- Includes TypeScript declarations and module builds.
- Runs in browser projects and Node.js environments.
Use Cases
- Product catalog search matches partial product names, misspelled brands, and alternate keywords from one local index.
- A staff directory finds employees from first names, last names, departments, and email addresses as users type.
- Command palettes prioritize exact command prefixes before broader keyword matches.
- Documentation search maps each article to titles, headings, tags, and aliases for faster local lookup.
- Admin dashboards update or remove records from a searchable list after local edits or API refreshes.
How To Use It:
Installation
Install the fuzzy-search package with NPM.
npm install @m31coding/fuzzy-search
Import it as an ES module in modern JavaScript or TypeScript projects.
import * as fuzzySearch from '@m31coding/fuzzy-search';
// OR <script type="module"> import * as fuzzySearch from "https://cdn.jsdelivr.net/npm/@m31coding/[email protected]/dist/fuzzy-search.module.js"; </script>
Basic Usage
Index objects once after the data becomes available. Create a new query whenever the search input changes.
import * as fuzzySearch from '@m31coding/fuzzy-search';
const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher();
const teamMembers = [
{
id: 101,
name: 'Marina Lopez',
role: 'Product Designer',
email: '[email protected]'
},
{
id: 102,
name: 'Ethan Cole',
role: 'Frontend Developer',
email: '[email protected]'
},
{
id: 103,
name: 'Noah Bennett',
role: 'Support Engineer',
email: '[email protected]'
}
];
// Index each record with a stable ID and searchable terms.
searcher.indexEntities(
teamMembers,
(member) => member.id,
(member) => [member.name, member.role, member.email]
);
// Search for a name with a typo.
const result = searcher.getMatches(
new fuzzySearch.Query('marna lopez')
);
// Access the matched records.
console.log(result.matches);
Each match includes the original entity, a quality score, and the indexed term that matched the query.
Advanced Usages
Build a Custom Autocomplete Field
Place a result list beside the input field for a lightweight local autocomplete. This example uses textContent for result labels and leaves keyboard navigation under application control.
<label for="member-search">Search team members</label> <input id="member-search" type="search" autocomplete="off" placeholder="Search by name or role" > <ul id="member-results" role="listbox"></ul>
const input = document.querySelector('#member-search');
const resultsList = document.querySelector('#member-results');
function renderMatches(queryText) {
const result = searcher.getMatches(
new fuzzySearch.Query(queryText, 6)
);
resultsList.replaceChildren();
result.matches.forEach(({ entity }) => {
const item = document.createElement('li');
item.setAttribute('role', 'option');
item.textContent = `${entity.name} · ${entity.role}`;
resultsList.appendChild(item);
});
}
input.addEventListener('input', () => {
renderMatches(input.value);
});
Index Plain Strings
Use the string itself as the entity ID when the data source has no separate identifier.
const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher();
const departments = [
'Customer Success',
'Engineering',
'Human Resources',
'Product Operations'
];
searcher.indexEntities(
departments,
(department) => department,
(department) => [department]
);
const result = searcher.getMatches(
new fuzzySearch.Query('human res')
);
console.log(result.matches);
Update Searchable Records
Use bulk updates after a record changes in local application state. Large sets of repeated updates cost more than a fresh full index, so batch major data changes when possible.
const updatedMembers = [
{
id: 101,
name: 'Marina Lopez',
role: 'Design Director',
email: '[email protected]'
},
{
id: 104,
name: 'Derek Wang',
role: 'QA Engineer',
email: '[email protected]'
}
];
// Updates matching IDs and adds new IDs.
searcher.upsertEntities(
updatedMembers,
(member) => member.id,
(member) => [member.name, member.role, member.email]
);
// Removes records that should no longer appear in search.
searcher.removeEntities([103]);
Support Non-Latin Search Terms
The default normalizer filters non-alphanumeric characters. Adjust the allowed-character rule before creating the searcher when your indexed terms contain Arabic, Cyrillic, Greek, Han, or other non-Latin scripts.
const config = fuzzySearch.Config.createDefaultConfig(); // Preserve characters from additional scripts. config.normalizerConfig.allowCharacter = () => true; const searcher = fuzzySearch.SearcherFactory.createSearcher(config);
Control Query Result Quality
Increase the fuzzy threshold when broad matches create noisy results. Keep the default prefix and substring searchers for autocomplete fields that should favor direct text matches.
const query = new fuzzySearch.Query(
'design systm',
8,
[
new fuzzySearch.FuzzySearcher(0.45),
new fuzzySearch.SubstringSearcher(0),
new fuzzySearch.PrefixSearcher(0)
]
);
const result = searcher.getMatches(query);
A fuzzy quality threshold below 0.3 often produces irrelevant matches. ([GitHub][3])
Configuration Options
Query Options
string(string): The text to search.topN(number): Maximum number of matches to return. The default is10. UseInfinityfor every available match.searchers(SearcherSpec[]): Search modes and minimum quality thresholds. The default includes fuzzy, substring, and prefix search.
Searcher Specification Options
type(SearcherType): Search mode. Available values arefuzzy,substring, andprefix.minQuality(number): Minimum quality score for a search mode. Values below0become0.
Main Configuration Options
searcherTypes(SearcherType[]): Search modes included in the indexed searcher.maxQueryLength(number): Maximum supported query length. The default is150.sortOrder(SortOrder): Result sort mode. UsequalityAndIndexorqualityAndMatchedString.normalizerConfig(NormalizerConfig): Text normalization settings.fuzzySearchConfig(FuzzySearchConfig): N-gram fuzzy matching settings.substringSearchConfig(SubstringSearchConfig): Suffix-array search settings.
Normalizer Configuration Options
replacements(Map[]): Character replacement maps for normalized text.treatCharacterAsSpace(function): Determines which characters become spaces.allowCharacter(function): Determines which characters remain in searchable terms.
Fuzzy Search Configuration Options
paddingLeft(string): Prefix added before each normalized term.paddingRight(string): Suffix added after each normalized term.paddingMiddle(string): Replacement inserted where a normalized term contains spaces.ngramN(number): Character count for each generated n-gram.transformNgram(function): Converts or removes generated n-grams.inequalityPenalty(number): Score reduction for non-exact matches.
Substring Search Configuration Options
suffixArraySeparator(string): Separator used between indexed terms in the suffix array.
API Methods
// Creates a searcher with the package defaults.
const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher();
// Creates a searcher with a custom Config object.
const configuredSearcher = fuzzySearch.SearcherFactory.createSearcher(config);
// Replaces the current index with a new set of entities.
searcher.indexEntities(
teamMembers,
(member) => member.id,
(member) => [member.name, member.role, member.email]
);
// Returns ranked matches for a Query instance.
const result = searcher.getMatches(
new fuzzySearch.Query('product designer')
);
// Returns one indexed entity by its ID, or null.
const member = searcher.tryGetEntity(101);
// Returns every currently indexed entity.
const members = searcher.getEntities();
// Returns indexed terms for one entity ID, or null.
const terms = searcher.tryGetTerms(101);
// Returns every searchable term in the current index.
const allTerms = searcher.getTerms();
// Removes one entity and returns true when the ID existed.
const removed = searcher.removeEntity(101);
// Replaces an entity object but keeps its existing indexed terms.
const replaced = searcher.replaceEntity(
101,
updatedMember,
updatedMember.id
);
// Removes several entity IDs and returns removal metadata.
const removalResult = searcher.removeEntities([101, 102]);
// Updates existing entities and inserts new entities.
const updateMeta = searcher.upsertEntities(
updatedMembers,
(member) => member.id,
(member) => [member.name, member.role, member.email]
);
// Saves indexed state into a transferable Memento object.
const state = new fuzzySearch.Memento();
searcher.save(state);
// Loads previously saved indexed state.
searcher.load(new fuzzySearch.Memento(state.objects));
Implementation Tips:
- Create the index after data loads, not inside the input event handler.
- Keep entity IDs stable across indexing, updates, and removals.
- Use the same searchable fields for
indexEntities()andupsertEntities(). - Rebuild the full index with
indexEntities()after large batches of record changes. - Run indexing in a Web Worker when the dataset exceeds roughly 100,000 terms.
- Configure non-Latin normalization before the first index operation.
- Add listbox roles, active-option behavior, and keyboard support in autocomplete interfaces.
- Use a small
topNvalue for responsive suggestion lists.
Alternatives:
- Fast Fuzzy Search In Pure JavaScript – fuzzysort
- Simple Performant Fuzzy Search Library – Microfuzz
- Fast Autocomplete & Typeahead Library – autoComplete.js
- Fast Fuzzy Autocomplete Library – tiny-complete
- Fuzzy Search Library for Client-Side JavaScript – Fuse.js
FAQs:
Q: Does fuzzy-search include an autocomplete interface?
A: No. The package indexes and ranks data, but it does not create input fields, suggestion menus, keyboard navigation, or styles.
Q: How do I search multiple object properties?
A: Return every searchable value from the getTerms callback. A people directory might return a name, email address, role, team name, and a combined full-name value.
Q: Why do non-Latin search terms return no matches?
A: The default normalization rule filters many non-Latin characters. Set normalizerConfig.allowCharacter before creating the searcher, then index the data again.
Q: Why do updated records still return old search terms?
A: replaceEntity() only replaces the entity object. Use upsertEntities() when the searchable text changes, or rebuild the index with indexEntities() after a large batch update.
Q: How do I disable one of the search modes?
A: Pass a custom searcherTypes array in the configuration. For example, config.searcherTypes = ['fuzzy']; keeps only fuzzy search.
Q: How do I handle large datasets without blocking the UI?
A: Index the searcher inside a web worker. The repository includes a worker usage example. The worker builds the index and responds to query messages asynchronously.







