npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

fuzzyfindjs

v1.0.51

Published

A multi-language optimized fuzzy search library with phonetic matching, compound word splitting, and synonym support

Readme

🔍 fuzzyfindjs

NPM Version TypeScript License: MIT Bundle Size

🌞 Introduction

⚠️ BETA: fuzzyFindJS is in Beta-State. Be aware of potential quality, security and performance concerns when using fuzzyFindJS.

A multi-language fuzzy search library with phonetic matching, compound word splitting, and intelligent synonym support.

✨ Features

  • 🌍 Multi-language Support: German, English, Spanish, French with auto-detection and language-specific optimizations
  • 🔊 Phonetic Matching: Kölner Phonetik (German), Soundex-like algorithms for other languages
  • 🧩 Compound Word Splitting: Intelligent German compound word decomposition
  • 📚 Synonym Support: Built-in synonyms + custom synonym dictionaries
  • 🚀 Inverted Index: Auto-enabled for large datasets (10k+ words) - 10-100x faster for 1M+ words
  • 🎨 Match Highlighting: Show WHERE matches occurred with position tracking
  • 🔄 Batch Search: Search multiple queries at once with auto-deduplication
  • 🌍 Accent Normalization: Automatic handling of accented characters (café ↔ cafe)
  • ⚖️ Field Weighting: Multi-field search with weighted scoring (title > description)
  • 🚫 Stop Words Filtering: Remove common words (the, a, an) for better search quality
  • 📍 Word Boundaries: Precise matching with wildcard support (cat* matches category)
  • 💬 Phrase Search: Multi-word queries with quotes ("new york" finds "New York City")
  • 🎯 Typo Tolerant: Handles missing letters, extra letters, transpositions, keyboard neighbors
  • 🔤 N-gram Matching: Fast partial substring matching
  • 📊 BM25 Scoring: Industry-standard relevance ranking for better search results
  • 🎯 Bloom Filters: 50-70% faster negative lookups for large datasets
  • 🔍 FQL (Fuzzy Query Language): Boolean operators (AND, OR, NOT) for complex queries
  • 🔤 Phrase Parsing: Parse complex queries with quoted phrases ("new york")
  • 🌍 Language Detection: Auto-detect languages from text with confidence scores
  • 🔄 Incremental Updates: Add/remove items 10-100x faster than rebuilding
  • 📊 Configurable Scoring: Customizable thresholds and edit distances
  • 📦 Zero Dependencies: Lightweight and self-contained

📦 Installation

NPM / Yarn / PNPM

npm install fuzzyfindjs
yarn add fuzzyfindjs
pnpm add fuzzyfindjs

CDN (Browser)

For quick prototyping or simple projects, you can use FuzzyFindJS directly from a CDN:

<!-- unpkg - latest version -->
<script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>

<!-- unpkg - specific version (recommended for production) -->
<script src="https://unpkg.com/[email protected]/dist/umd/fuzzyfindjs.min.js"></script>

<!-- jsdelivr - latest version -->
<script src="https://cdn.jsdelivr.net/npm/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>

<!-- jsdelivr - specific version (recommended for production) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/fuzzyfindjs.min.js"></script>

Bundle Size: 23.7 KB minified, 7.5 KB gzipped

The library will be available globally as FuzzyFindJS:

<!DOCTYPE html>
<html>
<head>
    <title>FuzzyFindJS CDN Example</title>
</head>
<body>
    <input type="text" id="search" placeholder="Search...">
    <ul id="results"></ul>

    <script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
    <script>
        // Access the library from the global FuzzyFindJS object
        const { createFuzzySearch } = FuzzyFindJS;
        
        // Create search instance
        const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];
        const search = createFuzzySearch(items, {
            languages: ['english'],
            performance: 'fast',
            maxResults: 5
        });
        
        // Handle search input
        document.getElementById('search').addEventListener('input', (e) => {
            const results = search.search(e.target.value);
            const resultsList = document.getElementById('results');
            
            resultsList.innerHTML = results
                .map(r => `<li>${r.display} (${r.score.toFixed(2)})</li>`)
                .join('');
        });
    </script>
</body>
</html>

🎮 Try the Interactive Demo

Want to test the library before installing? Run the interactive demo dashboard:

git clone https://github.com/LucaIsMyName/fuzzyfindjs.git
cd fuzzyfindjs
npm install
npm run dev

The demo opens at http://localhost:3000 with:

  • 🔍 Real-time fuzzy search testing
  • 📚 5 pre-loaded dictionaries (German Healthcare, Cities, English Tech, Multi-language, Large Dataset)
  • ⚙️ Live configuration controls (performance modes, features, thresholds)
  • 📊 Performance metrics and debug information
  • 💾 Auto-saves your settings to localStorage
  • apps/speed-test.html: Comparison between different performance modes and with fuse.js
  • apps/ajax-search.html: AJAX search example with a Shop-Like Interface
  • apps/search-text.html: Search a large text corpus adn highlight matching Words. FQL is enabled.

🚀 Quick Start

import { createFuzzySearch } from 'fuzzyfindjs';

// Create a search instance with your dictionary
const search = createFuzzySearch([
  'Hospital',
  'Pharmacy',
  'Doctor',
  'Dentist',
  'Kindergarten',
  // ...
]);

// Search with typos, partial words, phonetic similarity
const results = search.search('hospitl');
// [{ display: 'Hospital', score: 0.92, ... }]

const results2 = search.search('farmacy');
// [{ display: 'Pharmacy', score: 0.88, ... }]

// German example
const germanSearch = createFuzzySearch([
  'Krankenhaus',
  'Apotheke',
  'Zahnarzt'
], {
  languages: ['german']
});

const results3 = germanSearch.search('krankenh');
// [{ display: 'Krankenhaus', score: 0.95, ... }]

🎛️ API

Core Functions

buildFuzzyIndex(words, options?)

Creates a searchable index from strings or objects. Supports multi-field indexing for complex data structures.

Signature:

function buildFuzzyIndex(
  words: (string | object)[],
  options?: BuildIndexOptions
): FuzzyIndex

Parameters:

  • words - Array of strings or objects to index
  • options.config - Fuzzy search configuration (languages, features, performance)
  • options.fields - Field names to index (required for objects)
  • options.fieldWeights - Weight multipliers for each field
  • options.languageProcessors - Custom language processors
  • options.onProgress - Progress callback (processed, total) => void
  • options.useInvertedIndex - Force inverted index (auto-enabled for 10k+ items)

Returns: FuzzyIndex object

Example 1: Simple string array

import { buildFuzzyIndex } from 'fuzzyfindjs';

const cities = ['Berlin', 'München', 'Hamburg', 'Frankfurt'];
const index = buildFuzzyIndex(cities, {
  config: {
    languages: ['german'],
    performance: 'balanced'
  }
});

Example 2: Multi-field objects

const products = [
  { name: 'iPhone 15', brand: 'Apple', price: 999, category: 'Phones' },
  { name: 'Galaxy S24', brand: 'Samsung', price: 899, category: 'Phones' },
  { name: 'MacBook Pro', brand: 'Apple', price: 1999, category: 'Laptops' }
];

const index = buildFuzzyIndex(products, {
  fields: ['name', 'brand', 'category', 'price'],
  fieldWeights: {
    name: 2.0,    // Name matches weighted 2x
    brand: 1.5,   // Brand matches weighted 1.5x
    category: 1.0 // Category normal weight
  }
});

Example 3: Large dataset with progress

const largeDataset = [...]; // 100k items

const index = buildFuzzyIndex(largeDataset, {
  config: { performance: 'fast' },
  onProgress: (processed, total) => {
    console.log(`Progress: ${(processed/total*100).toFixed(1)}%`);
  }
});

getSuggestions(index, query, maxResults?, options?)

Searches the index and returns ranked results. Supports filtering, sorting, and advanced search options.

Signature:

function getSuggestions(
  index: FuzzyIndex,
  query: string,
  maxResults?: number,
  options?: SearchOptions
): SuggestionResult[]

Parameters:

  • index - The fuzzy index from buildFuzzyIndex()
  • query - Search query string
  • maxResults - Maximum results to return (default: from config)
  • options.fuzzyThreshold - Override threshold (0-1, default: 0.75)
  • options.languages - Filter by specific languages
  • options.matchTypes - Filter by match types ('exact', 'fuzzy', 'phonetic', etc.)
  • options.debug - Include debug information
  • options.includeHighlights - Include match position highlights
  • options.enableFQL - Enable Fuzzy Query Language (AND/OR/NOT operators)
  • options.filters - E-commerce filters (range, term, boolean)
  • options.sort - Custom sorting configuration

Returns: Array of SuggestionResult objects

Example 1: Basic search

import { getSuggestions } from 'fuzzyfindjs';

const results = getSuggestions(index, 'berln', 5);
// Returns: [{ display: 'Berlin', score: 0.92, ... }]

results.forEach(r => {
  console.log(`${r.display} (${(r.score * 100).toFixed(0)}% match)`);
});

Example 2: Search with filters and sorting

const results = getSuggestions(index, 'phone', 10, {
  filters: {
    ranges: [{ field: 'price', min: 500, max: 1500 }],
    terms: [{ field: 'brand', values: ['Apple', 'Samsung'] }],
    booleans: [{ field: 'inStock', value: true }]
  },
  sort: {
    primary: { field: 'price', order: 'asc' },
    secondary: { field: 'rating', order: 'desc' }
  }
});

Example 3: Debug mode

const results = getSuggestions(index, 'hospitl', 5, {
  debug: true,
  includeHighlights: true
});

results.forEach(r => {
  console.log(r.display);
  console.log('Match type:', r._debug_matchType);
  console.log('Highlights:', r.highlights);
});

batchSearch(index, queries, maxResults?)

Searches multiple queries at once with automatic deduplication.

Signature:

function batchSearch(
  index: FuzzyIndex,
  queries: string[],
  maxResults?: number
): Map<string, SuggestionResult[]>

Parameters:

  • index - The fuzzy index
  • queries - Array of search queries
  • maxResults - Maximum results per query

Returns: Map of query → results

Example:

import { batchSearch } from 'fuzzyfindjs';

const queries = ['berln', 'munchen', 'hambur'];
const results = batchSearch(index, queries, 3);

results.forEach((suggestions, query) => {
  console.log(`Query: "${query}"`);
  suggestions.forEach(s => console.log(`  - ${s.display}`));
});

updateIndex(index, newWords)

Incrementally adds new items to an existing index.

Signature:

function updateIndex(
  index: FuzzyIndex,
  newWords: (string | object)[]
): FuzzyIndex

Parameters:

  • index - Existing index to update
  • newWords - New items to add

Returns: Updated index (mutates original)

Example:

import { updateIndex } from 'fuzzyfindjs';

// Initial index
const index = buildFuzzyIndex(['Apple', 'Banana']);

// Add new items later
updateIndex(index, ['Cherry', 'Date', 'Elderberry']);

// Index now contains all 5 items
const results = getSuggestions(index, 'cherry');

removeFromIndex(index, wordsToRemove)

Removes items from an existing index.

Signature:

function removeFromIndex(
  index: FuzzyIndex,
  wordsToRemove: string[]
): FuzzyIndex

Parameters:

  • index - Existing index
  • wordsToRemove - Items to remove (exact match)

Returns: Updated index (mutates original)

Example:

import { removeFromIndex } from 'fuzzyfindjs';

const index = buildFuzzyIndex(['Apple', 'Banana', 'Cherry']);

// Remove items
removeFromIndex(index, ['Banana']);

// Index now only contains Apple and Cherry
const results = getSuggestions(index, 'ban'); // Returns empty

createFuzzySearch(dictionary, options?)

Wrapper that combines index building and searching into a single object.

Signature:

function createFuzzySearch(
  dictionary: string[],
  options?: {
    languages?: string[];
    performance?: 'fast' | 'balanced' | 'comprehensive';
    maxResults?: number;
  }
): { search: (query: string, maxResults?: number) => SuggestionResult[]; index: FuzzyIndex }

Parameters:

  • dictionary - Array of strings to search
  • options - Quick configuration

Returns: Object with search() method and index property

Example:

import { createFuzzySearch } from 'fuzzyfindjs';

const fuzzy = createFuzzySearch(['Berlin', 'München', 'Hamburg'], {
  languages: ['german'],
  performance: 'fast',
  maxResults: 5
});

// Search directly
const results = fuzzy.search('berln');

// Access underlying index
console.log(fuzzy.index.base.length); // 3

Configuration

FuzzyConfig

Complete configuration interface:

interface FuzzyConfig {
  // Languages to enable
  languages: string[];  // default: ['english']
  
  // Features to enable
  features: FuzzyFeature[];
  
  // Performance mode
  performance: 'fast' | 'balanced' | 'comprehensive';  // default: 'balanced'
  
  // Maximum results to return
  maxResults: number;  // default: 10
  
  // Minimum query length
  minQueryLength: number;  // default: 2
  
  // Fuzzy matching threshold (0-1)
  fuzzyThreshold: number;  // default: 0.75
  
  // Maximum edit distance
  maxEditDistance: number;  // default: 2
  
  // N-gram size for partial matching
  ngramSize: number;  // default: 3
  
  // Custom synonym dictionaries
  customSynonyms?: Record<string, string[]>;
  
  // Custom normalization function
  customNormalizer?: (word: string) => string;

  // Enable FQL
  enableFQL?: boolean;

  // Enable Inverted Index
  enableInvertedIndex?: boolean;

  // Enable Highlighting
  enableHighlighting?: boolean;

  // Enable Debugging
  debug?: boolean;

  // Enable Progress Callback
  onProgress?: (processed: number, total: number) => void;

}

Available Features

type FuzzyFeature = 
  | 'phonetic'           // Phonetic matching (sounds-like)
  | 'compound'           // Compound word splitting (German)
  | 'synonyms'           // Synonym matching
  | 'keyboard-neighbors' // Keyboard typo tolerance
  | 'partial-words'      // Prefix/substring matching
  | 'missing-letters'    // Handle omitted characters
  | 'extra-letters'      // Handle extra characters
  | 'transpositions';    // Handle swapped characters

Performance Presets

import { PERFORMANCE_CONFIGS } from 'fuzzyfindjs';

// Fast: Minimal features, quick searches
PERFORMANCE_CONFIGS.fast

// Balanced: Good mix of features and speed (default)
PERFORMANCE_CONFIGS.balanced

// Comprehensive: All features, best accuracy
PERFORMANCE_CONFIGS.comprehensive

Types

SuggestionResult

interface SuggestionResult {
  display: string;      // Formatted display text
  baseWord: string;     // Original matched word
  isSynonym: boolean;   // True if matched via synonym
  score: number;        // Confidence score (0-1)
  language?: string;    // Language that matched
}

BuildIndexOptions

interface BuildIndexOptions {
  config?: Partial<FuzzyConfig>;
  languageProcessors?: LanguageProcessor[];
  onProgress?: (processed: number, total: number) => void;
}

SearchOptions

interface SearchOptions {
  maxResults?: number;
  fuzzyThreshold?: number;
  languages?: string[];
  matchTypes?: MatchType[];
  debug?: boolean;
}

Utility Functions

serializeIndex(index)

Converts index to JSON string for storage.

import { serializeIndex } from 'fuzzyfindjs';

const index = buildFuzzyIndex(['Apple', 'Banana']);
const json = serializeIndex(index);
localStorage.setItem('search-index', json);

deserializeIndex(json)

Reconstructs index from JSON string.

import { deserializeIndex } from 'fuzzyfindjs';

const json = localStorage.getItem('search-index');
const index = deserializeIndex(json);
const results = getSuggestions(index, 'aple');

saveIndexToLocalStorage(index, key)

Saves index to browser localStorage.

import { saveIndexToLocalStorage } from 'fuzzyfindjs';

saveIndexToLocalStorage(index, 'my-search-index');

loadIndexFromLocalStorage(key)

Loads index from browser localStorage.

import { loadIndexFromLocalStorage } from 'fuzzyfindjs';

const index = loadIndexFromLocalStorage('my-search-index');
if (index) {
  const results = getSuggestions(index, 'query');
}

getSerializedSize(index)

Returns size of serialized index in bytes.

import { getSerializedSize } from 'fuzzyfindjs';

const sizeBytes = getSerializedSize(index);
console.log(`Index size: ${(sizeBytes / 1024).toFixed(2)} KB`);

applyFilters(results, filters)

Applies filters to search results.

import { applyFilters } from 'fuzzyfindjs';

const allResults = getSuggestions(index, 'phone');
const filtered = applyFilters(allResults, {
  ranges: [{ field: 'price', min: 500, max: 1000 }],
  terms: [{ field: 'brand', values: ['Apple', 'Samsung'] }],
  booleans: [{ field: 'inStock', value: true }]
});

applySorting(results, sortConfig)

Applies custom sorting to results.

import { applySorting } from 'fuzzyfindjs';

const results = getSuggestions(index, 'laptop');
const sorted = applySorting(results, {
  primary: { field: 'price', order: 'asc' },
  secondary: { field: 'rating', order: 'desc' },
  keepRelevance: true
});

calculateHighlights(match, query, text)

Calculates character positions where query matches text.

import { calculateHighlights } from 'fuzzyfindjs';

const highlights = calculateHighlights(match, 'berln', 'Berlin');
// Returns: [{ start: 0, end: 3 }, { start: 4, end: 5 }]

formatHighlightedHTML(text, highlights)

Wraps highlighted portions in HTML tags.

import { formatHighlightedHTML } from 'fuzzyfindjs';

const html = formatHighlightedHTML('Berlin', highlights);
// Returns: '<mark>Ber</mark>li<mark>n</mark>'

removeAccents(text)

Removes accents from text.

import { removeAccents } from 'fuzzyfindjs';

removeAccents('café'); // 'cafe'
removeAccents('naïve'); // 'naive'
removeAccents('Müller'); // 'Muller'

hasAccents(text)

Checks if text contains accents.

import { hasAccents } from 'fuzzyfindjs';

hasAccents('café'); // true
hasAccents('cafe'); // false

normalizeForComparison(text)

Normalizes text for comparison (lowercase + remove accents).

import { normalizeForComparison } from 'fuzzyfindjs';

normalizeForComparison('Café'); // 'cafe'
normalizeForComparison('NAÏVE'); // 'naive'

getAccentVariants(text)

Generates all accent variants of text.

import { getAccentVariants } from 'fuzzyfindjs';

getAccentVariants('cafe');
// Returns: ['cafe', 'café', 'cafè', 'cafê', ...]

filterStopWords(text, stopWords)

Removes stop words from text.

import { filterStopWords } from 'fuzzyfindjs';

filterStopWords('the quick brown fox', ['the', 'a']);
// Returns: 'quick brown fox'

isStopWord(word, stopWords)

Checks if word is a stop word.

import { isStopWord } from 'fuzzyfindjs';

isStopWord('the', ['the', 'a', 'an']); // true
isStopWord('fox', ['the', 'a', 'an']); // false

getStopWordsForLanguages(languages)

Gets stop words for specified languages.

import { getStopWordsForLanguages } from 'fuzzyfindjs';

const stopWords = getStopWordsForLanguages(['english', 'german']);
// Returns: ['the', 'a', 'an', 'der', 'die', 'das', ...]

DEFAULT_STOP_WORDS

Pre-defined stop words for common languages.

import { DEFAULT_STOP_WORDS } from 'fuzzyfindjs';

console.log(DEFAULT_STOP_WORDS.english);
// ['the', 'a', 'an', 'is', 'at', 'on', ...]

isWordBoundary(char)

Checks if character is a word boundary.

import { isWordBoundary } from 'fuzzyfindjs';

isWordBoundary(' '); // true
isWordBoundary('-'); // true
isWordBoundary('a'); // false

matchesAtWordBoundary(text, query)

Checks if query matches at word boundary.

import { matchesAtWordBoundary } from 'fuzzyfindjs';

matchesAtWordBoundary('hello world', 'world'); // true
matchesAtWordBoundary('hello world', 'orld'); // false

findWordBoundaryMatches(text, query)

Finds all word boundary matches.

import { findWordBoundaryMatches } from 'fuzzyfindjs';

findWordBoundaryMatches('hello world hello', 'hello');
// Returns: [{ start: 0, end: 5 }, { start: 12, end: 17 }]

matchesWord(text, word)

Checks if text contains exact word.

import { matchesWord } from 'fuzzyfindjs';

matchesWord('hello world', 'hello'); // true
matchesWord('hello world', 'hel'); // false

matchesWildcard(text, pattern)

Matches text against wildcard pattern.

import { matchesWildcard } from 'fuzzyfindjs';

matchesWildcard('application', 'app*'); // true
matchesWildcard('category', 'cat*'); // true
matchesWildcard('dog', 'cat*'); // false

dataToIndex(data, options)

Extracts searchable text from structured data.

import { dataToIndex } from 'fuzzyfindjs';

const html = '<div><h1>Title</h1><p>Content</p></div>';
const words = dataToIndex(html, { type: 'html' });
// Returns: ['Title', 'Content']

const json = { name: 'John', email: '[email protected]' };
const words2 = dataToIndex(json, { type: 'json' });
// Returns: ['John', '[email protected]']

dataToIndexAsync(data, options)

Async version of dataToIndex for large datasets.

import { dataToIndexAsync } from 'fuzzyfindjs';

const largeHtml = '...'; // Large HTML document
const words = await dataToIndexAsync(largeHtml, {
  type: 'html',
  chunkSize: 1000
});

parseQuery(query)

Parses query into terms and phrases.

import { parseQuery } from 'fuzzyfindjs';

const parsed = parseQuery('hello "new york" world');
// Returns: {
//   terms: ['hello', 'world'],
//   phrases: ['new york'],
//   hasPhrases: true
// }

hasPhraseSyntax(query)

Checks if query contains phrase syntax (quotes).

import { hasPhraseSyntax } from 'fuzzyfindjs';

hasPhraseSyntax('"new york"'); // true
hasPhraseSyntax('new york'); // false

normalizePhrase(phrase)

Normalizes phrase for matching.

import { normalizePhrase } from 'fuzzyfindjs';

normalizePhrase('"New York"'); // 'new york'

splitPhraseWords(phrase)

Splits phrase into individual words.

import { splitPhraseWords } from 'fuzzyfindjs';

splitPhraseWords('new york city'); // ['new', 'york', 'city']

detectLanguages(text)

Detects languages in text.

import { detectLanguages } from 'fuzzyfindjs';

const languages = detectLanguages('Hello Krankenhaus café');
// Returns: ['english', 'german', 'french']

detectLanguagesWithConfidence(text)

Detects languages with confidence scores.

import { detectLanguagesWithConfidence } from 'fuzzyfindjs';

const results = detectLanguagesWithConfidence('Hello world');
// Returns: [{ language: 'english', confidence: 0.95 }]

sampleTextForDetection(words, sampleSize)

Samples text for language detection.

import { sampleTextForDetection } from 'fuzzyfindjs';

const sample = sampleTextForDetection(largeArray, 100);
// Returns first 100 items concatenated

isValidLanguage(language)

Checks if language is supported.

import { isValidLanguage } from 'fuzzyfindjs';

isValidLanguage('english'); // true
isValidLanguage('klingon'); // false

normalizeLanguageCode(code)

Normalizes language code.

import { normalizeLanguageCode } from 'fuzzyfindjs';

normalizeLanguageCode('en'); // 'english'
normalizeLanguageCode('de'); // 'german'

SearchCache

LRU cache for search results.

import { SearchCache } from 'fuzzyfindjs';

const cache = new SearchCache(100); // Max 100 entries

// Manual caching
cache.set('query', results, 10);
const cached = cache.get('query', 10);

// Stats
const stats = cache.getStats();
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);

// Clear
cache.clear();

LRUCache

Generic LRU cache.

import { LRUCache } from 'fuzzyfindjs';

const cache = new LRUCache<string, any>(50);

cache.set('key', { data: 'value' });
const value = cache.get('key');

console.log(cache.size); // 1
cache.clear();

DEFAULT_CONFIG

Default configuration object.

import { DEFAULT_CONFIG } from 'fuzzyfindjs';

console.log(DEFAULT_CONFIG.languages); // ['english']
console.log(DEFAULT_CONFIG.performance); // 'balanced'
console.log(DEFAULT_CONFIG.fuzzyThreshold); // 0.75

PERFORMANCE_CONFIGS

Pre-defined performance configurations.

import { PERFORMANCE_CONFIGS } from 'fuzzyfindjs';

// Fast mode
const fastConfig = PERFORMANCE_CONFIGS.fast;

// Balanced mode
const balancedConfig = PERFORMANCE_CONFIGS.balanced;

// Comprehensive mode
const comprehensiveConfig = PERFORMANCE_CONFIGS.comprehensive;

mergeConfig(userConfig)

Merges user config with defaults.

import { mergeConfig } from 'fuzzyfindjs';

const config = mergeConfig({
  languages: ['german'],
  maxResults: 20
});
// Returns complete config with defaults filled in

💡 Usage Examples

Browser: CDN Usage

Basic Search with Vanilla JavaScript

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FuzzyFindJS - Simple Search</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; }
        input { width: 100%; padding: 10px; font-size: 16px; }
        ul { list-style: none; padding: 0; }
        li { padding: 8px; border-bottom: 1px solid #eee; }
        .score { color: #666; font-size: 0.9em; }
    </style>
</head>
<body>
    <h1>🔍 Fuzzy Search Demo</h1>
    <input type="text" id="search" placeholder="Type to search..." autofocus>
    <ul id="results"></ul>

    <script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
    <script>
        const { createFuzzySearch } = FuzzyFindJS;
        
        const fruits = [
            'Apple', 'Apricot', 'Banana', 'Blackberry', 'Blueberry',
            'Cherry', 'Coconut', 'Cranberry', 'Date', 'Dragonfruit',
            'Elderberry', 'Fig', 'Grape', 'Grapefruit', 'Guava',
            'Kiwi', 'Lemon', 'Lime', 'Mango', 'Orange', 'Papaya',
            'Peach', 'Pear', 'Pineapple', 'Plum', 'Raspberry',
            'Strawberry', 'Watermelon'
        ];
        
        const search = createFuzzySearch(fruits, {
            languages: ['english'],
            performance: 'fast',
            maxResults: 10
        });
        
        const searchInput = document.getElementById('search');
        const resultsList = document.getElementById('results');
        
        searchInput.addEventListener('input', (e) => {
            const query = e.target.value;
            
            if (query.length < 2) {
                resultsList.innerHTML = '';
                return;
            }
            
            const results = search.search(query);
            
            if (results.length === 0) {
                resultsList.innerHTML = '<li>No results found</li>';
                return;
            }
            
            resultsList.innerHTML = results
                .map(r => `
                    <li>
                        <strong>${r.display}</strong>
                        <span class="score">Score: ${r.score.toFixed(2)}</span>
                    </li>
                `)
                .join('');
        });
    </script>
</body>
</html>

Multi-language Search

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Multi-language Fuzzy Search</title>
</head>
<body>
    <h1>🌍 Multi-language Search</h1>
    <input type="text" id="search" placeholder="Search in any language...">
    <div id="results"></div>

    <script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
    <script>
        const { createFuzzySearch } = FuzzyFindJS;
        
        // Dictionary with German, English, French, Spanish words
        const places = [
            'Krankenhaus', 'Hospital', 'Hôpital', 'Hospital',
            'Schule', 'School', 'École', 'Escuela',
            'Apotheke', 'Pharmacy', 'Pharmacie', 'Farmacia',
            'Bahnhof', 'Station', 'Gare', 'Estación',
            'Flughafen', 'Airport', 'Aéroport', 'Aeropuerto'
        ];
        
        const search = createFuzzySearch(places, {
            languages: ['german', 'english', 'french', 'spanish'],
            performance: 'comprehensive',
            features: ['phonetic', 'partial-words', 'synonyms'],
            maxResults: 15
        });
        
        document.getElementById('search').addEventListener('input', (e) => {
            const results = search.search(e.target.value);
            const output = document.getElementById('results');
            
            output.innerHTML = results.length 
                ? results.map(r => `<p>${r.display} (${r.score.toFixed(2)})</p>`).join('')
                : '<p>No results</p>';
        });
    </script>
</body>
</html>

Autocomplete Dropdown

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Autocomplete with FuzzyFindJS</title>
    <style>
        .autocomplete {
            position: relative;
            width: 400px;
            margin: 50px auto;
        }
        .autocomplete input {
            width: 100%;
            padding: 12px;
            font-size: 16px;
            border: 2px solid #ddd;
            border-radius: 4px;
        }
        .autocomplete-items {
            position: absolute;
            border: 1px solid #ddd;
            border-top: none;
            z-index: 99;
            top: 100%;
            left: 0;
            right: 0;
            max-height: 300px;
            overflow-y: auto;
            background: white;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }
        .autocomplete-item {
            padding: 10px;
            cursor: pointer;
            border-bottom: 1px solid #eee;
        }
        .autocomplete-item:hover {
            background-color: #e9e9e9;
        }
        .autocomplete-active {
            background-color: #4CAF50 !important;
            color: white;
        }
    </style>
</head>
<body>
    <div class="autocomplete">
        <input type="text" id="cityInput" placeholder="Search cities...">
        <div id="autocomplete-list" class="autocomplete-items"></div>
    </div>

    <script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
    <script>
        const { createFuzzySearch } = FuzzyFindJS;
        
        const cities = [
            'Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt',
            'Stuttgart', 'Düsseldorf', 'Dortmund', 'Essen', 'Leipzig',
            'Bremen', 'Dresden', 'Hannover', 'Nürnberg', 'Duisburg'
        ];
        
        const search = createFuzzySearch(cities, {
            languages: ['german'],
            performance: 'fast',
            maxResults: 8
        });
        
        const input = document.getElementById('cityInput');
        const list = document.getElementById('autocomplete-list');
        
        input.addEventListener('input', (e) => {
            const query = e.target.value;
            list.innerHTML = '';
            
            if (query.length < 2) return;
            
            const results = search.search(query);
            
            results.forEach(result => {
                const item = document.createElement('div');
                item.className = 'autocomplete-item';
                item.textContent = result.display;
                item.addEventListener('click', () => {
                    input.value = result.display;
                    list.innerHTML = '';
                });
                list.appendChild(item);
            });
        });
        
        // Close dropdown when clicking outside
        document.addEventListener('click', (e) => {
            if (!e.target.closest('.autocomplete')) {
                list.innerHTML = '';
            }
        });
    </script>
</body>
</html>

Frontend: Autocomplete Search

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

// Build index once on app initialization
const cities = ['Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt'];
const cityIndex = buildFuzzyIndex(cities, {
  config: { languages: ['german'], performance: 'fast' }
});

// Use in search input handler
function handleSearchInput(query: string) {
  const results = getSuggestions(cityIndex, query, 5);
  updateAutocompleteDropdown(results);
}

// Handles: "munchen" → "München", "berln" → "Berlin"

Backend: API Search Endpoint

import express from 'express';
import { createFuzzySearch } from 'fuzzyfindjs';

const app = express();

// Load product catalog
const products = await loadProductsFromDatabase();
const productNames = products.map(p => p.name);

// Build search index
const search = createFuzzySearch(productNames, {
  languages: ['english', 'german'],
  performance: 'balanced',
  maxResults: 20
});

// Search endpoint
app.get('/api/search', (req, res) => {
  const query = req.query.q as string;
  const results = search.search(query);
  
  // Map back to full product objects
  const products = results.map(r => 
    products.find(p => p.name === r.baseWord)
  );
  
  res.json(products);
});

Async Dictionary Loading (Fetch from API/Database)

The library is fully synchronous - no async methods needed! This gives you complete control over when and how to load your dictionary.

Pattern 1: Load Once on App Startup

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

// Fetch dictionary from API
async function initializeSearch() {
  // Load your dictionary from any source
  const response = await fetch('/api/dictionary');
  const words = await response.json();
  
  // Build index synchronously (fast!)
  const index = buildFuzzyIndex(words, {
    config: {
      languages: ['english'],
      performance: 'balanced'
    }
  });
  
  return index;
}

// Initialize once
let searchIndex;
initializeSearch().then(index => {
  searchIndex = index;
  console.log('Search ready!');
});

// Use in your app
function search(query) {
  if (!searchIndex) {
    return []; // Or show loading state
  }
  return getSuggestions(searchIndex, query);
}

Pattern 2: React with useState/useEffect

import { useState, useEffect } from 'react';
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

function SearchComponent() {
  const [index, setIndex] = useState(null);
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(true);

  // Load dictionary on mount
  useEffect(() => {
    async function loadDictionary() {
      try {
        const response = await fetch('/api/products');
        const products = await response.json();
        const words = products.map(p => p.name);
        
        // Build index (synchronous, fast)
        const searchIndex = buildFuzzyIndex(words, {
          config: { languages: ['english'], performance: 'fast' }
        });
        
        setIndex(searchIndex);
      } catch (error) {
        console.error('Failed to load dictionary:', error);
      } finally {
        setLoading(false);
      }
    }
    
    loadDictionary();
  }, []);

  const handleSearch = (query) => {
    if (!index || !query) {
      setResults([]);
      return;
    }
    
    const matches = getSuggestions(index, query, 10);
    setResults(matches);
  };

  if (loading) return <div>Loading search...</div>;
  
  return (
    <div>
      <input 
        type="text" 
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {results.map(r => <li key={r.baseWord}>{r.display}</li>)}
      </ul>
    </div>
  );
}

Pattern 3: Node.js with Database

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
import { MongoClient } from 'mongodb';

class ProductSearch {
  private index = null;
  
  async initialize() {
    // Connect to database
    const client = await MongoClient.connect(process.env.MONGO_URL);
    const db = client.db('myapp');
    
    // Fetch all product names
    const products = await db.collection('products')
      .find({}, { projection: { name: 1 } })
      .toArray();
    
    const words = products.map(p => p.name);
    
    // Build index synchronously
    this.index = buildFuzzyIndex(words, {
      config: {
        languages: ['english', 'german'],
        performance: 'balanced',
        maxResults: 20
      },
      onProgress: (processed, total) => {
        console.log(`Indexing: ${processed}/${total}`);
      }
    });
    
    console.log(`Indexed ${words.length} products`);
    await client.close();
  }
  
  search(query) {
    if (!this.index) {
      throw new Error('Search not initialized');
    }
    return getSuggestions(this.index, query);
  }
}

// Usage
const search = new ProductSearch();
await search.initialize();

app.get('/search', (req, res) => {
  const results = search.search(req.query.q);
  res.json(results);
});

Pattern 4: Dynamic Re-indexing

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

class DynamicSearch {
  private index = null;
  private lastUpdate = 0;
  private updateInterval = 5 * 60 * 1000; // 5 minutes
  
  async refreshIndex() {
    const now = Date.now();
    
    // Only refresh if enough time has passed
    if (now - this.lastUpdate < this.updateInterval) {
      return;
    }
    
    console.log('Refreshing search index...');
    
    // Fetch latest data
    const response = await fetch('/api/dictionary?updated_since=' + this.lastUpdate);
    const words = await response.json();
    
    // Rebuild index (fast, even for large dictionaries)
    this.index = buildFuzzyIndex(words, {
      config: { languages: ['english'], performance: 'fast' }
    });
    
    this.lastUpdate = now;
    console.log(`Index refreshed with ${words.length} words`);
  }
  
  async search(query) {
    // Auto-refresh if needed
    await this.refreshIndex();
    
    if (!this.index) {
      throw new Error('Index not initialized');
    }
    
    return getSuggestions(this.index, query);
  }
}

Key Points

No async needed in the library - buildFuzzyIndex() is synchronous and fast

Fetch your dictionary however you want:

  • REST API (fetch, axios)
  • Database (MongoDB, PostgreSQL, etc.)
  • File system (fs.readFile)
  • GraphQL
  • WebSocket
  • Any async source!

Index building is fast:

  • 1,000 words: ~10ms
  • 10,000 words: ~50-250ms (depending on performance mode)
  • 100,000 words: ~500ms-2s

Build once, search many times:

  • Building the index is the "expensive" operation
  • Searching is extremely fast (<1ms per query)
  • Cache the index in memory for best performance

Re-indexing strategies:

  • Static: Build once on app startup
  • Periodic: Rebuild every N minutes/hours
  • On-demand: Rebuild when data changes
  • Incremental: Keep old index, build new one in background

Multi-language Search

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

const multiLangDictionary = [
  'Hospital', 'Krankenhaus', 'Hôpital', 'Hospital',
  'School', 'Schule', 'École', 'Escuela',
  'Car', 'Auto', 'Voiture', 'Coche'
];

const index = buildFuzzyIndex(multiLangDictionary, {
  config: {
    languages: ['english', 'german', 'french', 'spanish'],
    performance: 'comprehensive',
    features: ['phonetic', 'partial-words', 'synonyms']
  }
});

// Search works across all languages
getSuggestions(index, 'kranken');  // Finds "Krankenhaus"
getSuggestions(index, 'hospit');   // Finds all hospital variants
getSuggestions(index, 'ecol');     // Finds "École", "Escuela", "School"

Custom Synonyms

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

const medicalTerms = ['Physician', 'Nurse', 'Surgeon', 'Therapist'];

const index = buildFuzzyIndex(medicalTerms, {
  config: {
    languages: ['english'],
    features: ['synonyms', 'phonetic'],
    customSynonyms: {
      'physician': ['doctor', 'doc', 'md'],
      'nurse': ['rn', 'caregiver'],
      'surgeon': ['doctor', 'md', 'specialist']
    }
  }
});

// Synonym matching works
getSuggestions(index, 'doctor');  // Finds "Physician", "Surgeon"
getSuggestions(index, 'doc');     // Finds "Physician"

Large Dataset with Progress

import { buildFuzzyIndex } from 'fuzzyfindjs';

const largeDictionary = await loadMillionWords();

const index = buildFuzzyIndex(largeDictionary, {
  config: {
    languages: ['english'],
    performance: 'fast'  // Optimize for large datasets
  },
  onProgress: (processed, total) => {
    const percent = ((processed / total) * 100).toFixed(1);
    console.log(`Building index: ${percent}%`);
    updateProgressBar(percent);
  }
});

React Hook Example

import { useState, useMemo } from 'react';
import { createFuzzySearch } from 'fuzzyfindjs';

function useSearch(dictionary: string[]) {
  const [query, setQuery] = useState('');
  
  const search = useMemo(() => 
    createFuzzySearch(dictionary, {
      languages: ['english'],
      performance: 'fast',
      maxResults: 10
    }),
    [dictionary]
  );
  
  const results = useMemo(() => 
    query.length >= 2 ? search.search(query) : [],
    [query, search]
  );
  
  return { query, setQuery, results };
}

// Usage in component
function SearchComponent() {
  const { query, setQuery, results } = useSearch(myDictionary);
  
  return (
    <div>
      <input 
        value={query} 
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {results.map(r => (
          <li key={r.baseWord}>
            {r.display} <span>({r.score.toFixed(2)})</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

Node.js CLI Tool

#!/usr/bin/env node
import { createFuzzySearch } from 'fuzzyfindjs';
import * as readline from 'readline';

const dictionary = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
const search = createFuzzySearch(dictionary);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

console.log('Fuzzy Search CLI - Type to search, Ctrl+C to exit\n');

rl.on('line', (query) => {
  const results = search.search(query);
  
  if (results.length === 0) {
    console.log('No results found\n');
  } else {
    results.forEach((r, i) => {
      console.log(`${i + 1}. ${r.display} (${r.score.toFixed(2)})`);
    });
    console.log();
  }
});

Advanced: Custom Language Processor

import { 
  buildFuzzyIndex, 
  BaseLanguageProcessor,
  type FuzzyFeature 
} from 'fuzzyfindjs';

class ItalianProcessor extends BaseLanguageProcessor {
  readonly language = 'italian';
  readonly displayName = 'Italiano';
  readonly supportedFeatures: FuzzyFeature[] = [
    'phonetic', 'partial-words', 'synonyms'
  ];
  
  normalize(text: string): string {
    return text.toLowerCase()
      .replace(/à/g, 'a')
      .replace(/è/g, 'e')
      .replace(/é/g, 'e')
      .replace(/ì/g, 'i')
      .replace(/ò/g, 'o')
      .replace(/ù/g, 'u');
  }
  
  getSynonyms(word: string): string[] {
    const synonyms: Record<string, string[]> = {
      'ciao': ['salve', 'buongiorno'],
      'casa': ['abitazione', 'dimora']
    };
    return synonyms[this.normalize(word)] || [];
  }
}

// Use custom processor
const italianWords = ['Ciao', 'Casa', 'Città'];
const index = buildFuzzyIndex(italianWords, {
  languageProcessors: [new ItalianProcessor()]
});

🎯 Performance Tips

1. Choose the Right Performance Mode

// For autocomplete (speed critical)
{ performance: 'fast' }

// For general search (balanced)
{ performance: 'balanced' }

// For critical searches (accuracy critical)
{ performance: 'comprehensive' }

2. Limit Features for Large Datasets

// Faster indexing and searching
{
  features: ['partial-words', 'missing-letters'],
  maxEditDistance: 1
}

3. Build Index Once, Search Many Times

// ❌ Bad: Rebuilding index on every search
function search(query) {
  const index = buildFuzzyIndex(dictionary);
  return getSuggestions(index, query);
}

// ✅ Good: Build once, reuse
const index = buildFuzzyIndex(dictionary);
function search(query) {
  return getSuggestions(index, query);
}

4. Use Appropriate Thresholds

// Strict matching (fewer, more accurate results)
{ fuzzyThreshold: 0.9 }

// Lenient matching (more results, some false positives)
{ fuzzyThreshold: 0.6 }

5. Inverted Index for Large Datasets

The library automatically uses an inverted index for datasets with 10,000+ words, providing 10-100x performance improvement:

// Automatically uses inverted index for large datasets
const largeDictionary = Array.from({ length: 100000 }, (_, i) => `Word${i}`);
const index = buildFuzzyIndex(largeDictionary);
// ✨ Inverted index automatically enabled!

// Force inverted index for smaller datasets (optional)
const index = buildFuzzyIndex(smallDictionary, {
  useInvertedIndex: true  // Manual override
});

// Or via config
const index = buildFuzzyIndex(dictionary, {
  config: {
    useInvertedIndex: true
  }
});

Performance comparison:

| Dataset Size | Classic Index | Inverted Index | Speedup | | --------------- | ------------- | -------------- | ------- | | 1,000 words | 1ms | 1ms | 1x | | 10,000 words | 5ms | 2ms | 2.5x | | 100,000 words | 50ms | 5ms | 10x | | 1,000,000 words | 500ms | 10ms | 50x |

No code changes required - the library automatically detects and uses the optimal index structure!

6. Match Highlighting

Get exact positions of matched characters for UI highlighting:

// Enable highlighting
const results = getSuggestions(index, 'app', 5, {
  includeHighlights: true
});

// Results include highlight positions
console.log(results[0].highlights);
// → [{ start: 0, end: 3, type: 'prefix' }]

// Format as HTML
import { formatHighlightedHTML } from 'fuzzyfindjs';
const html = formatHighlightedHTML(results[0].display, results[0].highlights);
// → '<mark class="highlight highlight--prefix">App</mark>lication'

Perfect for:

  • Search result highlighting
  • Autocomplete UI
  • Showing users WHY something matched

7. Search Result Caching

Automatic LRU cache for 10-100x faster repeated queries:

// Cache is enabled by default!
const index = buildFuzzyIndex(dictionary);

// First search - cache miss
getSuggestions(index, 'app', 5); // ~5ms

// Second search - cache hit!
getSuggestions(index, 'app', 5); // ~0.1ms (50x faster!)

// Check cache stats
const stats = index._cache.getStats();
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);

// Disable cache if needed
const index = buildFuzzyIndex(dictionary, {
  config: { enableCache: false }
});

// Custom cache size
const index = buildFuzzyIndex(dictionary, {
  config: { cacheSize: 200 }  // Default: 100
});

Ideal for:

  • Autocomplete (users type incrementally)
  • Search-as-you-type
  • Repeated queries

8. Index Serialization

Save and load indices for 100x faster startup:

import { serializeIndex, deserializeIndex } from 'fuzzyfindjs';

// Build index once
const index = buildFuzzyIndex(largeDictionary);

// Serialize to JSON
const serialized = serializeIndex(index);
localStorage.setItem('search-index', serialized);

// Later: Load instantly (100x faster!)
const loaded = await deserializeIndex(localStorage.getItem('search-index'));
const results = getSuggestions(loaded, 'query'); // Works immediately!

// Get serialized size
import { getSerializedSize } from 'fuzzyfindjs';
const sizeInBytes = getSerializedSize(index);
console.log(`Index size: ${(sizeInBytes / 1024).toFixed(2)} KB`);

Perfect for:

  • Server-side apps (save to disk)
  • Browser apps (save to localStorage)
  • Skipping index rebuild on startup

Performance:

  • Index building: ~250ms (10k words)
  • Serialization: ~10ms
  • Deserialization: ~5ms
  • 50-100x faster than rebuilding!

9. Batch Search API

Search multiple queries at once with automatic deduplication:

import { batchSearch } from 'fuzzyfindjs';

// Search multiple queries efficiently
const results = batchSearch(index, ['apple', 'banana', 'cherry']);
// → { apple: [...], banana: [...], cherry: [...] }

// Automatic deduplication
const results = batchSearch(index, ['app', 'app', 'ban', 'app']);
// → { app: [...], ban: [...] }  // Only 2 queries executed!

// Supports all search options
const results = batchSearch(index, ['app', 'ban'], 5, {
  includeHighlights: true,
  fuzzyThreshold: 0.8
});

// Perfect for multi-field forms
const formResults = batchSearch(index, [
  formData.name,
  formData.email,
  formData.phone
]);

Benefits:

  • Deduplicates identical queries automatically
  • Cache-friendly - repeated queries hit cache
  • Cleaner code - one call instead of multiple
  • Type-safe - full TypeScript support

Use cases:

  • Multi-field search forms
  • Batch processing
  • Server-side batch queries
  • Deduplicating search requests

10. Accent Normalization

Automatic handling of accented characters for international support:

import { buildFuzzyIndex, getSuggestions, removeAccents } from 'fuzzyfindjs';

// Utility function
removeAccents('café');   // → 'cafe'
removeAccents('José');   // → 'Jose'
removeAccents('Müller'); // → 'Muller'
removeAccents('naïve');  // → 'naive'

// Automatic in search - works bidirectionally!
const index = buildFuzzyIndex(['café', 'naïve', 'José', 'Müller']);

// Search without accents - finds accented words
getSuggestions(index, 'cafe');   // ✅ Finds 'café'
getSuggestions(index, 'naive');  // ✅ Finds 'naïve'
getSuggestions(index, 'Jose');   // ✅ Finds 'José'
getSuggestions(index, 'Muller'); // ✅ Finds 'Müller'

// Search with accents - also works!
const index2 = buildFuzzyIndex(['cafe', 'naive']);
getSuggestions(index2, 'café');  // ✅ Finds 'cafe'
getSuggestions(index2, 'naïve'); // ✅ Finds 'naive'

Supported Languages:

  • 🇫🇷 French: café, crème, naïve, résumé, château
  • 🇪🇸 Spanish: José, María, niño, señor, mañana
  • 🇩🇪 German: Müller, Köln, Zürich, Straße, Äpfel
  • 🇵🇹 Portuguese: São Paulo, açúcar, coração
  • 🇵🇱 Polish: Łódź, Kraków, Gdańsk
  • And 100+ more accented characters!

Benefits:

  • Automatic - no configuration needed
  • Bidirectional - café ↔ cafe both work
  • Preserves original - display text keeps accents
  • Zero overhead - indexed once, searched fast

Perfect for:

  • International applications
  • Multi-language search
  • User-friendly input (users can't always type accents)
  • E-commerce with international products

11. Field Weighting (Opt-In)

⚠️ This is an opt-in feature - By default, FuzzyFindJS works with simple string arrays. Field weighting is only activated when you explicitly provide the fields option.

Multi-field search with weighted scoring for better ranking:

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

// ✅ DEFAULT: Simple string array (no field weighting)
const simpleIndex = buildFuzzyIndex(['Apple iPhone', 'Samsung Phone', 'Google Pixel']);
const results1 = getSuggestions(simpleIndex, 'phone');
// Works normally with fuzzy matching

// ✅ OPT-IN: Multi-field search with objects
const products = [
  { id: 1, title: 'Apple iPhone', description: 'Smartphone with great camera', category: 'Electronics' },
  { id: 2, title: 'Apple Pie Recipe', description: 'Delicious dessert', category: 'Food' },
  { id: 3, title: 'Samsung Phone', description: 'Apple-like design', category: 'Electronics' }
];

// You MUST specify fields when indexing objects
const index = buildFuzzyIndex(products, {
  fields: ['title', 'description', 'category'],  // Required for objects
  fieldWeights: {                                 // Optional: customize weights
    title: 2.0,        // Title matches worth 2x
    description: 1.0,  // Description matches normal weight
    category: 1.5      // Category matches worth 1.5x
  }
});

// Search for "apple"
const results = getSuggestions(index, 'apple');
// Result order (by weighted score):
// 1. Apple iPhone (title match - 2x weight)
// 2. Apple Pie Recipe (title match - 2x weight)
// 3. Samsung Phone (description match - 1x weight)

// Access field data in results
console.log(results[0].fields);
// → { title: 'Apple iPhone', description: 'Smartphone...', category: 'Electronics' }

Important Notes:

  • 🔴 Required: When indexing objects, you must specify fields option
  • Optional: fieldWeights are optional (defaults to 1.0 for all fields)
  • Backwards Compatible: String arrays work exactly as before
  • No Performance Impact: Only activated when using objects with fields

Use Cases:

  • 📱 E-commerce: Product name > description > tags
  • 📄 Documents: Title > content > metadata
  • 👤 User Search: Name > email > bio
  • 🎵 Music: Song title > artist > album
  • 🏢 Companies: Company name > industry > description

Features:

  • Weighted Scoring - Boost important fields
  • Multi-Field Search - Search across object properties
  • Field Preservation - Results include all field data
  • Opt-In Only - Doesn't affect simple string arrays
  • Automatic Defaults - Unspecified fields default to 1.0

Example: Document Search

const docs = [
  { title: 'TypeScript Guide', content: 'Learn TypeScript basics', tags: 'programming' },
  { title: 'JavaScript Intro', content: 'TypeScript is a superset', tags: 'tutorial' }
];

const index = buildFuzzyIndex(docs, {
  fields: ['title', 'content', 'tags'],  // Required!
  fieldWeights: {                         // Optional
    title: 3.0,    // Title most important
    content: 1.0,  // Content normal
    tags: 2.0      // Tags important
  }
});

const results = getSuggestions(index, 'typescript');
// "TypeScript Guide" ranks first (title match with 3x weight)

12. E-Commerce: Filtering & Sorting

Built-in support for e-commerce use cases with filtering and custom sorting:

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

// Index products with multiple fields
const products = [
  { name: "Nike Air Max", brand: "Nike", price: 120, rating: 4.5, inStock: true },
  { name: "Adidas Ultraboost", brand: "Adidas", price: 180, rating: 4.8, inStock: true },
  { name: "Nike React", brand: "Nike", price: 90, rating: 4.2, inStock: false },
  { name: "Puma RS-X", brand: "Puma", price: 110, rating: 4.0, inStock: true },
];

const index = buildFuzzyIndex(products, {
  fields: ["name", "brand", "price", "rating", "inStock"],
});

// Search with filters and sorting
const results = getSuggestions(index, "nike", 10, {
  filters: {
    // Range filters (numeric fields)
    ranges: [
      { field: "price", min: 80, max: 150 },
      { field: "rating", min: 4.0 }
    ],
    // Term filters (categorical fields)
    terms: [
      { field: "brand", values: ["Nike", "Adidas"] }
    ],
    // Boolean filters
    booleans: [
      { field: "inStock", value: true }
    ]
  },
  sort: {
    // Primary sort: price ascending
    primary: { field: "price", order: "asc" },
    // Secondary sort: rating descending (tie-breaker)
    secondary: { field: "rating", order: "desc" },
    // Keep relevance as final tie-breaker (default: true)
    keepRelevance: true
  }
});

// Results are filtered and sorted
results.forEach(result => {
  console.log(result.display);           // "Nike Air Max"
  console.log(result.fields?.price);     // 120
  console.log(result.fields?.rating);    // 4.5
  console.log(result.fields?.inStock);   // true
});

Filter Types:

// Range Filter - Numeric comparisons
interface RangeFilter {
  field: string;
  min?: number;  // Minimum value (inclusive)
  max?: number;  // Maximum value (inclusive)
}

// Term Filter - Categorical matching
interface TermFilter {
  field: string;
  values: any[];           // Values to match
  operator?: "AND" | "OR"; // Default: "OR"
}

// Boolean Filter - True/false matching
interface BooleanFilter {
  field: string;
  value: boolean;
}

Sorting Options:

interface SortConfig {
  primary: SortOption;      // Primary sort field
  secondary?: SortOption;   // Tie-breaker
  keepRelevance?: boolean;  // Use relevance as final tie-breaker (default: true)
}

interface SortOption {
  field: string;
  order: "asc" | "desc";
  type?: "number" | "string" | "date";  // Auto-detected if not specified
}

Real-World E-Commerce Example:

// Product catalog search
const catalog = [
  { name: "iPhone 15 Pro", category: "Phones", price: 999, rating: 4.8, inStock: true },
  { name: "Samsung Galaxy S24", category: "Phones", price: 899, rating: 4.7, inStock: true },
  { name: "iPad Air", category: "Tablets", price: 599, rating: 4.6, inStock: false },
  { name: "MacBook Pro", category: "Laptops", price: 1999, rating: 4.9, inStock: true },
];

const index = buildFuzzyIndex(catalog, {
  fields: ["name", "category", "price", "rating", "inStock"],
});

// User searches "phone" with filters
const results = getSuggestions(index, "phone", 10, {
  filters: {
    ranges: [{ field: "price", max: 1000 }],
    terms: [{ field: "category", values: ["Phones"] }],
    booleans: [{ field: "inStock", value: true }]
  },
  sort: {
    primary: { field: "price", order: "asc" }
  }
});

// Returns: Samsung Galaxy S24 ($899), iPhone 15 Pro ($999)
// Filtered by: price ≤ $1000, category = Phones, inStock = true
// Sorted by: price ascending

Use Cases:

  • 🛒 Product Search - Filter by price, category, brand, availability
  • 📱 Marketplace - Sort by price, rating, popularity
  • 🏨 Booking Sites - Filter by price range, ratings, availability
  • 🍔 Food Delivery - Filter by cuisine, price, rating, delivery time
  • 🏠 Real Estate - Filter by price, bedrooms, location

13. Stop Words Filtering

Filter common words that add noise to search results:

import { buildFuzzyIndex, getSuggestions, DEFAULT_STOP_WORDS } from 'fuzzyfindjs';

// Enable stop words filtering
const index = buildFuzzyIndex(dictionary, {
  config: {
    stopWords: ['the', 'a', 'an', 'is', 'at', 'on', 'in'],
    enableStopWords: true
  }
});

// Search with automatic filtering
getSuggestions(index, 'the hospital');
// Searches for: "hospital" (stop word "the" removed)

getSuggestions(index, 'a school in the city');
// Searches for: "school city" (stop words filtered)

// Use built-in stop words for any language
const index2 = buildFuzzyIndex(dictionary, {
  config: {
    stopWords: DEFAULT_STOP_WORDS.english,  // 38 common English stop words
    enableStopWords: true
  }
});

// Available languages
DEFAULT_STOP_WORDS.english   // the, a, an, is, at, on, in, to, for...
DEFAULT_STOP_WORDS.german    // der, die, das, ein, eine, und, oder...
DEFAULT_STOP_WORDS.spanish   // el, la, los, las, un, una, de, en...
DEFAULT_STOP_WORDS.french    // le, la, les, un, une, de, à, et...

Benefits:

  • Better Quality - Focus on meaningful words
  • Faster Search - Fewer words to process
  • Case Preserved - Original text case maintained
  • Multi-language - Built-in stop words for 4 languages
  • Safe Fallback - Returns original query if all words are stop words

Use Cases:

  • 🏥 Medical Search: "the hospital" → "hospital"
  • 📚 Document Search: "a guide to programming" → "guide programming"
  • 🏢 Business Search: "the company in london" → "company london"
  • 🔍 General Search: Remove noise from user queries

Manual Filtering:

import { filterStopWords, isStopWord } from 'fuzzyfindjs';

// Filter stop words from any text
filterStopWords('the quick brown fox', ['the', 'a']);
// → 'quick brown fox'

// Check if a word is a stop word
isStopWord('the', DEFAULT_STOP_WORDS.english);
// → true

13. Word Boundaries

Precise matching with word boundary detection and wildcard support:

import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';

// Enable word boundaries for more precise results
const index = buildFuzzyIndex(dictionary, {
  config: {
    wordBoundaries: true
  }
});

// Without word boundaries (default)
const index1 = buildFuzzyIndex(['cat', 'category', 'scatter', 'concatenate']);
getSuggestions(index1, 'cat');
// Matches: "cat", "category", "scatter", "concatenate" (all contain 'cat')

// With word boundaries
const index2 = buildFuzzyIndex(['cat', 'category', 'scatter', 'concatenate'], {
  config: { wordBoundaries: true }
});
getSuggestions(index2, 'cat');
// Matches: "cat", "category" (starts with 'cat')
// Doesn't match: "scatter", "concatenate" (cat in middle)

// Wildcard support
getSuggestions(index, 'cat*');
// Matches: "cat", "cats", "category"

getSuggestions(index, 'app*tion');
// Matches: "application", "appreciation"

Benefits:

  • More Precise - Reduce false positives
  • Wildcard Support - Flexible pattern matching with *
  • User Control - Choose exact vs substring matching
  • Professional - Standard search engine behavior
  • Backwards Compatible - Disabled by default

Use Cases:

  • 🔍 Exact Search: Find "cat" without matching "scatter"
  • 📝 Autocomplete: Match word prefixes only
  • 🎯 Pattern Matching: Use wildcards for flexible queries
  • 📚 Dictionary Search: Match whole words only

Manual Boundary Checking:

import { isWordBoundary, matchesAtWordBoundary, matchesWildcard } from 'fuzzyfindjs';

// Check if position is at word boundary
isWordBoundary('hello world', 6); // → true (after space)
isWordBoundary('hello', 2);       // → false (middle of word)

// Check if match is at word boundary
matchesAtWordBoundary('the cat sat', 4, 3); // → true ('cat')
matchesAtWordBoundary('scatter', 1, 3);     // → false ('cat' in middle)

// Wildcard matching
matchesWildcard('category', 'cat*');     // → true
matchesWildcard('application', 'app*');  // → true

14. Data Indexing Utilities

Easily extract searchable words from unstructured data sources like HTML, JSON, or text dumps:

import { dataToIndex, createFuzzySearch } from 'fuzzyfindjs';

// ✅ Simple text
const text = "The quick brown fox jumps over the lazy dog.";
const words = dataToIndex(text, {
  minLength: 3,
  stopWords: ['the', 'a', 'an']
});
// → ['quick', 'brown', 'fox', 'jumps', 'over', 'lazy', 'dog']

// ✅ HTML content (strips tags automatically)
const html = `
  <html>
    <body>
      <h1>Welcome to Our Store</h1>
      <p>We sell <strong>amazing</strong> products!</p>
    </body>
  </html>
`;
const htmlWords = dataToIndex(html, { format: 'html' });
// → ['welcome', 'our', 'store', 'we', 'sell', 'amazing', 'products']

// ✅ JSON data (extracts string values only)
const products = [
  { name: 'iPhone 15', category: 'Electronics', price: 999 },
  { name: 'MacBook Pro', category: 'Computers', price: 2499 }
];
const jsonWords = dataToIndex(JSON.stringify(products), { format: 'json' });
// → ['iphone', '15', 'electronics', 'macbook', 'pro', 'computers']

// ✅ Base64 encoded content
const base64 = btoa("Hello World");
const base64Words = dataToIndex(base64, { format: 'base64' });
// → ['hello', 'world']

// ✅ Direct integration with fuzzy search
const htmlContent = "<h1>Coffee</h1><p>Kaffee is German for coffee</p>";
const dictionary = dataToIndex(htmlContent, { format: 'html' });
const search = createFuzzySearch(dictionary);

search.search('kaffee');
// → [{ display: 'kaffee', score: 1.0, ... }]

Options:

interface DataToIndexOptions {
  minLength?: number;        // Minimum word length (default: 2)
  splitWords?: boolean;      // Split into words (default: true)
  stopWords?: string[];      // Remove stop words (default: false)
  overlap?: number;          // Chunk overlap (default: 0)
  chunkSize?: number;        // Chunk size (default: 0 = no chunking)
  splitOn?: 'word' | 'sentence' | 'paragraph';  // Split strategy
  format?: 'string' | 'html' |