Expressions

What you can write in an expression field, and what is in scope when it runs.

Application source is compiled by applications/main/src/templates/declarative/authoring-expressions.ts into closed Expression data. Server and browser evaluate that data through the same generic interpreter; no source evaluation or native callback registry runs at execution time.


The six slots

SlotLives onDecidesEvaluated
startExpressiontemplateCompatibility metadata onlyNot activated as a start gate
expressioncondition stepWhich branch?When the step executes
expressionautomatic stepWhat value to storeWhen the step executes
completeExpressioninput stepMay this step be finished?Every completion attempt
visibleExpressionfield, view controlShow or hideOn render
waitUntilExpressionwait stepUntil when to parkOnce, on arrival

Plus two interpolation forms, which substitute rather than decide:

FormWherePurpose
${step.field}defaultValue, message bodiesInsert a context value
{{ … }}view controls, downloadFilenameSupported authoring expression, compiled to closed data

What is in scope

Accumulated context, keyed by step key:

input_description.description     // a field written by an earlier step
run.dealName                      // a run input, from template.runInputs
currentProcess.url                // built from APP_BASE_URL

completeExpression additionally gets hasPermission("name").

View controls and {{ }} get a wider surface: step keys, keccak256, generatePayload, Date, Math, JSON.


Examples

Branching — condition

expression: "review_description.description_review_ok === true",
thenStepKey: "input_risk_model_availability",
elseStepKey: "input_description",

Gating completion — input

completeExpression:
  "trim(input.name).length > 0 && trim(input.contractAddress).length > 0 && " +
  "trim(input.contactPlatform).length > 0 && trim(input.contact).length > 0 && " +
  "trim(input.agent).length > 0 && trim(input.wallet).length > 0",

integrator-onboarding marks no fields mandatory and enforces completeness entirely through this expression.

Showing a field conditionally

{ key: "description_review", type: "string", title: "Provide feedback for the Prime",
  visibleExpression: "review_description.description_review_ok === false" }

Scheduling — wait

waitUntilExpression: "nextUtcWeekdayTime(1, 20, 0)",   // next Monday 20:00 UTC

Substituting a prior answer

{ key: "_view_0", type: "string", title: "Feedback from OEA",
  readOnly: true, defaultValue: "${review_description.description_review}" }

Computing in a view control

{ title: "Payload", data: "{{ generatePayload(context) }}", plainText: true }

Helpers

OwnerProvides
Platform presentation scopeExplicit process/user metadata, permissions and time
Domain declarative definitionsPrime, dates (including isoDateAddDays and cadenceDates for choice recipes), amounts and EVM/Solana protocol expressions
App authoring compilerSource lowering, helper expansion and named render macros
Canonical service / generic FormsCompletion validation and compiled field presentation

completeExpression vs mandatory

Both enforce completeness. They are not interchangeable.

mandatorycompleteExpression
Scopeone fieldthe whole step
Enforcedrunner and serviceserver-side, on every completion
Seesthat fieldall context
Skipped for hidden / read-only fieldsyesno

Use completeExpression when the rule spans fields or is conditional. If a step will not complete and nothing looks required, read its completeExpression — the API distinguishes authorization from invalid interaction data; inspect its error code rather than assuming every refusal means missing permissions.


Writing them safely

  • Guard optional context: input.foo && input.foo.length > 0, never input.foo.length > 0 on a field that may not be written yet.
  • Context is keyed by step key, not field name.
  • ${…} substitutes; {{ … }} computes. They are not interchangeable.
  • A hidden mandatory field does not block completion, so visibleExpression and mandatory interact — prefer completeExpression for conditional rules.

Known gaps

The source language is deliberately closed, not arbitrary JavaScript. Unknown calls/source are rejected before registration. Predicate/display adapters handle expected semantic failures under their documented policy; resource and invalid-AST defects cannot be hidden as ordinary validation. enabledExpression remains source metadata without execution gating. Known failure recovery is separate from uncertain external outcomes; see durability.