Skip to main content
JSON Transform stage showing Jinja2 template transformation of documents
The JSON Transform stage applies a Jinja2 template to each document, rendering the template with full document context and replacing the document with the parsed JSON output. Use this to reformat documents for external APIs or reshape data for downstream consumers.
Stage Category: APPLY (1-1 Transformation)Transformation: N documents → N documents (or fewer with fail_on_error=False)

When to Use

When NOT to Use

Parameters

Template Context

Templates have access to the full retriever execution context:
Both uppercase and lowercase namespace formats work identically (DOC == doc).

Built-in Functions

These numeric helpers are available directly in templates — useful for clamping and rounding computed values (e.g. ranking scores): Read tunable inputs with a default using INPUT.get, so callers can override a weight per request but omit it otherwise: {{ INPUT.get('recency_weight', 0.3) }}.

Template Features

Jinja2 Syntax

Useful Filters

Configuration Examples

Error Handling

Common failure causes:
  • Invalid template syntax
  • Template rendering errors (missing fields)
  • Invalid JSON output from template
  • Document missing required fields
Use fail_on_error: false for public APIs where partial results are acceptable. Use fail_on_error: true for internal workflows where data integrity is critical.

Performance

Multi-line Templates

For complex templates, use HEREDOC syntax in the API call:

Common Patterns

Drop Unused Fields

Flatten Nested Metadata

Add Query Context

Compute a custom ranking signal (recency boost + used-demotion)

json_transform is the general-purpose way to compute a custom ranking score at query time: read the relevance score and any document fields, blend in your own signals, emit the result as a score, then order by it with sort_relevance. Because every weight is read from INPUT, the same retriever is tunable per request — including an instant off-switch — with no redeploy. The pipeline is three stages:
A worked example: boost fresh documents, and demote any document that already carries a used_in_ad edge. The blended score is
where recency_factor is a gentle linear decay max(0, 1 − days_old / freshness_days). The template reads each weight from INPUT with a default, so callers override only what they want. This is the exact template that runs in production, prettified for readability (the verbatim single-line version is in the block below):
A few things worth noticing in the real template:
  • User metadata is read from _internal.metadata here (md), the path managed-pipeline documents use. If your documents carry metadata at the top level or under metadata instead, adjust the path to match how they were ingested.
  • The default idiom guards null, not just missing. INPUT.get('recency_weight') if INPUT.get('recency_weight') is not none else 0.3 falls back both when the caller omits the input and when it passes an explicit nullINPUT.get('x', 0.3) alone would keep a client-sent null.
  • days_old is a pseudo-ordinal (Y*372 + M*31 + D), monotonic and accurate to about ±1 day versus true calendar days — deliberately cheap, since exact date math isn’t needed to rank by freshness.
The production template is authored on one line (newlines are optional in Jinja). This is byte-for-byte what ran in prod:
Then sort by the computed field (it is the default, shown here for clarity):
json_transform replaces each document with the template’s JSON output, so emit every field you want downstream — including the score that sort_relevance reads. Pass the template to the stage as its template string (escape it as JSON, or use the heredoc form shown under Multi-line Templates).
Tunable inputs (declare them in the retriever’s input_schema as optional):