Options Reference
Post-resolution transforms: format, validate, encode, and cache the resolved value.
Contents
How Options Work
Options are declared in the expression after the path, prefixed with @. They are applied by the orchestrator after the directive resolves the raw value, in this order:
- PopulateConstraints — each option records its constraints (Required, NotEmpty, DefaultValue) into
FieldConstraints - Apply — each option transforms the resolved value
- FormatOutput — the final
FormattedValuestring is produced
ICacheMarkerOption is special
Options implementing
ICacheMarkerOption (@cache, @cache-thread, @cache-process) are handled by the orchestrator before directive invocation. A cache hit returns immediately, skipping the directive entirely.
IDirectiveOption Interface
public interface IDirectiveOption
{
string OptionName { get; } // token after @
bool IsSensitive { get; } // mask value in logs
void PopulateConstraints(FieldConstraints constraints); // set Required/NotEmpty/Default
object? Apply(object? resolvedValue); // transform the value
string? FormatOutput(object? finalValue); // produce FormattedValue string
}
public interface ICacheMarkerOption : IDirectiveOption
{
CacheScope Scope { get; }
string BuildCacheKey(EvaluationRequest request);
}
Option Syntax
| Pattern | Example | Notes |
|---|---|---|
| Simple flag | @uppercase | No parameter |
| With parameter | @default:N/A | Parameter after colon |
| With format string | @date:yyyy-MM-dd | Format string as parameter |
| With scope | @cache-thread | Hyphen-separated qualifier |
Multiple options in a single expression — space-separated, applied left to right:
Trim, then uppercase, then cache at thread scope
{@ $var.status @trim @uppercase @cache-thread }
Built-in Options Reference
Transform Options
| Option | Effect on Value | Example |
|---|---|---|
@uppercase | Converts string to upper case | "hello" → "HELLO" |
@lowercase | Converts string to lower case | "HELLO" → "hello" |
@trim | Removes leading/trailing whitespace | " hi " → "hi" |
@json | JSON-serializes the value | { id: 1 } → '{"id":1}' |
@base64 | Base64-encodes the string representation | "abc" → "YWJj" |
@date:format | Formats a DateTime value using the given format string | @date:yyyy-MM-dd |
Validation Options
| Option | Behavior |
|---|---|
@required | Sets FieldConstraints.IsRequired = true. If value is null, returns ValidationError. |
@not-empty | Sets FieldConstraints.MustNotBeEmpty = true. If value is empty string, returns ValidationError. |
@default:value | Sets FieldConstraints.DefaultValue. Returns the default when resolved value is null or empty. |
Output Shape Options
| Option | Behavior |
|---|---|
@typed | Sets OutputFieldName = "value" on FieldConstraints — tells the assignment layer to use the raw Value property (typed) rather than FormattedValue (string). |
Cache Options
Cache options implement ICacheMarkerOption. They must appear in the expression — they cannot be programmatically added.
| Option | Scope | Invalidated when | Use for |
|---|---|---|---|
@cache | Node | Current node execution ends | Values constant within a node run (e.g., config lookups) |
@cache-thread | Thread | Workflow thread completes | Values constant for entire workflow execution (e.g., exchange rates) |
@cache-process | Process | Top-level process completes | Values constant across parallel branches (e.g., tenant settings) |
Cache an expensive API call for the entire thread
{@ $api.ExchangeService/rates/USD.rate @cache-thread }
First call makes HTTP request. All subsequent calls in same thread return cached value.
Combining Multiple Options
Options are applied left to right. The output of each Apply becomes the input of the next.
Chain: default → trim → uppercase
{@ $var.countryCode @default:US @trim @uppercase }
null → "US" (default applied) → "US" (trim: no-op) → "US" (uppercase: already upper)
Order matters
@json @base64 first JSON-serializes, then Base64-encodes the JSON string. Reversing to @base64 @json would Base64-encode the raw value, then JSON-serialize the Base64 string — a very different result.
Creating a Custom Option
Implement IDirectiveOption and register it in the DI container:
public class SanitizeHtmlDirectiveOption : IDirectiveOption
{
public string OptionName => "sanitize-html";
public bool IsSensitive => false;
public void PopulateConstraints(FieldConstraints constraints)
{
// No constraints to set
}
public object? Apply(object? resolvedValue)
{
if (resolvedValue is not string str) return resolvedValue;
return HtmlEncoder.Default.Encode(str);
}
public string? FormatOutput(object? finalValue)
=> finalValue?.ToString();
}
// Registration in DI
services.AddSingleton<IDirectiveOption, SanitizeHtmlDirectiveOption>();
The orchestrator discovers all registered IDirectiveOption implementations by name when parsing the @sanitize-html token from an expression.
Using the custom option
{@ $input.current.userComment @sanitize-html }
<script>alert('xss')</script> → <script>alert('xss')</script>