Data binding is how you make any property in the builder dynamic. Instead of typing a fixed value, you write an expression inside double curly braces — {{ }} — and reference live data: another block's state, a data source result, a page variable, the signed-in user, the current URL. The moment a value contains {{ }}, the builder treats it as an expression rather than plain text.


The Three Rules
| # | Rule | What it means |
| 1 | Write an expression anywhere a property accepts one | A block label, a visibility condition, a data source input, an action parameter — if you can type into it, you can usually bind it. |
| 2 | The expression sees the whole page | Every binding evaluates against a shared picture of your app: blocks by name, data sources, page variables, page functions, user context, theme, URL parameters, and built-in utilities. |
| 3 | It recomputes when its inputs change | When a referenced value changes — a user types, a data source finishes, a variable is set — every expression that reads it recomputes and the UI updates. You never manually refresh a binding. |
Basic Syntax
Wrap any expression in double curly braces. Everything between {{ and }} is evaluated as JavaScript against the page's data context:
Basic binding examples
Simple field reference:
Hello {{ user_name_input.value }}!Data source result:
{{ orders_query.data.objects }}Computed value:
{{ orders_query.data.objects.length }} orders foundConditional:
{{ orders_query.data.objects?.[0]?.status === 'open' ? 'Active' : 'Closed' }}Loading state:
{{ orders_query.isLoading ? 'Loading...' : '' }}
How It Works
Detect {{ }}: Any string containing a complete
{{ }}pair is classified as dynamic. Plain text without the braces is returned as-is.Resolve names against the page context: Every entity on the page — blocks, data sources, variables, functions — is in scope by its name. Fixed namespaces like
userContext,theme, andutilsare always available. See Binding Context for the full list.Evaluate the expression: The content between the braces is evaluated as real JavaScript. A bare path reference (
{{user.name}}) returns the value; an expression ({{ qty * price }}) computes it.Render the result: A lone binding hands over the referenced value as-is (an array stays an array). Text mixed with bindings produces a string. An expression that fails produces an empty value — not an error screen.
Where Bindings Work
Bindings are accepted anywhere the platform evaluates config against the page context:
Block properties — labels, placeholder text, default values, visibility conditions, disabled states, styles
Data source inputs — the inputs to an API call or query, including filter values and paging parameters
Action parameters — the values passed to actions (navigate URL, notification message, variable value)
Page function bodies — the JavaScript code of a page function
Variable initial values — the starting value of a page variable
Condition expressions — visibility conditions, disable conditions, run conditions
Type Preservation
The type of a binding's result depends on what surrounds it:
| What you write | Result type | Example |
| A single binding alone | Keeps the referenced type — array stays array, object stays object, number stays number | {{ tags_array }} → ["a", "b"] |
| Text mixed with a binding | Always produces a string | Hello {{ name }}! → "Hello Priya!" |
| Two adjacent bindings with no text | JavaScript addition — numbers add, other types concatenate | {{ a }}{{ b }} where a=1, b=2 → 3 (not "12") |
Reactive Updates
Bindings recompute automatically when their inputs change — no polling or manual refresh. This is how a filter box narrows a table without event wiring: the table's data property is bound to a filtered expression, and as the user types, the expression recomputes and the table re-renders.
Example — Search-as-you-type without event handlers
Table's data property: {{ orders_query.data.objects.filter(o => o.customerName.toLowerCase().includes(search_input.value?.toLowerCase() ?? '')) }}
No event handler is needed. As the user types in search_input, the filter expression recomputes and the table updates.
The State Explorer
The State explorer panel — "State available for this input. Click to map." — shows all values available for the current binding field. It opens beside the editor when you configure a data source, page function or page variable, and beside an expression field when you open it with Expand. It lists the page's blocks, data sources, variables and other context as a tree. Clicking a value inserts its binding expression automatically, so you don't need to know entity IDs or exact paths by memory.
Failures Are Silent
An expression that fails — a reference to a non-existent name, a thrown error, a type mismatch — produces an empty value rather than an error screen. The page keeps rendering. This "forgiving" behavior is useful for partial states (a data source still loading), but it means bugs in bindings show up as missing content, not error messages. Always test bindings against the actual data shape: run the data source in its editor and check the Output tab.
Warning: Referencing an entity by display name instead of its correct binding path — e.g. {{ variables.myVar }} instead of {{ my_var.value }} — evaluates to undefined with no error. Always verify binding paths using the State explorer or the data source's Output tab.
This Section
Page | What it covers |
|---|---|
Path bindings vs full expressions, type rules, what's invalid. | |
The full namespace — blocks, data sources, variables, fixed namespaces like | |
When bindings are evaluated, and how null values and errors are handled. | |
The | |
How | |
Wiring the Filters block to data sources; condition builders and sort controls. |
Common Binding Patterns
Getting the logged-in user's email
Email:
{{ userContext.email }}User ID:
{{ userContext.id }}Name:
{{ userContext.name }}
Binding to a data source result
All records (multiple-record source):
{{ orders_query.data.objects }}Count:
{{ (orders_query.data.objects || []).length }}First record's field:
{{ orders_query.data.objects?.[0]?.customerName }}Loading state:
{{ orders_query.isLoading }}Error message:
{{ orders_query.error?.message }}
Reading a page variable
A page variable named selected_tab exposes { value, setValue }. Read it with {{ selected_tab.value }}.
setValue changes state, so call it only from an action or a script (for example selected_tab.setValue('billing') in a Run Script action), never in a label, visibility or other rendered binding: those recompute on every change and would call it again each time. To set a variable without code, use the variable actions in Data & Variable Actions.
URL and navigation values
Named URL path segment (/orders/:orderId):
{{ pageParams.orderId }}Values passed from another page:
{{ pageInputs.customerId }}Current URL path:
{{ location.pathname }}