Back to blog

ZBSearch v4.0.0: Come for the speed, stay for the quality.

A suggestions API, non-blocking indexing, chunked persistence - and a fresh head-to-head against Orama on speed, memory, quality, and index size.

Michele RivaAugust 11, 2026

ZBSearch v4.0.0 is out. It ships schema-less databases, a new suggestions API, non-blocking batch indexing, chunked persistence for large indexes, and a pile of smaller fixes.

If you only remember one thing: ZBSearch 4.0.0 is faster than Orama on every search, indexing, and vector workload - while using less memory, shipping smaller indexes, and returning more relevant results.

ZBSearch is a fork of Orama maintained by the original Orama team, and it's 100% backward compatible with Orama: migrating is a one-line import change.

By losing access to Orama, we lost over 10.5k GitHub stars. Help us get them back by giving us a star!

Search throughput: the headline numbers

Same dataset, same queries, same Node.js process for both engines. ZBSearch is faster on every search workload, from simple prefix searches to geosearch with filters:

How much faster ZBSearch 4.0.0 answers each workload vs Orama 3.1.18
Geosearch + filters

+222%

Geosearch (radius)

+217%

Plain full-text

+151%

Complex query + filters

+94%

Typo-tolerant

+81%

Facets + filters

+67%

Exact match

+56%

Facets

+38%

Field boosting

+30%

Prefix search

+26%

Filters

+20%

Results pinning

+8%

ZBSearch's throughput advantage per workload (Δ% in ops/s). 1,512 records, Node v24.16.0, Apple Silicon, August 11, 2026.

In absolute terms, a plain full-text search runs at 83,980 ops/s in ZBSearch versus 33,510 ops/s in Orama, and typo-tolerant search at 12,860 ops/s versus 7,100 ops/s. Both of those sit on the hot path of every keystroke in a search box.

Indexing, updates, and deletes

v4's insert path is built around an explicit postings-list inverted index. Indexing the full 1,512-document dataset:

Indexing 1,512 documents with insertMultiple (median latency)
ZBSearch 4.0.0

39.94 ms

Best

Orama 3.1.18

59.73 ms

1.5× slower

  • ZBSearch
  • Other engines

Median wall-clock time to index the full dataset. Lower is better.

One-by-one inserts show the same shape (40.23 ms vs 68.66 ms, 41% faster), and removing 100 documents takes 39.23 ms in ZBSearch versus 68.39 ms in Orama. If you rebuild indexes in CI or at boot, that's roughly 40% off your indexing time.

Memory and serialized index size

The postings-list index pays off a second time here: it also stores less. After indexing the same dataset in isolated, GC-controlled Node.js processes:

Memory footprint after indexing (RSS delta)
ZBSearch 4.0.0

12.20 MB

Best

Orama 3.1.18

22.27 MB

1.8× larger

  • ZBSearch
  • Other engines

Resident set size increase over a GC'd baseline after building the index. Lower is better.

Heap delta shows the same trend: 11.68 MB vs 14.69 MB (20% lower). And when you persist the index - to ship it to a browser, an edge worker, or object storage - delta-encoded postings make the difference even bigger:

Serialized index size (JSON)
ZBSearch 4.0.0

2.88 MB

Best

Orama 3.1.18

4.98 MB

1.7× larger

  • ZBSearch
  • Other engines

save() output for the same indexed dataset. Lower is better.

42% smaller serialized indexes means 42% less to store, transfer, and parse on every cold start.

Search quality, measured

Speed is easy to game - return worse results faster. So we also measure ranking quality on standard BEIR collections (SciFact, NFCorpus, ArguAna) with official relevance judgments, using exact trec_eval semantics. Every engine runs in its best relevance configuration using only features its own ecosystem ships.

Ranking quality: macro-average nDCG@10 across SciFact, NFCorpus, ArguAna
ZBSearch (BM25)

0.453

Best

Lunr

0.425

1.1× worse

MiniSearch

0.305

1.5× worse

Orama

0.222

2.0× worse

FlexSearch

0.168

2.7× worse

Fuse.js

0.066

6.9× worse

  • ZBSearch
  • Other engines

BEIR test collections with official qrels, trec_eval semantics. Each engine in its best own-ecosystem relevance configuration. Higher is better. MiniSearch and Fuse.js exceeded the 5-minute query budget on ArguAna and score 0 there.

ZBSearch's default BM25 configuration scores 0.453 macro nDCG@10 - more than twice Orama's 0.222 - while answering queries 3× faster (8.8 ms/query vs 27.8 ms/query, averaged over all judged queries). On SciFact, ZBSearch's 0.675 nDCG@10 lands above the published Lucene BM25 reference of 0.665.

This is the core of the pitch: ZBSearch returns more relevant results and returns them faster, so the usual speed-versus-quality trade-off simply never comes up. The optional QPS and PT15 algorithms trade relevance for raw throughput and index size, and score lower on BEIR; the benchmarks page has the full per-dataset tables for every engine and configuration.

New in v4.0.0

Schema-less databases

The schema option is now optional. Call create() with no arguments and ZBSearch infers the type of every property the first time it sees it, indexing documents on the fly - nested objects, arrays, and geopoints included:

import { create, insert, search } from 'zbsearch'

const db = create()

await insert(db, { title: 'The Godfather', year: 1972, cast: { director: 'Coppola' } })
// inferred schema: { title: 'string', year: 'number', cast: { director: 'string' } }

await search(db, { term: 'godfather' })

Inference stays predictable: types lock the first time a property is seen (a later year: "1972" is rejected exactly as with a declared schema), inferred schemas survive save/load round-trips, and vector properties still need an explicit declaration since an embedding is indistinguishable from a plain number[]. The create docs cover the full inference rules.

Suggestions API

suggest() returns ranked query completions straight from the index - no more deduping full search results to build an autocomplete dropdown:

import { suggest } from 'zbsearch'

const result = suggest(db, { term: 'noise can' })
// {
//   count: 2,
//   suggestions: [
//     { suggestion: 'noise cancelling', score: 4.2, count: 3 },
//     { suggestion: 'noise cancellation', score: 1.1, count: 1 }
//   ]
// }

Read more in the autocomplete docs.

Non-blocking indexing

insertMultipleAsync() indexes in batches and yields to the event loop between them, with progress callbacks - so a big index build doesn't freeze your UI or starve your server's request handlers:

await insertMultipleAsync(db, docs, {
  batchSize: 500,
  onProgress: ({ processed, total }) => updateProgressBar(processed / total),
})

The responsiveness suite indexes 30,000 documents on the main thread. Synchronous insertMultiple holds the event loop hostage for the entire run; the async variant stays under a 60fps frame budget while finishing in the same total time:

Worst main-thread block while indexing 30,000 documents
insertMultipleAsync

6.7 ms

Best

insertMultiple (sync)

783 ms (fully blocked)

117× longer

  • ZBSearch
  • Other engines

Longest stretch the event loop could not run. Chrome flags anything over 50 ms as a long task; a 60fps frame budget is 16.7 ms. Total wall-clock time is ~equal (783 ms sync, 713 ms async).

Chunked persistence

save(db, { format: 'chunked' }) splits the serialized index into fixed-size chunks, and loadAsync() restores it without blocking the main thread. Large indexes no longer hit string-length limits or freeze the tab while loading. See the serialization docs.

Things Orama's core simply doesn't have

Beyond the shared API surface, ZBSearch ships features with no Orama equivalent:

  • IVF vector indexing - approximate nearest-neighbor search for vector and hybrid queries.
  • Zero-config multilingual tokenization - language: 'multilingual' handles Unicode tokenization and diacritic folding across scripts, including Cyrillic and Arabic.
  • An edge runtime - self-hosted search on Cloudflare Workers + R2, with snapshot + WAL storage.
  • Docs integrations and search boxes - covered in their own section below.

The IVF index deserves its own chart. On 2,000 documents with 128-dimensional vectors:

Vector search throughput: flat scan vs IVF (2,000 docs, 128d)
ZBSearch (IVF)

11,810 ops/s

Best

ZBSearch (flat)

2,674 ops/s

4.4× slower

Orama (flat)

2,215 ops/s

5.3× slower

  • ZBSearch
  • Other engines

Approximate nearest-neighbor with nlist=179, nprobe=16. Higher is better. Orama's JS core has no IVF equivalent.

ZBSearch's flat index already beats Orama's on every vector workload (13-33% faster), but flip on IVF and vector search runs at 5.3× Orama's throughput - 16,441 vs 4,476 ops/s on strict-similarity queries, 16,621 vs 5,216 ops/s with filters. Orama has nothing to flip on.

Search for your docs site, in one package

If what you actually want is "good search on my documentation site", you don't have to touch the engine at all. ZBSearch ships first-class integrations that index your content at build time and replace the default search dialog:

  • Docusaurus - @zbsearch/plugin-docusaurus indexes your docs, blog posts, and MDX pages while Docusaurus builds.
  • Starlight - @zbsearch/plugin-starlight swaps Starlight's built-in Pagefind search, wired into your content collections.
  • VitePress - @zbsearch/plugin-vitepress builds the index from VitePress' own content loader, so results follow your srcDir, cleanUrls, and base exactly as the router does.

The index ships with your static build and runs in the visitor's browser - no search server, no API key, no per-query pricing.

A headless search box you can actually theme

Under those integrations sit @zbsearch/searchbox-react and @zbsearch/searchbox-vue. They ship the behavior - searching, result grouping, keyboard navigation, accessibility - and leave the look to a handful of CSS variables:

.my-search {
  --zbs-surface: #ffffff;
  --zbs-accent: #18181b;
  --zbs-text: #09090b;
  --zbs-border: rgb(9 9 11 / 10%);
  --zbs-radius: 6px;
  /* ...that's the whole theming API */
}

Here it is live - a real ZBSearch index of this documentation, running in your browser right now. Switch presets to restyle the same component from "minimal" to "terminal" to "soft", in light and dark; the panel on the right shows the exact variables being painted:

All of this is self-hosted and free: no cloud account, no API key, no per-query bill - and nothing equivalent ships in Orama's open-source core.

Migrating from Orama

ZBSearch is 100% backward compatible with Orama. For most projects, migration is:

- import { create, insert, search } from '@orama/orama'
+ import { create, insert, search } from 'zbsearch'

Schemas, queries, filters, facets, hooks, and plugins keep working. The ZBSearch vs Orama page has the full benchmark methodology and per-suite numbers, updated for this release.

npm install zbsearch

Every benchmark in this post is reproducible from the repository:

cd benchmarks && npm install
npm run benchmark:compare        # Orama vs ZBSearch head-to-head
npm run benchmark:search-quality # BEIR ranking quality

Come for the speed, stay for the quality.