Defining an index
An index is schema. It lives in project config and deploys with your code, the way sections and fields do. Everything derived from it — records, artifacts, dependency edges — stays in the database and on disk.
Sources
Where records come from. Entries, categories, tags, assets, users and Commerce products are built in, and a plugin can register its own element type.
Each source is scoped to containers (section handles for entries, group handles for categories, volume handles for assets) and optionally types (entry types, or file kinds for assets). Leave either empty to include everything.
The status is almost always live. An index is a public artifact served as a static file — it cannot check who is asking, so a disabled entry that reached it would be visible to everyone.
Attributes
One row per key in a record. An attribute reads a value off the element and gives it one or more roles, which decide what Caffeine does with it.
| Role | What it does |
|---|---|
searchable | Text is tokenised into the artifact’s inverted index. |
facet | Values become a refinable facet. |
sortable | An ordering over this key is precomputed into the artifact. |
payload | The value rides along on the hit, so Twig can render a card without a database round trip. |
Roles are deliberately not exclusive. A product’s title is usually searchable, sortable and payload; its brand is usually facet and payload. Modelling them as a set rather than one “type” keeps the screen honest about the fact that one field feeds several parts of the artifact.
Where the value comes from
An attribute reads either an element attribute (title, slug, uri, postDate) or a custom field, by handle.
The path can descend, with dots:
| Path | Reaches |
|---|---|
title | The element’s own title. |
category.title | The title of every related category. |
specs.material | A field inside a Matrix field’s blocks. |
venue.city | A named part of an extracted value — here, an address. |
Descending into a relation denormalises: the category’s title is copied into the record at build time, which is what makes the count instant. Caffeine records every element it read in a dependency map, so renaming that category marks every record that copied it as stale. Without that, the artifact would serve a label that exists nowhere in the CMS.
Transforms
A fixed vocabulary applied to values in order — trim, lower, upper, stripTags, slug, unique, first, compact, date:FORMAT. Deliberately not an expression language: this runs over every value of every record on every build, and it arrives from project config, which is neither a safe nor a fast place for arbitrary code.
Sortings
Each named sorting is worked out once at build time and stored as a list of record IDs, so switching sort at query time is a pointer swap rather than a sort. They are Algolia’s “replicas” in everything but storage cost.
relevance always exists and needs no row. A sorting must name an attribute with the sortable role — one over a key with no precomputed order would have to sort at query time, which is the cost the whole plugin exists to avoid.
The rest
| Setting | What it does |
|---|---|
| Transport | How refinements reach the browser. See Transports. |
| Hits per page | The default page size. A template can override it. |
| Values per facet | How many values a facet returns before “show more”. |
| Publish debounce | Seconds to wait after a change before republishing, so a burst of edits costs one build. 0 republishes immediately. |
| Shard the payload | Split the card data into its own file, so a facet-count request never fetches it. |
| Sites | Which sites to build records for. One record per element per site, because a facet value in French is not the same value as its English twin. |
Checking your work
Two tools on the edit screen, both Pro:
The record preview runs the definition against one real element and shows exactly what the mapper produced — facets, sortable values, payload, tokens with their weights, and the elements the record now depends on. It is the fastest way to find out why a facet is empty.
The query playground runs a state against the published artifact and shows the result JSON with timing. Deliberately the published one rather than a fresh compile: the question it answers is “what are visitors getting”, and a playground that quietly recompiled would answer a different one.
Changing a definition
A changed definition invalidates every record built under the old one — a renamed key or a new facet means the stored records no longer match what the artifact needs. Rather than working out which changes are compatible, Caffeine marks the whole index stale and lets the next build settle it. Rebuilds are background work; a subtly stale artifact is a bug report.