Skip to main content
Version: 2.5

Triggers and Conditions

The trigger starts an effect; when decides whether its actions run.

TriggerUse it when
Value changeA watched form or context value changes
Widget eventA widget emits a semantic event such as success
Invoked effectAnother effect explicitly calls reusable logic

Conditions

when is optional. It must evaluate to the boolean true for the actions to run:

{"when": "${$value.customerType == 'company'}"}

A boolean field can be used directly, for example {"when": "${$value.sameAsShipping}"}. The string "true", number 1, and non-empty objects are not boolean true. A missing when permits the actions after the trigger fires.

Trigger Types

Value Change

A value-change effect has listen and no trigger. It runs when at least one watched path changes according to deep equality.

{
"id": "recalculate-total",
"listen": ["$value.quantity", "$value.price"],
"do": [
{
"type": "set",
"field": "total",
"value": "${($value.quantity || 0) * ($value.price || 0)}"
}
]
}

An empty or missing listen array never runs as a value-change effect.

Supported listen roots are:

RootWatched data
$valueCurrent root form value
$contextForm context
$outputContextOutput context
$configApplication/API configuration
$runtimeInfoCurrent user and runtime information
$versionApplication version
$buildBuild metadata

listen is not inferred from expressions. If an effect reads $value.customer.id and its change should rerun the effect, include that path explicitly.

Datasource results are resolved as part of the expressions that call them; they are not a top-level value-change trigger. Put the datasource call in when, set.value, or runEffect.inputs and listen to the form or context values passed as its parameters. Datasource calls inside scriptData are not resolved by the current Form Effects runtime.

Initialization

The first settled form snapshot normally establishes only the comparison baseline. runOnInitialization: true opts a value-change effect into exactly one run after initial values, expression defaults, and their data sources have settled.

Intermediate default or datasource emissions do not each cause a separate initialization run. The option does not apply to widget-event or invoked effects, and the designer removes it when the trigger type changes.

Fluent-array row scope

Use $row in a watched path to run once for each changed row of one fluent-array. All $row listeners in an effect must identify the same array; one $row represents one array level. For path syntax, lifecycle, and complete examples, see Arrays and nested forms.

Widget Event

A widget-event effect listens to one semantic event from one concrete widget or layout node:

{
"id": "remember-opened-panel",
"trigger": {
"type": "widgetEvent",
"objectPointer": "/layout/0",
"event": "panelOpened"
},
"do": [
{
"type": "set",
"field": "lastOpenedPanel",
"value": "${$payload.index}"
}
]
}
Trigger propertyDescription
typeAlways widgetEvent.
objectPointerCanonical JSON Pointer of the emitting schema node.
eventStable event name declared by that widget's plugin definition.

Only events declared by the widget are accepted at runtime. The Form Designer filters the event selector accordingly and provides known payload members to JEXL autocomplete.

The source selector displays the widget breadcrumb, selector, and pointer. Use Locate widget to select that source in the designer and scroll it into view.

The event context contains:

{
name: string;
payload?: unknown;
objectPointer: string;
dataPointer?: string;
row?: number;
indexes?: number[];
selector?: string;
}

Use $payload as the preferred shorthand for $event.payload. Use $event when event metadata such as name, objectPointer, or selector is required. For an event emitted inside a fluent-array, objectPointer identifies the authored schema node while dataPointer identifies its concrete rendered occurrence. indexes lists its enclosing array indexes from outermost to innermost, and row is the index owned by the event source. The expression context exposes that same value as $row.

For example, an event from orders[2].lines[1] has indexes: [2, 1] and $row === 1. Expressions can read the outer row as $event.indexes[0] and the inner row as $row:

$value.orders[$event.indexes[0]].lines[$row].description

This is how a root-level widget-event effect identifies its concrete nested source. The root location of effects[] does not imply row 0 and the runtime does not infer the deepest row from schema nesting alone.

For tab and step transitions, prefer stable identifiers such as $payload.previousId and $payload.activeId when available. Position-based previousIndex and activeIndex remain available.

objectPointer identifies a schema position rather than a persistent widget ID. Do not maintain it by hand after structural edits. The Form Designer remaps widget-event pointers when nodes are moved, inserted, renamed, duplicated, or deleted.

Explicit Invocation

An invoked effect is a reusable named action block. It runs only through a runEffect action:

{
"id": "process-customer",
"trigger": {"type": "invoked"},
"when": "${$input.customer != null}",
"do": [
{
"type": "set",
"field": "customerName",
"value": "${$input.customer.name}"
}
]
}

Its non-empty id must identify exactly one effect in the root schema. Values passed by runEffect.inputs are available through $input.

For row-scoped worked examples, see Arrays and nested forms.