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
selfto 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-nullresult
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 SUMAVGMINMAXCOUNT
The source sanitizes the aggregation name by uppercasing it and stripping hyphens.
Supported return types:
renderedplainraw
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
renderedorplain: concatenated string with<br>separators - list mode with
raw: JSON-encoded array of raw IDs or raw values nullon invalid setup in most failure cases0when the calculation isCOUNTand 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-
nullresult
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
| Helper | What it does | Notes |
|---|---|---|
this | Refers to the current base object context. | Mostly redundant, but useful for clarity. |
self | Refers to the current property value in property-aware evaluation. | In some flows it can use a custom provided self value. |
parent | Moves to the parent object or component. | In strict mode, unresolved parent lookup throws a scope-resolution exception. |
__User | Resolves 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
| Helper | What it does | Notes |
|---|---|---|
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
| Helper | What it does | Notes |
|---|---|---|
[: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\Stringsare 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.