🔍 Whoosh is back: reviving pure-Python full-text search

by Priya Sundaram · 14 July 2026

Also worth reading:

· Whoosh ImportError on modern Python — the fixcannot import name 'Iterable', No module named 'imp': why old Whoosh breaks on Python 3.10+ and the one-line fix.
· Is Whoosh still maintained? (2026 status) — which fork is active, Python 3.14 support, and how to upgrade in one line.
· Search your files from the command line — a hands-on tour of the whoosh CLI: grep, but ranked by relevance.
· Add full-text search to a Flask app — a step-by-step, pure-Python tutorial (indexing, ranking, highlighting).
· Add full-text search to a FastAPI app — a /search endpoint with Pydantic models, plus how to run Whoosh safely from async routes.
· Add full-text search to a Django app — index your models and search them, no PostgreSQL or external service required.
· Whoosh vs SQLite FTS5 — pure Python vs a built-in C extension, with real benchmark numbers.
· Migrating from SQLite FTS5 to Whoosh — a runnable, step-by-step guide: export your rows, mirror the schema, match FTS5 stemming.
· Whoosh analyzer recipes — copy-paste tokenizers: accent folding, stemming, camelCase, tags, and n-gram search.
· Whoosh vs Tantivy (tantivy-py) — pure Python vs Rust bindings: install story, speed, and portability.
· Whoosh vs Elasticsearch — when an embedded library beats running a JVM search cluster.
· Use Whoosh as a LangChain retriever — a copy-paste pure-Python BM25 retriever for RAG and hybrid search.
· Use Whoosh as a LlamaIndex retriever — the first-class whoosh.llamaindex BM25 retriever for RAG and QueryFusionRetriever hybrid search.
· Full-text search over a pandas DataFrame — index df.to_dict("records"), rank with BM25, map hits back to rows, filter numeric columns.
· Faceted search & result counts — group by category/brand, bucket prices with RangeFacet, drill down on a clicked facet.
· Highlighted search-result snippets — Google-style excerpts with matched words wrapped in <mark>, fragment control, non-stored fields.
· Keep a Whoosh index in sync — incremental add / update_document / delete_by_term driven by modification time, so you never rebuild.
· Whoosh as an MCP server for AI agents — expose search and fetch tools to an LLM agent over the Model Context Protocol, backed by a local pure-Python BM25F index.

Whoosh is a full-text search library for Python: ranked results, a real query language, faceting, highlighting, and spell-correction — all in pure Python, with no compiler, no server, and no native dependencies. pip install and go.

It was widely used, then went unmaintained. It was revived once as whoosh-reloaded, then went quiet again. I'm picking it back up, and this is the first release under new maintenance.

Update (3.15–3.30) — a grown-up CLI, full public-API type hints, and CJK search. The revival has kept a steady release cadence. The bundled whoosh command matured into a proper grep-style search tool: paging (--page), machine-readable output (--jsonl/--ndjson), -l/--files-with-matches (with -0 for null-separated paths), --or matching, --color, --min-score, --max-size, --follow-symlinks, and a --dry-run index preview all landed (3.16–3.25). The whole public API now carries PEP 484 type hints — sorting, highlighting, classification, compound/base queries, and the reader, writer, and spelling surfaces — so your editor autocompletes Whoosh and mypy checks your calls, with the heavy imports kept behind TYPE_CHECKING so there is zero import-time cost (3.25–3.30). And 3.28.0 added first-class CJK (Chinese / Japanese / Korean) text support via a new CJKFilter/CJKAnalyzer. Along the way, real reliability fixes shipped in the multiprocessing writer, phrase highlighting, and corrupt-index handling — every change with a regression test. pip install -U whoosh3.

Update (3.12–3.14) — a new CLI report, two safer APIs, and a nested-search fix. The revival has kept shipping. whoosh stats --top-terms FIELD now lists a field's most frequent indexed terms — a reviewed community contribution from @Nitjsefnie (3.12.0). On the correctness side, multiprocessing writes to non-shared storage no longer silently discard documents, and a handful of query-parser and date edge cases were fixed (3.12.x). Two new opt-in APIs make common footguns safe: AsyncWriter.wait() gives you a race-free way to block on the background commit thread and surface any error instead of losing buffered documents (3.13.0), and Phrase(..., degrade=True) lets a quoted query gracefully fall back to an AND-of-terms on fields that store no positions, instead of raising — handy when you feed user input straight into a parser over n-gram fields (3.14.0). Finally, NestedParent no longer silently drops results when the index contains a child document with no parent above it, a long-standing bug in nested search (3.14.1). Every one of these landed with a regression test. pip install -U whoosh3.

Update (3.11.x) — Python 3.14 and two scoring/reader fixes. Whoosh now runs on Python 3.10 through 3.14, with the latest stable CPython in CI (3.11.0). Two correctness fixes followed, each with a regression test: boosts on multi-term queries (Prefix, Wildcard, FuzzyTerm, TermRange) are now actually applied to the score instead of being silently dropped (3.11.6); and IndexReader.field_terms() no longer raises OverflowError or yields garbage on NUMERIC/DATETIME fields — it now returns exactly the distinct values you indexed (3.11.7). pip install -U whoosh3.

Update (3.10.0) — first community feature merged. whoosh search --sort-by score|mtime lets the CLI order results by relevance (the default) or by file modification time, newest first; --count still reports the true total regardless of sort order. This was the first community feature contribution to the revived project, from @abhiramvsmg — exactly the kind of contribution the good-first-issue backlog exists to invite.

Update (3.9.0) — the CLI plays nicely with pipes. whoosh search --no-highlight prints a plain, grep-friendly slice of each match with no UPPERCASED markup — ideal when you're piping results into another tool — and --snippet-chars N lets you set how much context each result shows (default 200), for both text and JSON output. The whoosh command also now prints the project home in its --help and --version output, so the docs and issue tracker are one glance away. pip install -U whoosh3.

Update (3.8.x) — four real reliability fixes. A revival is only worth trusting if the bugs actually get fixed, so this run went after correctness rather than features. whoosh search --field NAME (a reviewed community contribution) lets you restrict a query to specific fields (3.8.0). Then three data-safety fixes, each with a regression test: a memory-mapped compound segment no longer leaks a file descriptor on close — the "too many open files" trap for long-running servers (3.8.1); AsyncWriter no longer silently swallows a failure on its background commit thread and drop your buffered documents — it now records the exception and releases the lock (3.8.2); and a commit() that raises partway through no longer leaves the index write-locked and effectively read-only until someone clears the stale lock by hand (3.8.3). These are exactly the kinds of latent, hard-to-reproduce bugs that make a library feel unmaintained — now closed. pip install -U whoosh3.

Update (3.5.0) — the CLI grew up. The bundled whoosh command now speaks to your shell scripts as fluently as to you. whoosh search --json emits a machine-readable array (path, score, snippet, title) you can pipe straight into jq; --count prints just the number of matches for if-tests and pipelines; --fields trims the output to the stored fields you care about; and --limit now rejects nonsensical values with a clear error instead of silently misbehaving. There's also a new static-site search example that indexes a folder of Markdown/RST/text files and searches them with highlighted snippets — a server-free way to add search to a static site or ship an index next to a desktop app. Most of these landed as reviewed community contributions, which is exactly the sign of life a revived project wants. pip install -U whoosh3, then whoosh search --help.

Update (3.3.1): fixed a long-standing bug where large in-memory (RamStorage) indexes could fail to commit with NameError: <name>.run once the posting pool spilled sorted run files — reported across both the original tracker and the whoosh-reloaded fork. In-memory runs are now retained correctly; disk-backed indexes were never affected. Comes with a regression test that forces multiple spills and verifies the committed index is searchable.

Update (3.3.0) — search a folder from your terminal. Whoosh now installs a whoosh command, so you can index and search a directory of notes, docs, or code without writing any Python: whoosh index ~/notes then whoosh search "ranked query". Think of it as a ranked grep — BM25-scored results with highlighted snippets, incremental re-indexing (only changed files are touched), file-type filters, and optional HTML output. It's a small, fully-tested wrapper over the same public API, so anything the CLI does you can also script. pip install -U whoosh3, then whoosh --help. See the command-line reference.

Update (3.0.3): the natural-language date parser now accepts ISO-8601 dates (e.g. created:2023-05-17) instead of choking on the - separators, so DateParserPlugin works with the date format most apps already store. This release also adds a compatibility test suite that guards the exact API surface downstream projects rely on, so future changes can't silently break them. pip install -U whoosh3.

Update (3.0.2): another patch release, fixing two real correctness bugs. First: a filter=/mask= set was silently ignored whenever a search also had a time limit, so a filtered, time-limited search returned every matching document. Second: sorting or faceting by a column field returned scrambled results after documents were added through a BufferedWriter. Both come with regression tests. pip install -U whoosh3.

Update (3.0.1): a patch release fixes a real correctness bug from the upstream backlog — NumericRange with an open lower bound could match every document instead of the intended range (an underflow in the trie-range splitter that hit unsigned NUMERIC fields). It's fixed with an exhaustive regression test. pip install -U whoosh3.

What's new in the 3.0.0 revival

Try it in 30 seconds

from whoosh.fields import Schema, TEXT, ID
from whoosh.filedb.filestore import RamStorage
from whoosh.qparser import QueryParser

ix = RamStorage().create_index(Schema(path=ID(stored=True), body=TEXT))
w = ix.writer(); w.add_document(path="/a", body="the quick brown fox"); w.commit()
with ix.searcher() as s:
    q = QueryParser("body", ix.schema).parse("quick fox")
    print([hit["path"] for hit in s.search(q)])   # ['/a']

Prefer the terminal? Search a folder of files with the bundled whoosh command — no Python required:

pip install whoosh3
whoosh index ~/notes          # build an index (incremental on re-run)
whoosh search "ranked query"  # BM25-scored hits with highlighted snippets

Install it with pip install whoosh3. Or don't install anything and run the live browser demo first.

Why pure-Python search still matters in 2026

The obvious question: why not just use a C extension like SQLite FTS5, or a managed service? Sometimes you should — and the benchmark in the repo is honest about where FTS5 wins. But there's a large middle ground where you want real ranked search inside a Python process: no extra service to run, no native build step in your Docker image or serverless function, full programmability of analyzers and scoring, and the ability to run the exact same code on your laptop, your server, PyPy, or WebAssembly. That's the niche Whoosh has always filled, and it's why the library kept getting used long after it stopped being maintained.

Credit where it's due

Whoosh was created by Matt Chaput and later carried by the whoosh-reloaded maintainers (Sygil-Dev). This continuation preserves their copyright and BSD-2-Clause license. Thank you.

Issues and PRs are welcome — I aim to respond promptly and kindly.

⭐ Star Whoosh on GitHub  ·  Join the discussion  ·  Live demo