Caffeine
Model an index in the control panel. Caffeine keeps a self-contained search artifact up to date and served, and refinements resolve against it — never against the database.
The problem it solves
A faceted listing needs a count next to every value — “Acme (12)”, “Globex (4)” — and those counts depend on every other refinement. Done with element queries, that is one query per facet value per interaction, and it is why filtered listings built on Sprig feel slow at exactly the moment the visitor is exploring fastest.
Caffeine moves the work to index time. A background build maps your content into records, compiles them into one artifact, and publishes it. From then on a refinement is set arithmetic over postings lists — on the server for the first paint, in the browser for everything after.
The shape of it
- You model an index in the control panel: sources, attributes, facets, sortings, transport. The definition lives in project config, so it deploys with your code.
- A build maps content into records. This is the only place Caffeine runs element queries, and it runs them in the background.
- Records compile into an artifact — a self-contained file carrying the facet postings, the inverted index, the precomputed orderings and the card data.
- The artifact is published as a static file, content-addressed and immutable, behind a small stable pointer.
- The front end queries it. PHP renders the first paint; the browser takes over refinement.
What makes it work
The full-text index ships with the data
Tokenising happens once, in PHP, at index time, and the finished inverted index travels inside the artifact. The browser needs no search library at all — just a binary search over a sorted token array. This is the load-bearing idea; everything else follows from it.
One spec, two engines, one fixture suite
The server renders the first paint and the browser takes over, so the two implementations must agree exactly — any disagreement shows up as the page rearranging itself under the visitor the moment they touch a control. There is a written query specification, two implementations of it, and five pieces of shared logic pinned against each other by fixtures: the tokeniser, the varint codec, the facet-value projection, the URL codec and the haversine.
Everything works without JavaScript
Every facet is a real <a href> and every widget a real form. The runtime intercepts controls that already work rather than inventing behaviour, which is why it is a few kilobytes and why turning it off degrades to page loads rather than to nothing.
It survives a full-page cache
Cached HTML embeds a stable pointer, never a versioned URL. The payload it names is content-addressed and immutable, so it can be cached forever while the pointer is cached not at all. A page cached months ago still finds today’s index.
What it is not
Caffeine serves what was published, not what is in the database this millisecond. Saves mark records stale and a debounced job republishes — 30 seconds by default. For a product listing or an article archive that is invisible. For anything where a visitor must see their own write immediately, it is the wrong tool, and Sprig is still the better one.
It also does not replace Craft’s own search for a global, cross-element-type site search. Caffeine builds its own inverted index and never touches searchindex.
Where to go next
- Installation — install it and publish a first index.
- Defining an index — sources, attributes, roles.
- The Twig API — the tags, and everything
searchgives you. - Migrating from Sprig — a straight translation.