> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kayanos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Form expressions and lookups

> Build readable form rules, repeatables, UI checks, and controlled record lookups without treating an expression as authorization.

## When to use form expressions

Use a form expression to derive a default, control visibility or editability, compute a value, shape action text, or look up service data for the current form experience. It is not a server-side permission system and it is not a substitute for a workflow approval.

The full callable catalog is in [Form helpers and lookup filters](/reference/expressions/helpers/forms). This guide explains how to use those helpers safely.

![KayanOS form field settings for the Citizen Service Intake form, including visibility, repeatable, and validation-expression controls.](https://kayanos.app/docs-images/en/reference/expressions-forms-and-lookups.png)

## Form roots

| Root                                                                                       | Meaning                                                                                                  |
| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `$this`                                                                                    | The node currently evaluating the expression.                                                            |
| `$form`                                                                                    | The exposed form object.                                                                                 |
| `$values`                                                                                  | Raw variables and field answers in one consistent value-only tree. Prefer this root for new expressions. |
| `$`                                                                                        | Form variables directly, plus exposed child objects. Read a field answer through its `.value` property.  |
| `$parent`, `$siblings`, `$grandparent`, `$uncles`                                          | Nearby nodes in the form structure.                                                                      |
| `$repeatables`, `$repeatable_instances`                                                    | Repeatable containers and the current instance map.                                                      |
| `$index`, `$item`                                                                          | Current repeatable-loop index and item. `$index` falls back to `0`.                                      |
| `$value`, `$defaultValue`                                                                  | Current field value and configured default value.                                                        |
| `organizationId`, `formId`, `formVersionId`, `sessionId`, `initiatorId`, `currentMemberId` | Qualified identifiers for the active form/session/member.                                                |
| `currentUserType`                                                                          | `"public"`, `"member"`, or no value.                                                                     |

Use small, local expressions. A condition for one field should usually depend on that field’s siblings or a clear form value, not on a long path across several repeaters.

```jexl theme={null}
$values.status == "submitted" && $values.request_amount > 1000
```

Legacy `$.status` and `$.request_amount.value` expressions remain supported. The expression editor suggests their `$values` equivalents.

```jexl theme={null}
$siblings.requires_inspection.value == true
```

## Repeatable patterns

In a looped repeatable, `$item` is the source loop item and `$index` is its zero-based position. In a user-controlled repeatable, use the instance/sibling values that exist in the current form structure.

```jexl theme={null}
`Inspection {{ $index + 1 }}: {{ $item.site_name || "site not supplied" }}`
```

Do not use a repeatable count as a hidden policy gate. Validate the actual field values that make a request eligible.

## UI checks are not authorization

`hasVerb`, `hasVerbOnRecord`, `hasScope`, and `hasAnyScope` can adapt what a member sees in a form. They do not grant access. In particular, verb helpers return `true` during server evaluation, so an expression using them cannot be your only enforcement point.

```jexl theme={null}
hasVerb("review") && $.service_category.value == "regulated"
```

Use this to show a reviewer-oriented block. Keep actual authorization and state-transition rules in the relevant server-side service policy.

## Record lookup: keep it narrow, private, and non-authoritative

`getRecord` and `getRecords` are form data conveniences. They can return an empty/null-like result when the form context is unavailable or a lookup times out. Their backing lookup runs through a system-level data path; it does **not** filter results using the current participant’s ordinary record or field permissions, role, or scope. It also has no lookup-limit argument, so a broad filter can load every matching record.

That makes three boundaries explicit:

1. `hasVerb*` and `hasScope*` can change presentation only; they do not authorize an action.
2. `getRecord` and `getRecords` must never decide whether a participant is authorized or entitled to data.
3. A lookup result must never be rendered to a public or otherwise unauthorized participant. A hidden field is not an access-control mechanism.

Use a selective filter against an entity and fields deliberately designed for this purpose, then safely handle no result:

```jexl theme={null}
getRecords("licenses", f.and(
  f.eq("status", "approved"),
  f.eq("holder_id", $.applicant_id.value)
))
```

Do not copy this as a broad public lookup pattern. Do not query an entity that carries personal, financial, or operational data merely to decide visibility. Prefer a small purpose-built record with only the minimum non-sensitive fields, an equality filter tied to the current request, and an owner-reviewed output. Before publishing a public form, test with a public participant and prove that no value returned by a lookup is rendered, exported, or used to authorize an action for that participant.

### Build filters with `f.`

The `f.` functions build a database filter object for `getRecords`; they do not filter an arbitrary list in memory. The most useful pattern is a small conjunction:

```jexl theme={null}
f.and(
  f.eq("status", "approved"),
  f.gte("score", 80),
  f._in("district", ["central", "north"])
)
```

Use `f._in` when a readable, unambiguous inclusion helper is preferred. Test the exact field keys and stored option values. Do not build a broad lookup and then hide results with a later expression.

## Weekdays and service shifts

`isWeekend(date, entityId?)` and `isWeekday(date, entityId?)` can use configured shift work days when an applicable entity and service data are available. Without that data, they fall back to Saturday/Sunday. Treat them as online regular-form helpers and test the target organization’s schedule.

```jexl theme={null}
isWeekday($.appointment_date.value, $.assigned_position_id.value)
```

## Offline form limits

Offline forms block `getRecord`, `getRecords`, `hasVerbOnRecord`, `hasVerb`, `hasScope`, and `hasAnyScope`. They also restrict several data-backed form features. Keep offline expressions self-contained: field values, simple conditions, and deterministic general helpers.

```jexl theme={null}
$.requires_inspection.value == true && isNotEmpty($.site_address.value)
```

Validate an offline form before publishing. Do not rely on a function that happens to be suggested by the editor if the offline validator blocks it.

## Test matrix

| Test                 | What to verify                                                                                 |
| -------------------- | ---------------------------------------------------------------------------------------------- |
| Public/member        | `currentUserType` produces the intended harmless UI difference.                                |
| Scope/verb           | The UI adapts for the intended member, but server policy still rejects an unauthorized action. |
| Empty lookup         | A lookup returning no rows leaves the form understandable and safe.                            |
| Public lookup safety | A public participant cannot see, export, or gain access from any returned lookup value.        |
| Timeout              | A slow lookup does not turn an unknown result into an approval.                                |
| Repeatable           | `$index` and `$item` resolve for first, later, and empty loop items.                           |
| Offline              | The form saves/publishes with no blocked data/permission function.                             |

## Related guides

* [Forms, fields, and repeatables](/build/form-fields-and-repeatables)
* [Form actions, logic, and signatures](/build/form-actions-logic-and-signatures)
* [Expression contexts and availability](/reference/expressions/contexts)
