Skip to main content

Use this page before writing a form expression

Every KayanOS form expression is evaluated in a context: the form, its submitted or in-progress values, the current item in the form tree, and selected session/member identifiers. The same expression can be valid in one field and empty or misleading in another because its context is different. Use this reference for defaults, visibility, editability, validation, calculated values, action text, and other form-builder expressions. It describes values exposed by the form builder; it does not grant access, turn a visibility condition into authorization, or make a lookup safe for a public participant. For syntax and the full function catalog, use Expression language syntax and Form expressions and lookups. KayanOS form-builder context for a Citizen Service Request form.

First identify where the expression runs

Before copying an expression, identify all four parts of its context: The builder shows representative context shapes to help author an expression. Those shapes are not a production data preview and must not be used to infer that a field, selector result, or record is available to every participant at runtime.

Form-level roots and identifiers

The shared context exposes these stable roots and identifiers: Use $form.submissionNumber when the expression should name the public-facing submission number explicitly. It is not the internal sessionId, and there is no root submissionNumber alias. $ is not a magic database root. It begins with form variables and includes top-level form children that are exposed into the shared context. Variables are values, but a field key resolves to its exposed field object—use .value to read the stored answer. A nested repeatable value should be accessed through its local item/parent context rather than assumed to be a top-level value.
This can show a short internal reminder to a member. It must not authorize a regulated decision or expose data to a public participant.

The current item and tree shortcuts

Each item has a location in the form tree. KayanOS gives an item local shortcuts so an expression does not need a fragile path across the entire form. Use a sibling value for a local condition. This is clearer and safer than relying on a long, brittle path.
If requires_inspection can be absent, handle that outcome deliberately instead of treating an error/empty value as approval.
The builder also maintains item paths such as child and repeater-instance paths to construct context and autocomplete. Treat these paths as builder navigation aids, not as a public integration contract or a substitute for a field key.

Values by field type

The expression editor provides representative shapes so a builder can see how an item is expected to behave. The actual saved value still depends on the field configuration and user input. For a multi-select field, test the actual array behavior rather than copying a single-select equality check:
Only use a function when it is listed for the form context. The expression editor may display a general helper that the offline validator or runtime blocks.

Exposed item objects and nested arrays

Autocomplete mirrors the public form objects used by expressions. Every item exposes key, path, and a category discriminator in type (for example, field or section); fields also expose their concrete input kind in fieldType, plus title, tooltip, value, defaultValue, editable, required, validation, isValid, and index. Selectors add selectionType, options, and either record or records. Money fields add currency, and file fields add multiple, accept, and maxSizeBytes. Sections expose title, description, editable, children, index, and isValid. Repeaters expose repeaterTitle, minRepeat, maxRepeat, requestedRepeat, actualRepeat, instances, repeatType, and isValid; section repeaters also expose layoutMode, tableResponsiveMode, and tableColumns. Actions expose title, description, clickable, loading, actionType, isValid, and calls. Action-type-specific properties such as linkTarget and docxFieldKey appear only when that action supports them. Array suggestions use one representative element so that typing after an array mapper such as .signatures[. or .instances[. can reveal its properties. This sample element is autocomplete metadata, not proof that the live array is non-empty.

Signature object

A signature item exposes the current and superseded signing state: Each signature summary exposes id, requestId, ruleKey, memberId, resolution, status, notes, reason, signingCapacity, signedAt, symbolicSignature, symbolicSignatureHash, and stale. Treat signingCapacity and symbolicSignature as evidence payloads whose internal structure can evolve; do not write policy rules against undocumented nested fields.
An empty result means no matching current signature. It does not mean that a stale signature is current, that prerequisites were satisfied, or that the participant is authorized to approve.

Repeatables: context changes per instance

Use a repeater when the Citizen Service Request needs a variable number of observations, documents, or inspection rows. Each instance has its own local values. A rule inside an observation row should use that row’s value/siblings, not a request-wide value that happens to share a label.

User-controlled repeaters

In a user-controlled repeater, a member or participant adds/removes instances within the configured bounds. $index identifies the current instance position for wording such as “Observation 2”; it is not a stable record ID and can change if rows are reordered or removed.
Validate the actual values in every instance. A repeat count alone must never decide whether a request is eligible for approval.

Loop-mode repeaters

In a loop-mode repeater, $item is available for the source item and $index is its position. Use a reviewed fallback for an optional property.
Test the first item, a later item, an empty source item, and no source items. Do not assume $item exists in a user-controlled repeater.

A simple local rule

For a repeatable observation, make the note required only when the current row is marked unsafe:
Then test safe, unsafe, missing, first-row, later-row, and newly added-row cases. A visibility or required expression changes the form experience; the server-side workflow still owns the final transition/approval decision.

Regular versus offline forms

Offline forms are a constrained form type, not regular forms delayed until connectivity returns. The form builder disables actions, variables, entity-selection fields, form-session-selection fields, entity-backed option sources, public links, and prefilled links for offline structures. It also blocks data/permission-dependent expression functions. For an offline inspection form, keep expressions local and deterministic:
Do not use form lookups, permission helpers, or a selected entity/session as the condition that makes an offline submission valid. See Offline forms for package, local-file, and synchronization behavior, and Form expressions and lookups for blocked lookup/permission functions.

Route a session to a role at an entity

A dynamic role scope is a form-level routing item. It resolves a role and an entity when a regular form session is saved. Configure the role and routing separately. For example:
  • Role key: printing_manager
  • Scope Type: locations
  • Routing Code: $.location.value
The entity returned by Routing Code must belong to Scope Type. The code can use other form fields and may return one qualified entity reference, an array of references, or null. Existing forms that use roleScope(roleKey, entityId) continue to run, but new routing items do not require that helper.
  • null means “no route” and is valid.
  • A non-null route whose role or entity does not exist, belongs to another organization, or does not match Scope Type blocks the save.
  • Routing attaches the session to a role at an entity; it does not grant List, Open, Update, Delete, or action verbs.
  • An empty role still receives the routed backlog. No session or member record is rewritten when a member is assigned to that role later.
  • Builder preview is diagnostic only. The server resolves and validates the route again before persisting it.
  • Offline forms do not support dynamic role scopes.
In role permissions, each enabled form verb uses one binding mode. Entity scope grants that verb for sessions of the form at the selected entity scope. Role at entity grants it only for sessions of that form routed to this role at that entity. When a form supports both modes, select the intended mode per verb; the modes are not combined.

Build and test a context-dependent rule

Use this small, repeatable test protocol:
  1. State the decision in plain language: “Show the inspection details when requires_inspection is true in this request.”
  2. Put the rule on the field/section that owns the decision, not a distant item.
  3. Use the smallest local root or sibling reference that expresses it.
  4. Test member and public sessions if the form supports both; confirm they see only the intended harmless difference.
  5. Test absent/null, false, true, first/later repeater instance, and a saved/reopened session.
  6. Test the corresponding server action or submission. Verify an unauthorized actor remains denied even if they can force a client-side value.
  7. For offline forms, run the offline validator and test a real synchronized submission before publishing.

Common mistakes