Expression Policies
An ExpressionPolicy declares when a field's value is resolved
(the evaluation stage) and how it is resolved (the evaluator kind). Together,
these two settings determine whether a field is static config or a live runtime expression.
Structure
public sealed class ExpressionPolicy
{
public EvaluationStage EvaluationStage { get; init; } = EvaluationStage.AtConfigLoad;
public EvaluatorKind EvaluatorKind { get; init; } = EvaluatorKind.Template;
}
EvaluationStage
Determines the point in the execution lifecycle at which the field's value is resolved.
AtConfigLoad
Evaluated once when the workflow instance loads or the node's configuration is initialised. The resolved value is cached and reused across all executions.
Use when: The value is fixed in the workflow definition and does not depend on runtime data.
| Example fields |
|---|
| SMTP host / port — infrastructure config |
| Approval strategy — "AnyOne", "NofM", "Unanimous" |
| HTTP method — "GET", "POST" |
| Static labels or identifiers |
AtInputReady
Evaluated just before the node executes, after all upstream data has been resolved and mapped into the node's input. Has full access to workflow variables, upstream outputs, and live data.
Use when: The value depends on data from previous nodes or workflow runtime state.
| Example fields |
|---|
Email recipient — {{workflow.customer.email}} |
| Slack channel — determined by workflow context |
| Form assignee — determined by a preceding step |
| HTTP request body — built from upstream data |
NodeControlled
The executor itself determines when to evaluate the field. The expression engine does not automatically trigger evaluation.
Use when: Advanced nodes with custom evaluation timing that does not fit the standard two-stage model.
AsLiteral
The value is treated as a raw constant. No expression processing occurs — the value is used exactly as stored in config. Use when a value looks like a template expression but must not be processed.
EvaluatorKind
Determines the expression language used to evaluate the field value.
Template
Evaluates handlebars-style {{expression}} placeholders. The most common evaluator.
"Hello {{workflow.customer.firstName}}"
"{{inputs.userId}}"
"{{env.API_BASE_URL}}/users/{{payload.id}}"
Supports: workflow variable references, upstream output references, environment variables, simple dot-notation paths.
JavaScript
Evaluates a JavaScript snippet. Provides full scripting capability for complex transformations.
JSON.stringify({
userId: inputs.customerId,
amount: inputs.orderTotal * 1.1,
currency: workflow.tenant.defaultCurrency || 'USD'
})
Use for: conditional logic, data transformation, arithmetic, combining multiple upstream values.
JsonPath
Extracts a value from a JSON document using JSONPath syntax.
$.customer.address.city
$.items[0].productId
$.results[?(@.status == 'active')].id
Use when the field value should be extracted from a larger JSON payload.
Literal
The value is used as-is. No expression engine is invoked. Use for fixed constants or when the value superficially resembles a template but must not be evaluated.
None
The field value is not evaluated from config at all. The executor sets it at runtime (e.g., an output field populated after the node completes its operation).
Use for: output fields populated by the executor, runtime-generated identifiers (message IDs, timestamps), values from external system responses.
Stage × Kind Quick Reference
| EvaluationStage | Common EvaluatorKinds | Notes |
|---|---|---|
AtConfigLoad | Template, Literal | Config is static; JavaScript rarely needed at load time |
AtInputReady | Template, JavaScript, JsonPath | Full runtime data available |
NodeControlled | Any | Node decides |
AsLiteral | Overrides kind | Stage takes precedence; kind ignored |
| Output fields (any stage) | None | Output fields are never evaluated from config |
Decision Guide
Is the value fixed in the workflow definition?
YES → AtConfigLoad
NO → AtInputReady
Does it use {{placeholders}}?
YES → Template
NO, needs logic/transformation → JavaScript
NO, extracts from JSON path → JsonPath
NO, is a constant → Literal
NO, set by the executor after running → None
EvaluationStage.AtInputReady + EvaluatorKind.None. The AtInputReady stage is a convention signalling "this is a runtime field" even though no evaluation occurs.