Script fields

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 of amount
  • doc.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.value errors (guard with "amount" in doc when 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 × qty line totals, currencies converted with a params rate — 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 amount exceeds a params.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 under fields in 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_fields enriches the response; the computed values cannot filter, sort, or score — those need scripted queries (script filters, script_score, the _script sort), 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: 0 request 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.
  • The script language — the shared Rhai reference: syntax, the doc binding, containment.
  • Scripted queries — the query-time siblings: script filters, script_score, the _script sort key.
  • Highlighting — the other page-sized fetch-time enrich.
  • Sort and pagination — the _script sort clause it links to, and why from/size bound what scripts ever see.
Calendar October 7, 2026
Edit Edit this page