Script fields #
Compute extra values for each hit, at fetch time, from the hit’s own
_source — like
highlighting, but producing numbers and
strings instead of fragments. Scripts run on Pizza’s sandboxed script
engine (see
the script language reference),
only for the hits actually returned on the page.
Every returned hit carries the computed values under fields, as arrays:
{
"_source": { "amount": 30.0, "type": "hat", "...": "..." },
"fields": {
"doubled": [60],
"with_vat": [36.9]
}
}
The script source goes in either wire form — "script": "<source>"
or "script": { "source": ..., "params": ... }. Extra variables arrive
as params.<name>.
How it works #
script_fields is an engine-native fetch-phase enrich
(lib/engine/src/search/aggregator/script.rs,
apply_script_fields). The engine compiles each named script once per
request, then evaluates it per hit of the RETURNED page — after ranking
and truncation, exactly like highlight. A size: 3 request runs each
script three times; size: 0 never runs it at all, whatever the match
count.
Per hit, the engine builds the doc-values map from the hit’s stored
fields — doc.price.value (first value), doc.price.values (all
values) — and evaluates with doc and params in scope. The result is
coerced to the field shape: a scalar becomes a one-element array, an
array stays an array, and a script evaluating to nothing (()) yields
an empty array.
Errors are fail-closed by default: a script error fails the whole
request rather than returning a silently partial page. ignore_failure: true demotes just that field — omitted for the hits where it errors,
computed where it does.
The index.max_script_fields setting (count cap, default 32) is
validated by the ES-compatibility layer. The native engine
path enforces no count cap; the practical limits are the page size and
the sandbox below.
The doc binding
#
Each script sees the hit’s _source in the doc-values shape — the
same binding scripted_metric’s map_script sees:
doc.amount.value— the first value ofamountdoc.amount.values— all values (multi-value fields)doc['amount'].value— the bracket spelling- a field the document doesn’t have has no entry —
doc.missing.valueerrors (guard with"amount" in docwhen a field may be absent)
ignore_failure
#
By default a script error fails the whole request — never a silently
partial page. Set ignore_failure: true on a field to omit just that
field for the hits where its script errors:
discount does not exist on the sample data, so maybe_discounted is
simply absent from every hit while line_total is computed.
Use cases #
- Derived units on display. Unit prices with VAT,
amount × qtyline totals, currencies converted with aparamsrate — computed at read time so the stored document stays the source of truth. - Cheap formatting. Bucket labels, display names glued from parts
(
doc.first + " " + doc.last), or normalization (rounding, string casing) without re-indexing. - Conditional annotations. Flag hits whose
amountexceeds aparams.threshold— an annotation on the page, not a filter (filters belong in the query). - Field trimming for wide docs. Emit one compact computed field instead of shipping several raw ones to a thin client.
Parameters for script_fields
#
<name>
(Required, object) One entry per computed field; the key is the name underfieldsin the response.script(Required, string | object) Inline source string, or{ "source": ..., "params": ... }.ignore_failure(Optional, boolean, default:false) Omit this field for hits where the script errors instead of failing the request.
Notes and limits #
- Language: Rhai, not Painless — everything in the language reference applies.
- Compute-only:
script_fieldsenriches the response; the computed values cannot filter, sort, or score — those need scripted queries (scriptfilters,script_score, the_scriptsort), which run at query time rather than fetch time. - Page-sized cost: scripts run after truncation, so only the
returned page pays — roughly three microseconds per field per hit,
native. A
size: 0request never evaluates a script field. - Sandboxed: same containment as aggregation scripts — an operation cap stops runaway loops, there is no host or network IO, and string, array and map sizes are bounded.
Related #
- The script language — the
shared Rhai reference: syntax, the
docbinding, containment. - Scripted queries — the
query-time siblings:
scriptfilters,script_score, the_scriptsort key. - Highlighting — the other page-sized fetch-time enrich.
- Sort and pagination — the
_scriptsort clause it links to, and whyfrom/sizebound what scripts ever see.