Expression Parsing And Shortcodes

What It Is

This chapter explains the parser layer that sits underneath Jetstack expressions. The old name "validators" is misleading here. The code in validators.php is primarily about:

  • variable-path resolution
  • parser-level shortcode helpers
  • table-column translation
  • fallback lookup behavior
  • final string post-processing

This is the layer that turns strings like {$this.owner.full_name}, {{ env('loginLink') }}, {?sum(this.items, price)}, or {$this.status|upper} into actual runtime values.

Why It Matters

Two expressions can look similar and still be handled by different parser mechanisms:

  • {$this.owner.full_name} is variable traversal
  • {{ env('loginLink') }} is code-processor evaluation (template-interpolation style)
  • {=contains({$this.tags}, "vip")} is code-processor evaluation (raw-value style)
  • {?sum(this.items, price)} is utility-shortcode evaluation
  • {%status%} is table-column translation
  • {$this.title|upper} is variable traversal followed by a string filter

When an expression behaves unexpectedly, the most useful question is often not "is the data wrong?" but "which parser is responsible for this expression?"

Main Parsing Layers

Variable Parser

The variable parser resolves dot-path access from the current context. It is responsible for expressions such as:

{$this.customer.name}
{$this.parent.status}
{$this.lines[:first].price}

This parser can also apply a small set of path-level helper nodes and final string filters.

Code Processor Parser

The code-processor parser is the {{...}} layer. It evaluates the inner expression through the Code Processor and substitutes the result back into the surrounding string, post-processed as a stringified value suitable for template interpolation.

Use this layer when you need expressions like:

{{ env('loginLink') }}
{{ templateVar('pageTitle') }}
{{ var('orderTotal') }}
{{ selection() }}
{{ self.invoice_number }}
{{ if(self.is_paid, 'Paid', 'Outstanding') }}

Inside an Automation or Canvas context the parser reuses that resource's own CodeProcessor instance, so var(), trigger(), endpoint(), and the full provider set resolve against the live runtime. In other contexts it builds a minimal processor with templateVar / env / appParameter / dataQuery / secrets providers wired against the current app.

Math Parser

The math parser also delegates to the code processor, but is the {=...} layer and returns raw values rather than stringified ones. It exposes the same function catalog documented in Code Processor Functions.

It also adds two extras beyond the shared CodeProcessor configuration:

  • pre-binds self to the current property value when the expression mentions it
  • registers a try(arg1, arg2, arg3, arg4) fallback helper that attempts variable resolution on each argument until one produces a non-null result

Practical difference vs. {{...}}: {=...} is the right choice when the caller needs a boolean, a number, or a non-string value — for example in show_conditions or filter parameters. {{...}} is the right choice when the result is being embedded into a template, email body, or any other string.

Table Column Parser

The table-column parser translates a property reference into the SQL-oriented table-column representation used by query logic.

Use this mental model when a feature needs a column-safe expression rather than a resolved runtime value.

Utility Parser

The utility parser is the shortcode layer behind {?...} expressions. This is the part of validators.php that exposes the built-in shortcode-style helper functions documented in detail below.

Supported Shortcode Functions

These are the utility shortcodes defined in UtilsParser.

date(...)

Purpose:
Formats the current date or a provided date value.

Syntax:

{?date()}
{?date("Y-m-d")}
{?date("d.m.Y", "2026-04-10")}

Parameters:

  • argument 1, optional: PHP date format string
  • argument 2, optional: date string to parse through strtotime()

Behavior:

  • with no arguments, it uses Y-m-d
  • with one argument, it formats the current time
  • with two arguments, it formats the parsed second argument using the first format

Return value:
Formatted date string.

Notes:

  • This is a parser-level shortcode, not the same thing as the code-processor date(...) helper.
  • Use the code-processor date functions when you need more explicit typed behavior inside {=...} expressions.

sum(collectionExpression, itemExpression)

Purpose:
Calculates the sum of a value over an iterable collection.

Syntax:

{?sum(this.items, price)}
{?sum(this.positions, total_price)}

Parameters:

  • argument 1: variable expression resolving to an iterable collection
  • argument 2: expression evaluated against each item in that collection

Behavior:

  • resolves the first argument through VariableParser
  • iterates over the resulting collection
  • resolves the second argument against each item
  • adds numeric values directly
  • casts non-numeric values to float before adding

Return value:
Numeric sum. Returns 0 when argument count is wrong or the first argument is not iterable.

Use this when:

  • you need a quick total in a renderer or parser-driven template
  • the input is already present in scope and you do not need a separate query

avg(collectionExpression, itemExpression)

Purpose:
Calculates the arithmetic average over an iterable collection.

Syntax:

{?avg(this.lines, amount)}

Parameters:

  • argument 1: variable expression resolving to an iterable collection
  • argument 2: expression evaluated against each item

Behavior:

  • resolves the collection through VariableParser
  • evaluates the item expression for each element
  • collects numeric results
  • filters empty values before averaging

Return value:
Average value as a number. Returns 0 when there are no usable values or the argument count is wrong.

Notes:

  • In the source, non-numeric fallback handling is intended but inconsistently assigned in one branch. For documentation purposes, treat this shortcode as intended for numeric item expressions.

min(collectionExpression, itemExpression)

Purpose:
Finds the minimum value over an iterable collection.

Syntax:

{?min(this.lines, amount)}

Parameters:

  • argument 1: variable expression resolving to an iterable collection
  • argument 2: expression evaluated against each item

Behavior:

  • resolves the collection
  • evaluates the item expression for each element
  • collects values and applies min() to the non-empty result set

Return value:
Minimum value from the collected results.

Notes:

  • If the argument count is wrong, it returns 0.
  • Like avg, it is safest to use this with clearly numeric values.

max(collectionExpression, itemExpression)

Purpose:
Finds the maximum value over an iterable collection.

Syntax:

{?max(this.lines, amount)}

Parameters:

  • argument 1: variable expression resolving to an iterable collection
  • argument 2: expression evaluated against each item

Behavior:

  • resolves the collection
  • evaluates the item expression for each element
  • collects values and applies max() to the non-empty result set

Return value:
Maximum value from the collected results.

Notes:

  • If the argument count is wrong, it returns 0.
  • This is best used with numeric item expressions.

count(collectionExpression)

Purpose:
Counts items in an iterable value.

Syntax:

{?count(this.items)}

Parameters:

  • argument 1: variable expression resolving to an iterable collection

Behavior:

  • resolves the argument through VariableParser
  • returns count(...) if the result is iterable

Return value:
Item count as a number. Returns 0 when the value is not iterable or the argument count is wrong.

lookup(type, relationPath, property = "id", calculation = "LIST", returnType = "rendered")

Purpose:
Performs a lookup across related records and optionally aggregates the target property.

Syntax:

{?lookup("Invoice", "customer", "amount", "SUM", "plain")}
{?lookup("Project", "client.account_manager", "id", "COUNT", "raw")}
{?lookup("Task", "project", "name")}

Parameters:

  • argument 1: target type system name
  • argument 2: dot-separated relation path used to connect the current object to the target type
  • argument 3, optional: property on the target type to read; defaults to id
  • argument 4, optional: aggregation mode
  • argument 5, optional: return type

Supported calculation modes:

  • empty value or LIST: no aggregate function, return matching values as a list-like result
  • SUM
  • AVG
  • MIN
  • MAX
  • COUNT

The source sanitizes the aggregation name by uppercasing it and stripping hyphens.

Supported return types:

  • rendered
  • plain
  • raw

Behavior:

  • starts from the current base object context
  • resolves the target type
  • resolves each property in the relation path
  • resolves the final property to be calculated or listed
  • builds a SQL query joining through the discovered relationship path
  • filters out deleted target records
  • caches identical lookup results during the request

Return value:

  • aggregate mode: one calculated value, simulated through the target property
  • list mode with rendered or plain: concatenated string with <br> separators
  • list mode with raw: JSON-encoded array of raw IDs or raw values
  • null on invalid setup in most failure cases
  • 0 when the calculation is COUNT and the query yields no result

When to use it:

  • when you need a relation-aware aggregate without creating a dedicated query
  • when the lookup logic is local to one expression and still readable

When not to use it:

  • when the relation logic is central to the solution and deserves a reusable query
  • when the expression becomes hard to explain or test

try(arg1, arg2, arg3, ...)

Purpose:
Returns the first argument whose variable resolution yields a non-null result.

Syntax:

{?try(this.title, this.name, this.id)}

Parameters:

  • one or more candidate expressions

Behavior:

  • evaluates each argument through VariableParser
  • stops at the first non-null result

Return value:
First non-null resolved value, or null when none resolve.

Notes:

  • A similarly named helper is also added to the math parser for {=...} expressions. The purpose is the same: fallback resolution across multiple candidates.

Variable-Path Helpers

The variable parser supports more than simple field access. These helpers appear as path nodes inside {$...} expressions.

Context And Navigation Helpers

HelperWhat it doesNotes
thisRefers to the current base object context.Mostly redundant, but useful for clarity.
selfRefers to the current property value in property-aware evaluation.In some flows it can use a custom provided self value.
parentMoves to the parent object or component.In strict mode, unresolved parent lookup throws a scope-resolution exception.
__UserResolves the current user object when used as the first path node.Useful in context-aware display logic.
getContext(TypeSystemName)Looks up a context object by type system name from the merged stack.Works with context-aware builders and nested scopes.
lookup(ClassName, conditions...)Looks up an object from the context stack by model class name, optionally filtered by conditions such as typeId:Invoice or other properties.This is different from the utility shortcode lookup(...). This helper searches the in-memory context stack, not related database rows.

Value-Conversion And Access Helpers

HelperWhat it doesNotes
toString()Converts the current result into string form.Uses Utils::xToString(...).
plain()On a property node, returns the plain property value.Only meaningful when the current result is a Property.
raw()On a property node, returns the raw stored value.Useful when rendered or normalized value would hide the actual storage representation.
count()On an array result, returns count(...).Path-level helper, different from the utility shortcode.
max()On an array result, returns max(...) or 0 for an empty array.
min()On an array result, returns min(...) or 0 for an empty array.
avg()On an array result, returns the average of filtered values.

Array And String Indexing Helpers

HelperWhat it doesNotes
[:first]Returns the first item from an array, or the first character from a string/scalar value.
[:last]Returns the last item from an array, or the last character from a string/scalar value.
[n]Returns an indexed item from an array or a character at index n from a scalar/string value.

Post-Processing Filters

After variable resolution, the parser can apply pipe-style post-processing filters:

{$this.title|upper}
{$this.code|trim|lower}
{$this.name|replace:a|b}

The parser splits filters by |. For each filter:

  • the filter name must match a static method on Nette\Utils\Strings
  • arguments can be passed after : and are split further by |

What This Means In Practice

  • only filters implemented as methods on Nette\Utils\Strings are supported here
  • this is not an open-ended filter registry
  • failed filter application falls back to the previous value rather than crashing the whole expression

Important Limitation

This parser layer does not define its own custom named filter catalog. Instead, it forwards to Nette\Utils\Strings when a matching method exists.

That means the supported filter surface depends on the string utility methods available in the installed Nette version.

Strict Mode And Failure Behavior

Several parser branches distinguish between strict and non-strict evaluation:

  • in strict mode, unresolved paths may throw QueryScopeResolutionException
  • in non-strict mode, the parser usually returns empty-string-like or null-like fallback values instead

This matters most in:

  • query-sensitive configuration
  • builder-assisted scope resolution
  • expressions that may run in both UI and query contexts

When To Use Which Layer

Use these rules of thumb:

  • use {$...} when you need dot-walker access to the current context (object properties, parent chains, __User, __env)
  • use {{...}} when you need a code-processor expression to render into a string — template interpolation, email bodies, layout placeholders
  • use {=...} when you need a code-processor expression to return a raw typed value — boolean conditions, math, filter parameters
  • use {?...} when you need one of the utility shortcodes from this chapter
  • use {%...%} when the target feature expects table-column translation

{{...}} and {=...} share the same underlying CodeProcessor engine; the difference is the post-processing layer (stringified vs. raw). When in doubt, pick the one whose return shape matches the surrounding field.

Regex Shape Of The Parts

All five parts share a unified regex template — \s*([^{}]+?)\s* between the delimiters — with the /u Unicode flag. This means surrounding whitespace is always trimmed ({$ x } works the same as {$x}), inner expressions never cross a nested { or }, and multibyte input is validated. The capture group's value is exactly the inner expression the handler receives.

Best Practices

  • Prefer plain variable paths first. Add shortcodes only when a simple path is not enough.
  • Use lookup(...) shortcode carefully. It is powerful, but it also hides real query complexity inside an expression.
  • Keep try(...) short and intentional so fallback order stays readable.
  • Use raw() only when the stored form truly matters for the behavior you are implementing.
  • When pipe filters are involved, remember they run after variable resolution and are string-oriented.