Sociologix
← Latest in AI

Developer tools · · 6 min read

Fuse.js: add typo-tolerant search to a business FAQ

Find reviewed answers despite misspellings, keep internal records out of results and test the handoff when a question has no useful match.

By Sociologix Editorial

Official GitHub preview of krisk/Fuse, identifying the JavaScript fuzzy-search repository and its publisher avatar.
GitHub’s actual preview of the official Fuse.js repository, retrieved October 22, 2026. Counters are a dated publisher snapshot, not evaluation results.Image source ↗

Help a misspelled question reach a reviewed answer

A visitor searching for “uplod” should have a chance to find your file-upload instructions. A small FAQ lookup can return an existing answer and its source before a customer-care worker needs to generate anything. The key is deciding when to offer a result and when to show a useful handoff.

This AI-assisted editorial guide tests Fuse.js against invented FAQ records. The demonstration runs locally with no model, embeddings, customer information or connected helpdesk. It checks eight cases, including a misspelling and an internal-only record. Fuse.js supplies character-based retrieval; it is not an AI model and does not decide whether an answer is correct.

1. Pin the package and keep the experiment separate

The official krisk/Fuse repository publishes the fuse.js package under Apache-2.0. We checked and ran version 7.5.0 with Node.js 24.19.0 on October 22, 2026. Pinning the package keeps this example separate from the prerelease features shown in parts of the current documentation. [1]

Use these PowerShell commands in a new scratch directory. Save both JavaScript blocks below, in order, as faq-demo.mjs before running the last command. Internet access is needed for installation; the example itself requires no credentials or external service. This installs a tutorial dependency, not a production support system.

mkdir faq-search-demo
cd faq-search-demo
npm install --ignore-scripts "fuse.js@7.5.0"
# Save both JavaScript blocks below as faq-demo.mjs first.
node faq-demo.mjs

2. Index titles and reviewed aliases

The records below contain a title, an answer and an illustrative help-page URL. The URLs are placeholders for your own real articles, not working endpoints supplied by this tutorial. Aliases provide reviewed vocabulary such as pricing, cost and estimate. They help connect a short query to a topic without inventing a new policy.

The title has more weight than the aliases, while ignoreLocation removes the penalty for a term appearing later in a field. includeScore exposes the relevance score. threshold:0.3 is a starting choice for this fixture, not a validated setting for every business. Tune these options against representative questions. [2]

The helper filters for approved, public records before constructing the index. In a browser implementation, perform that selection before delivering data to the browser; hiding a result after downloading private records does not protect them. Our synthetic internal row exists only to exercise this local filter.

import assert from 'node:assert/strict';
import Fuse from 'fuse.js';

// Invented fixtures, not a company's published policies.
const faqs = [
  { id: 'quote', title: 'Request a project quote',
    aliases: 'pricing cost estimate',
    answer: 'Send your scope for a reviewed estimate.',
    url: '/help/quote', approved: true, audience: 'public' },
  { id: 'files', title: 'Upload project files',
    aliases: 'documents attachments upload',
    answer: 'Use the secure project workspace for project files.',
    url: '/help/files', approved: true, audience: 'public' },
  { id: 'status', title: 'Check project progress',
    aliases: 'milestones status timeline',
    answer: 'Check the agreed milestones in your project workspace.',
    url: '/help/status', approved: true, audience: 'public' },
  { id: 'internal', title: 'Internal margin sheet',
    aliases: 'internal margins', answer: 'SYNTHETIC INTERNAL RECORD',
    url: '/internal/margins', approved: true, audience: 'internal' }
];

function createFaqSearch(records) {
  const publicFaqs = records.filter(
    row => row.approved && row.audience === 'public'
  );
  const fuse = new Fuse(publicFaqs, {
    keys: [
      { name: 'title', weight: 0.7 },
      { name: 'aliases', weight: 0.3 }
    ],
    includeScore: true,
    threshold: 0.3,
    ignoreLocation: true
  });
  return query => {
    if (typeof query !== 'string') return [];
    const text = query.trim();
    if (text.length < 3 || text.length > 80) return [];
    return fuse.search(text, { limit: 2 }).map(({ item, score }) => ({
      id: item.id, title: item.title, answer: item.answer,
      url: item.url, score
    }));
  };
}

3. Test matches and deliberate refusals

Append this block and run the file. It checks an exact title, the misspelling uplod, a reviewed pricing alias, blank input, a two-character query, an unrelated phrase, the internal topic and an oversized query. The last five checks require an empty result rather than a forced answer.

The wrapper’s three-to-eighty-character rule is an application decision. In this package version, a blank query can return the whole collection, so the explicit trim and length checks matter. The search call limits the result list to two; it does not certify either result as useful. [3]

const search = createFaqSearch(faqs);
const cases = [
  ['exact title', () => assert.equal(
    search('Upload project files')[0]?.id, 'files')],
  ['misspelling', () => assert.equal(search('uplod')[0]?.id, 'files')],
  ['reviewed alias', () => assert.equal(search('pricing')[0]?.id, 'quote')],
  ['blank query', () => assert.deepEqual(search('   '), [])],
  ['short query', () => assert.deepEqual(search('up'), [])],
  ['unrelated query', () => assert.deepEqual(search('lunar geology'), [])],
  ['internal record', () => assert.deepEqual(search('internal margins'), [])],
  ['oversized query', () => assert.deepEqual(search('x'.repeat(81)), [])]
];
for (const [name, check] of cases) {
  check();
  console.log('PASS:', name);
}
console.log('Example result:', JSON.stringify(search('uplod')));

4. Read the observed result without calling it confidence

All eight checks passed locally. The exact title and misspelling returned the files record first; pricing returned the quote record. Blank, short, unrelated, internal-topic and oversized inputs returned no results. These observations cover this four-record fixture, not real customer traffic or a production accuracy benchmark.

The uplod result had a reported score of approximately 0.395. That is not a 60.5% chance of correctness. Fuse.js combines text matching with key weights and field-length normalization; the final score is not interchangeable with the configured fuzziness threshold. Lower scores rank better, but relevance still needs evaluation against the questions your customers ask. [2]

A zero-result test is useful, but weak positive matches matter too. Add similar-looking topics and questions that use different business vocabulary. Check which answer ranks first, whether multiple results are ambiguous and whether your handoff remains available. Repeat the fixture whenever FAQ wording or search settings change.

5. Connect retrieval to customer care deliberately

Show the reviewed answer with a descriptive link to its real help article. When nothing useful appears, offer a contact or service-request path and explain what information to include. A search hit should not approve a price, change a project agreement or trigger a customer-account action.

Fuzzy search is useful for spelling variations; matching meaning is a different problem. An unfamiliar phrase may need additional reviewed aliases, a semantic retrieval layer or a person. Decide which approach to add from observed misses rather than assuming a looser character threshold will understand intent. [4]

If an AI worker summarizes retrieved answers, keep their source identifiers and do not let the model fill missing policy details. Maintain a review owner and update date for each FAQ, verify the actual links and measure answer selection and handoffs on a representative evaluation set. The small local search test is a starting component, not proof that the full support journey works.

Sources & further reading

  1. Fuse.js 7.5.0 package metadata, repository and license
  2. Fuse.js documentation: fuzzy search options and scoring
  3. Fuse.js 7.5.0 search implementation, limits and blank-query behavior
  4. Fuse.js: character matching versus semantic search

Give customer care a stronger answer foundation

Sociologix can help organize approved FAQs, build retrieval and AI-assisted support workflows, and connect unanswered questions to a useful service-request path. Bring your common questions and one support journey you want to improve.

Talk to Sociologix