Spelling, "did you mean?", and fuzzy search with Whoosh

Typo-tolerant search in pure Python — no server · by Priya Sundaram
TL;DR. Users mistype. Whoosh gives you three tools to handle it: word suggestions (corrector.suggest), automatic query correction that powers a "did you mean?" link (searcher.correct_query), and fuzzy matching that finds results even for a misspelled query (FuzzyTerm or the word~2 syntax). Every snippet below was run against whoosh3 3.16.4 before publishing; the outputs shown are the real outputs.

Nobody types cleanly. Someone searches serch pythonn and, if your search is exact-match only, gets zero results and bounces. The fix is typo tolerance, and it's one of the areas where a "small" library like Whoosh genuinely holds its own — you get suggestions, query correction, and fuzzy matching without standing up a server or a language model.

1. Turn on spelling for a field

Whoosh can build its correction data straight from the words already in your index — no external dictionary needed. Add spelling=True to the text fields you want suggestions from. That tells Whoosh to keep a word graph so it can find close matches later.

from whoosh.fields import Schema, TEXT, ID
from whoosh.index import create_in
import os

schema = Schema(
    title=TEXT(stored=True, spelling=True),   # spelling=True enables the corrector
    path=ID(stored=True),
)

os.makedirs("idx", exist_ok=True)
ix = create_in("idx", schema)

w = ix.writer()
for i, text in enumerate([
    "python programming tutorial",
    "advanced python search engine",
    "machine learning with python",
    "search algorithms explained",
    "the whoosh library documentation",
    "fast indexing techniques",
]):
    w.add_document(title=text, path=str(i))
w.commit()
Adding spelling to an existing index? Set spelling=True in the schema and re-index once so the word graph is built. There's no need to change your query code — the corrector reads the graph that indexing produced.

2. Word suggestions

Ask a searcher for a corrector on a field, then call suggest(). It ranks candidates by edit distance and by how common the word is in your index, so suggestions reflect your data — not a generic dictionary.

with ix.searcher() as s:
    corr = s.corrector("title")
    print(corr.suggest("pythonn", limit=3))   # -> ['python']
    print(corr.suggest("serch", limit=3))      # -> ['search']

Real output: ['python'] and ['search'].

3. "Did you mean?" — correcting a whole query

Suggesting a single word is nice, but what you usually want is to fix the entire query and show the classic "Did you mean: …?" prompt. That's searcher.correct_query(query, query_string). It returns a correction object with the fixed query string and a ready-made HTML rendering that highlights what changed.

from whoosh.qparser import QueryParser
from whoosh.highlight import HtmlFormatter

with ix.searcher() as s:
    qp = QueryParser("title", ix.schema)
    user_text = "serch pythonn"
    q = qp.parse(user_text)

    corrected = s.correct_query(q, user_text)
    print(corrected.string)   # -> 'search python'

    # HTML you can drop into a "did you mean?" link
    print(corrected.format_string(HtmlFormatter()))
    # -> search python

Real output: the corrected string is search python, and format_string wraps each fixed term in a <strong> so you can style it in your "did you mean?" banner.

A common pattern: run the user's query first; if it returns few or no hits and corrected.query != q, show a "Did you mean search python?" link that re-runs with the corrected query.

4. Fuzzy matching — find results despite the typo

Correction proposes a new query; fuzzy matching just matches misspelled terms directly against indexed words within an edit distance. Use it when you'd rather return results immediately than ask the user to click a suggestion.

Programmatically with FuzzyTerm

from whoosh.query import FuzzyTerm

with ix.searcher() as s:
    q = FuzzyTerm("title", "pythonn", maxdist=2)   # allow up to 2 edits
    results = s.search(q)
    print([h["title"] for h in results])
    # -> ['python programming tutorial',
    #     'machine learning with python',
    #     'advanced python search engine']

Real output: pythonn (a typo) still finds all three python documents.

In query syntax with the fuzzy plugin

If you accept raw query strings from users, enable FuzzyTermPlugin and let them (or you) append ~N to a term to allow up to N edits. Add ~N/P to also require the first P characters to match — a good way to keep fuzzy matching fast and precise.

from whoosh.qparser import QueryParser, FuzzyTermPlugin

with ix.searcher() as s:
    qp = QueryParser("title", ix.schema)
    qp.add_plugin(FuzzyTermPlugin())

    q = qp.parse("serch~2")            # up to 2 edits
    print(repr(q))                      # FuzzyTerm('title', 'serch', ... maxdist=2, prefixlength=0)
    print([h["title"] for h in s.search(q)])
    # -> ['search algorithms explained', 'advanced python search engine']

    # require the first character to match: fewer false positives, faster
    q2 = qp.parse("serch~2/1")          # maxdist=2, prefixlength=1

Real output: serch~2 matches both documents containing search.

Which one should I use? Use fuzzy matching when you want results returned immediately despite typos (search-as-you-type, tolerant lookups). Use query correction / "did you mean?" when you'd rather keep exact matching but offer a one-click fix. Many apps do both: return fuzzy results and show a correction link.

Performance notes

Get started

pip install whoosh3

Same import path (import whoosh), Python 3.9–3.14. Whoosh3 is the actively-maintained continuation of Whoosh. If you build something with it, or hit a rough edge, open an issue — I read every one. Star the repo if it helps.

Related guides


The suggestions, corrected query string, and fuzzy search results above came from running the exact code in this guide against whoosh3 3.16.4 before publishing. "Whoosh" is the pure-Python search library originally by Matt Chaput; this is an actively maintained continuation.