search.json reference
search.json declares which of your entities are indexed for search and what of each row goes into a document. The platform builds the index from it and keeps it in step with your rows. You write no indexing code, no triggers and no crawler. For when to reach for it at all, see Projections.
The JSON Schema at https://schemas.revenexx.com/search.schema.json is the machine-readable contract. Point your editor at it; this page explains what each key means.
A complete example
The products app ships this collection (trimmed):
{
"$schema": "https://schemas.revenexx.com/search.schema.json",
"version": "2",
"collections": [
{
"name": "products",
"source": "products",
"key": "id",
"scope": { "by": ["channel", "locale"], "mode": "facet" },
"fields": [
{ "name": "sku", "from": "column:sku", "sort": true },
{ "name": "kind", "from": "column:kind", "facet": true },
{ "name": "enabled", "from": "column:enabled", "type": "bool", "facet": true },
{ "name": "name", "from": ["attribute:locale.name", "column:label"], "type": "string", "locale": true, "sort": true },
{ "name": "description", "from": "attribute:locale.description", "locale": true, "optional": true },
{ "name": "manufacturer", "from": "attribute:common.manufacturer", "facet": true, "optional": true },
{ "name": "categories", "from": "relation:product_categories→categories.code[]", "type": "string[]", "facet": true, "optional": true },
{ "name": "category_labels", "from": "relation:product_categories→categories.labels.<locale>[]", "type": "string[]", "locale": true, "optional": true },
{ "name": "attrs", "from": "attribute:common.*", "type": "object", "types_from": "attributes", "index": false, "optional": true },
{ "name": "attrs_locale", "from": "attribute:locale.*", "type": "object", "types_from": "attributes", "locale": true, "index": false, "optional": true },
{ "name": "updated_at", "from": "column:updated_at", "type": "int64", "sort": true }
],
"default_sort": "updated_at",
"depends_on": ["product_categories", "categories"]
}
]
}
Top level
| Key | Required | Meaning |
|---|---|---|
version | Yes | Contract version, the string "2". "3" adds the keys in Version 3 and is being rolled out (as of 2026-10). |
collections | Yes | One entry per collection, at least one. |
One app ships one search.json.
A collection
| Key | Required | Meaning |
|---|---|---|
name | Yes | The logical collection name, ^[a-z][a-z0-9_]{0,62}$. It is what a client names in a query. Each tenant gets its own copy, so the name only has to be unique inside your app. |
source | Yes | The entity from your schema.json the documents are built from. |
key | No | The column that identifies a document. Default id. |
fields | Yes | What goes into each document, at least one field. |
scope | No | How channel and locale shape the documents. See Scope. |
default_sort | No | The field results are ordered by when a query names no order. It must be a field with "sort": true. |
depends_on | No | Other entities whose changes affect a document, for example a join table and the entity it joins to. A change there re-indexes the documents it touches. |
A field
| Key | Default | Meaning |
|---|---|---|
name | — | The field name in the document, ^[a-z][a-z0-9_]{0,62}$. |
from | — | Where the value comes from: one locator, or a list of locators tried in order. See Locators. |
type | inferred | string, string[], int32, int64, float, bool, bool[], object, object[], auto or geopoint. Strings infer string, timestamps int64, relation arrays string[]. |
facet | false | The field can be filtered on and counted per value. |
sort | false | The field can be sorted on. |
optional | false | The value may be missing on a row. Mark every field whose source can be empty. |
locale | false | The value is read per locale. One locale field makes the collection hold one document per row and locale. |
index | true | Whether full-text search covers the field. Set false for data you only want back in a hit, such as an attribute map or an image URL. |
types_from | none | Only on attribute:common.* and attribute:locale.*. Names the entity that declares your attributes (it needs code and type columns), and each value is typed by its attribute: boolean becomes true / false, number, integer and decimal become numbers when the stored text is numeric, everything else stays text. Typed values are what numeric ranges and boolean filters on attrs.<code> need. An unknown entity copies the values untyped. |
Locators
from names the source of a value. You never write SQL.
| Locator | Reads |
|---|---|
column:<col> | A column of the source entity. |
attribute:common.<code> | One attribute from the common bucket of attribute_values. |
attribute:common.* | The whole common bucket, as an object. |
attribute:locale.<code> | One attribute from the locale bucket of the document's locale. Use with "locale": true. |
attribute:locale.* | The whole locale bucket of the document's locale, as an object. |
relation:<join>→<entity>.<col>[] | A column of every row reached through a join entity, as an array. Example: the codes of a product's categories. |
relation:<join>→<entity>.<col>.<locale>[] | A translated label of every row reached through a join, in the document's locale. Use with "locale": true. |
reference:<attr>.label | The label of the reference record an attribute points at. |
asset:<slot>.url | The URL of an asset linked to the row. |
computed:<name> | A value the platform computes, such as completeness. |
The arrow is the character → (U+2192), not ->.
A list of locators
from also takes a list. The first locator that yields a value wins, in list order, so a product without a translated name still gets one:
{ "name": "name", "from": ["attribute:locale.name", "column:label"], "type": "string", "locale": true }
All locators in one list must yield the same kind of value: single values, arrays ([]) or objects. A list of single values of different types, such as a text column and a number, works only for a field of type string or auto, and the values arrive as text. A list with one locator behaves like the plain string. An empty list, or a list that mixes kinds, is refused when the app is deployed.
reference:, asset: and computed: are accepted by the schema but resolve to an empty value for now (as of 2026-10). Declare them optional if you add them early.
Locale fields
A field with "locale": true is read in the locale of the document. The platform creates one document per row and write key of the tenant, and the document's locale field says which. A tenant that keeps text per language produces de and en documents, a regional tenant de-DE and en-US. Keys no market configures are not published.
Which rows get a document in a language is the tenant's choice, not the app's. The main language (the write key of the tenant's fallback_locale) always has every row. The other languages follow the products setting search_untranslated: hide (default) publishes a row only where it has its own text in that language, fallback publishes every row and fills gaps from the main language. A fallback list in from decides what an untranslated row shows. A tenant without market locales gets one document per key that is stored. See Languages and locale codes.
A storefront asks for one locale with the X-Revenexx-Locale header and gets one hit per row back, even when a row has documents under both spellings. See Search collections.
Scope
"scope": { "by": ["channel", "locale"], "mode": "facet" }
| Key | Values | Meaning |
|---|---|---|
by | channel, locale | The dimensions documents vary by. |
mode | facet (default), materialize | facet keeps one document per row and locale and exposes the channel as a filterable field. Visibility is filtered at query time. materialize writes a separate document per scope value for hard isolation. It costs more index space, so use it only when a document must not exist outside its scope. |
Validation
The file is checked against the published schema when you push or publish an app version, when you validate one without publishing, and before a build starts. Unknown keys are refused, so a typo such as type_from fails instead of being ignored. The response lists each error with its path, for example collections[0].fields[1], and a build log prints them too. An invalid file fails the deployment of that version.
Version 3
Version 3 is being rolled out (as of 2026-10). Set "version": "3" to use these keys; under "2" they are refused. A version 2 file keeps working unchanged.
| Key | On | Meaning |
|---|---|---|
locale_source | collection | Where translated text comes from: attribute_values (default), labels, or { "type": "rows", "entity": "…", "locale_column": "locale", "where": { … } } for an entity that stores one row per locale. |
untranslated | collection | setting (default) follows the tenant's search_untranslated. publish gives every write key a document, only with a locale_source other than attribute_values. |
where | collection | Only rows matching these values are indexed. An object of column to string, number or boolean. |
views | collection | Default relevance settings per view (search, suggest, …): fields searched and their weights, typo tolerance, sort, facets, per_page and similar. They apply until a tenant saves its own settings for that view, and never overwrite them. |
analyzer | field | plain (default), language (stemming for the document's language) or identifier (codes and SKUs). |
New locators in version 3:
| Locator | Reads |
|---|---|
label:<col> | A translated label column in the document's locale. |
row:<col> | A column of the per-locale row, with locale_source of type rows. |
reference:<fk>→<entity>.<col> | A column of the record a foreign key points at, optionally .<locale> for a translated one. |
count:<entity> | How many enabled rows of an entity point at this row, as a number. |
ancestors:<col>[] | The column along a tree, root first, optionally .<locale>[]. Example: the names of a category's parents. |
What changing the file does
Add a field to the file and deploy. Existing rows are indexed in the background after the schema is applied, so results can be incomplete for a short while after a deploy. See Ship a schema change.
Where to go next
- Projections —
search.jsonnext toanalytics.json, and when you need either. - Search collections — the collections the platform ships and how to query them.
- Schema reference — the entities a collection is built from.