Unify Applications
Logo
Conditions & Visibility

Conditions & Visibility

Logo

9 mins READ

How Conditions Work

The platform never stores show/hide or run/skip logic as a raw JavaScript expression. Instead it stores a condition object — a structured, typed filter evaluated by the runtime's operator engine. This makes conditions predictable, inspectable, and consistent across visibility, editability, and action gating.

visibility_and_conditions.png
visibility_and_conditions.png

The same condition builder powers three distinct features:

  • Visibility — show or hide a component based on data, roles, or variables.

  • Disabled / Read-only — control whether a user can interact with a component.

  • Only run when (runCondition) — gate an action so it only executes when the condition holds.

Learn the condition builder once and you can drive all three consistently. For how Only run when fits into an event's actions, see Events & Actions Overview. For conditions on form fields, see Form Validation and Visibility.

Build a Condition in the Editor

  1. Select the component and find its Visibility setting. Choose Conditions, the third icon after the eye (Visible) and the crossed-out eye (Hidden).

  2. Click Add visibility conditions. The conditions panel opens.

  3. In the Where row, enter the value to test as a binding, for example {{ var_showPanel['value'] }}.

  4. Choose an operator, for example Equals to or Is present, and enter the value to compare with if the operator needs one. The Operator Reference below lists the operators by their builder label. Operators marked as not available in the condition builder can be used only in JSON or in form-field validation.

  5. Click Add Condition to add another rule, or Add Condition Group to add a nested group of rules.

  6. Click Set to apply the condition, or Reset to clear it. The Visibility setting then shows how many conditions are set.

Only run when on an action works the same way: open the condition from the action's Only run when field.

Condition Object Shape

A condition object is always one of two forms:

Form 1 — boolean literal

{ "type": "boolean", "payload": true } { "type": "boolean", "payload": false }

Form 2 — filter group

{ "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ ds_currentUser['data']['role'] }}", "filter": { "operator": "EQUAL", "value": "admin" } } ] } }

Warning: Always wrap leaves in a group. Even for a single condition, write {"operator":"AND","filters":[<leaf>]} — this matches the builder's stored shape. A bare leaf at the root is parsed but is non-standard.

Component Visibility

Every component has a Visibility setting on its Appearance tab with three modes:

Mode

Icon

Behavior

Visible

Eye

The component is always visible in the app.

Hidden

Crossed-out eye

The component is hidden from users but still appears in the builder's hierarchy panel for editing.

Conditions

Settings (sliders) icon

Visibility is controlled by a condition object. The component only renders when the condition evaluates to true.

The stored shape for conditional visibility wraps the condition one level deeper:

JSON — block visibility with condition

"visibility": { "value": "conditions", "conditions": { "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ ds_currentUser['data']['role'] }}", "filter": { "operator": "EQUAL", "value": "admin" } } ] } } }

Note: Per-device visibility: Visibility settings can be different per device breakpoint. You can show a component on desktop and hide it on mobile by configuring the setting separately for each device in the builder.

Disabled and Read-only

Interactive components can also be locked with a condition object. Each setting means what its name says, so the same condition has opposite effects on different settings:

  • Disabled (appearance.disabled) — when the condition is true, the component is disabled and can't be used.

  • Read-only on forms and form fields (readOnly) — when the condition is true, users can see the value but can't change it.

  • Required is a validation rule, not an editability setting: it decides whether a field must be filled before the form submits. See Form Validation and Visibility.

Disabled and read-only can be a fixed true or false (a boolean condition), or driven by a filter condition — useful for role-based access or data-driven form control.

Example — disable the Save button until the form is valid

The condition describes when the button is disabled, so it checks that the form is not valid (isValid Equals to "false"). The button is enabled as soon as the form becomes valid.

"disabled": { "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ form_create.isValid }}", "filter": { "operator": "EQUAL", "value": "false" } } ] } }

Operator Reference

The condition engine supports a rich set of operators organized by data type. Each operator compares a property (a binding expression) against a value.

Group Operators

OperatorBehavior
ANDAll child conditions must be true
ORAt least one child condition must be true

Equality Operators

Operator

Label in the builder

Behavior

EQUAL

Equals to

Strict equality (===) with per-type coercion (numbers via Number(), objects via deep equals)

NOT_EQUAL

Does not equal

Strict inequality

IN

In

Property value is in the provided list

NOT_IN

Not in

Property value is not in the provided list

Presence Operators

Operator

Label in the builder

Behavior

Important Edge Cases

EXISTS

Is present

Property is not null/undefined

Empty string, 0, false, [], {} all return true

MISSING

Is not present

Property is null or undefined

Empty string, 0, false, [] all return false. MISSING does not detect empty arrays.

Warning: Common trap: MISSING only catches null and undefined. An API returning an empty array [] is NOT missing. Count the rows with ['length'] to detect empty arrays — see the recipe below.

Text Operators

Operator

Label in the builder

Behavior

CONTAINS

Contains

String includes value (case-sensitive). Not for arrays — use IN for array membership.

NOT_CONTAINS

Does not contain

Negation of CONTAINS

STARTS_WITH / NOT_STARTS_WITH

Starts with / Does not start with (not available in the condition builder; JSON or form-field validation only)

String starts with value

ENDS_WITH / NOT_ENDS_WITH

Ends with / Does not end with (not available in the condition builder; JSON or form-field validation only)

String ends with value

REGEX / NOT_REGEX

Matches regex / Does not match regex

Regular expression match (case-sensitive)

IREGEX / NOT_IREGEX

Matches regex case insensitive / Does not match regex case insensitive

Regular expression match (case-insensitive). Use these for case-insensitive text matching.

MIN_LENGTH

Minimum length

String length is at least n. String-only — arrays return false.

MAX_LENGTH

Maximum length

String length is at most n. String-only — arrays return false.

Number Operators

Operator

Label in the builder

Behavior

GT

Greater than

Greater than

GTE

Greater than or equal to

Greater than or equal to

LT

Less than

Less than

LTE

Less than or equal to

Less than or equal to

Date & Time Operators

Operator

Label in the builder

Behavior

DATE_IS_BEFORE

Is before

Date is strictly before value

DATE_IS_AFTER

Is after

Date is strictly after value

DATE_IS_ON_OR_BEFORE

Is on or before

Date is on or before value

DATE_IS_ON_OR_AFTER

Is on or after

Date is on or after value

TIME_IS_BEFORE / TIME_IS_AFTER

Is before / Is after (time fields; not available in the condition builder; JSON or form-field validation only)

Time comparison

TIME_IS_EQUAL_TO

Is equal to

Exact time match

TIME_IS_EQUAL_TO_OR_BEFORE / _AFTER

Is equal to or before / Is equal to or after

Time comparison with equality

Nesting Condition Groups

Groups can be nested recursively to express complex logic. An OR inside an AND is valid:

JSON — nested AND / OR group

{ "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ ds_currentUser['data']['isActive'] }}", "filter": { "operator": "EQUAL", "value": "true" } }, { "operator": "OR", "filters": [ { "property": "{{ ds_currentUser['data']['role'] }}", "filter": { "operator": "EQUAL", "value": "admin" } }, { "property": "{{ ds_currentUser['data']['role'] }}", "filter": { "operator": "EQUAL", "value": "manager" } } ] } ] } }

Recipe: Detect an Empty Data Source Result

A data source that returns [] is not MISSING, and a data source that hasn't run yet has no data at all. Use this pattern to detect the "no results" state correctly. It is a complete condition object, wrapped in {"type":"filter","payload":…}:

Recipe — show "No results" when data source returns empty array

{ "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ ds_orders['isLoading'] }}", "filter": { "operator": "EQUAL", "value": "false" } }, { "property": "{{ ds_orders['error'] }}", "filter": { "operator": "MISSING" } }, { "property": "{{ ds_orders['data'] }}", "filter": { "operator": "EXISTS" } }, { "property": "{{ ds_orders['data']['objects']['length'] }}", "filter": { "operator": "EQUAL", "value": "0" } } ] } }

The rows sit inside the response at a path that depends on the data source — ['data']['objects'] here — so use the same path you bind the rows to, plus ['length']. Don't use data MISSING to mean empty: it is also true before the data source has run, while it loads and when it fails.

Where Conditions Apply

The condition object is used in these places:

ContextProperty PathShape
Block visibilityblock.visibility{"value": "conditions", "conditions": {type,payload}}
Block disabled stateblock.appearance.disabled{"type": "filter"|"boolean", "payload": ...}
Block loading stateblock.appearance.loading{"type": "filter"|"boolean", "payload": ...}
Form read-onlyblock.readOnly{"type": "filter"|"boolean", "payload": ...}
Action run conditionaction.runCondition{"type": "filter"|"boolean", "payload": ...}
Table cell conditional formattingcolumn.conditions{"type": "filter"|"boolean", "payload": ...}
Data source disabled (Disable query on the Advanced tab; see Run Behavior & Triggering)queryDisabled{"type": "filter"|"boolean", "payload": ...}

Warning: Do not conflate shapes. The visibility property uses {"value": "conditions", "conditions": {…}}, which is different from disabled and loading, which accept the condition object directly. Using the wrong shape silently does nothing.

Referencing Data in Conditions

The property field in each leaf is a binding expression — any {{ }} expression is valid. This means conditions can reference:

  • Page variables: {{ var_abc['value'] }}

  • Current user: {{ userContext.email }}, {{ userContext.id }}

  • Data source output: {{ ds_orders['data']['length'] }}

  • Component state: {{ form_create['isValid'] }}

  • URL parameters: {{ pageInputs['status'] }}

Note: Silent failures: A binding in a condition that fails to resolve is treated as undefined. MISSING will fire; EXISTS will not. A broken binding in a visibility condition makes the block behave as if the condition returned nothing — meaning it may show or hide unexpectedly.

Common Patterns

Role-Based Visibility

Show a component only to admins, using the role returned by a data source that loads the current user's record (here ds_currentUser):

JSON — admin-only visibility

"visibility": { "value": "conditions", "conditions": { "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ ds_currentUser['data']['role'] }}", "filter": { "operator": "EQUAL", "value": "admin" } } ] } } }

Variable-Driven Visibility

Show a panel only when a toggle variable is true:

JSON — show panel when toggle is on

"visibility": { "value": "conditions", "conditions": { "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ var_showPanel['value'] }}", "filter": { "operator": "EQUAL", "value": "true" } } ] } } }

Gate an Action with runCondition

Only send an email notification when a checkbox is checked:

JSON — action with runCondition

{ "actionType": "controlDataSource", "payload": { "dataSourceId": "ds_sendEmail", "method": "trigger" }, "id": "act_email", "runCondition": { "type": "filter", "payload": { "operator": "AND", "filters": [ { "property": "{{ checkbox_notify['value'] }}", "filter": { "operator": "EQUAL", "value": "true" } } ] } } }

Edge Cases and Gotchas

EQUAL with empty string

EQUAL coerces numbers via Number(). An empty string converted to a number is NaN, which is never equal to anything — so EQUAL will always fail if the property binding resolves to "".

Comparing booleans

The value box always stores text, so write booleans as "true" or "false", as the examples on this page do. Equals to converts the value to the type of the property before comparing, so "true" matches a boolean true — for example {{ var_showPanel['value'] }} Equals to "true".

MIN_LENGTH / MAX_LENGTH are string-only

These operators check typeof value !== 'string' first. An array returns false regardless of length. Use EQUAL with the ['length'] suffix instead for arrays.

Empty filter groups

An empty AND group (filters: []) evaluates to true (match all). An empty OR group evaluates to false (match none). Never write empty groups intentionally — always include at least one leaf.

FAQs

What is the difference between Visible, Hidden, and Conditions modes?

Visible always shows the block. Hidden always hides it at runtime (the block is not rendered); it stays in the Hierarchy panel so you can edit it. Conditions evaluates a filter object at runtime and shows the block only when the result is true. The stored shapes are {"value": true}, {"value": false} and {"value": "conditions", "conditions": {...}}.

Can I reference data source results or variables in a visibility condition?

Yes. Condition fields accept binding expressions ({{ ds_user['data']['role'] }}, {{ var_isAdmin['value'] }}). The condition is re-evaluated reactively every time a referenced value changes, so the block shows or hides automatically.

Why should I use EXISTS/MISSING instead of EQUAL to null?

A field can be absent from an object (missing entirely) or explicitly set to null. EQUAL null never matches: a null or missing property fails every EQUAL. MISSING matches both null and undefined — it is the reliable "this field has no value" check.

Can I nest AND/OR groups more than one level deep?

Yes. Each filter entry in a group can itself be an operator: "AND" or operator: "OR" group with its own filters array. There is no enforced depth limit, but conditions are easier to read and debug when kept to two or three levels.

What happens when a condition references a variable that hasn't been set yet?

The referenced value resolves to undefined. Most operators treat undefined as a no-match — so an EQUAL check against an unset variable evaluates to false, hiding the block. If you want the block to be visible by default until the variable is explicitly set to a hiding value, flip the logic: use MISSING to show when the variable is absent.