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.
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.
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.
requires_inspection can be absent, handle that outcome deliberately instead of treating an error/empty value as approval.
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:
Exposed item objects and nested arrays
Autocomplete mirrors the public form objects used by expressions. Every item exposeskey, 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.
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.
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.
$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: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: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
null. Existing forms that use roleScope(roleKey, entityId) continue to run, but new routing items do not require that helper.
nullmeans “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.
Build and test a context-dependent rule
Use this small, repeatable test protocol:- State the decision in plain language: “Show the inspection details when
requires_inspectionis true in this request.” - Put the rule on the field/section that owns the decision, not a distant item.
- Use the smallest local root or sibling reference that expresses it.
- Test member and public sessions if the form supports both; confirm they see only the intended harmless difference.
- Test absent/null, false, true, first/later repeater instance, and a saved/reopened session.
- Test the corresponding server action or submission. Verify an unauthorized actor remains denied even if they can force a client-side value.
- For offline forms, run the offline validator and test a real synchronized submission before publishing.

