Unify Logo Footer.svg
Unify Applications
Logo
Data Binding Overview

Data Binding Overview

Logo

7 mins READ

Data Binding Overview

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

#RuleWhat it means
1Write an expression anywhere a property accepts oneA block label, a visibility condition, a data source input, an action parameter — if you can type into it, you can usually bind it.
2The expression sees the whole pageEvery 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.
3It recomputes when its inputs changeWhen 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 found // Conditional {{ order.status === 'open' ? 'Active' : 'Closed' }} // Loading state {{ orders_query.isLoading ? 'Loading...' : '' }}

How It Works

  1. Detect {{ }}: Any string containing a complete {{ }} pair is classified as dynamic. Plain text without the braces is returned as-is.

  2. 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, and utils are always available. See Binding Context for the full list.

  3. 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.

  4. 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 writeResult typeExample
A single binding aloneKeeps the referenced type — array stays array, object stays object, number stays number{{ tags_array }}["a", "b"]
Text mixed with a bindingAlways produces a stringHello {{ name }}!"Hello Priya!"
Two adjacent bindings with no textJavaScript 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 needed — as the user types in search_input, // the filter expression recomputes and the table updates.

The State Explorer

The State Explorer panel in the builder — "State available for this input. Click to map." — shows all values available for the current binding field. 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 with the actual data shape from the Output panel.

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 Output panel.

This Section

PageWhat it covers
Expression SyntaxPath bindings vs full expressions, type rules, what's invalid.
Binding ContextThe full namespace — blocks, data sources, variables, fixed namespaces like userContext, theme, utils.
Built-in UtilitiesThe utils helpers for formatting dates, numbers, JSON, and downloading files.
Row-scoped BindingsHow currentRow and instances work inside Repeatables and Tables.
Filters, Sorting & PaginationWiring the Filters block to data sources; condition builders and sort controls.

Common Binding Patterns

Getting the logged-in user's email

{{ userContext.email }} {{ userContext.id }} {{ 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 and error state {{ orders_query.isLoading }} {{ orders_query.error?.message }}

Reading a page variable

// A page variable named "selected_tab" exposes { value, setValue } {{ selected_tab.value }} // Setting it from a binding/script {{ selected_tab.setValue('billing') }}

URL and navigation values

// Named URL path segment (/orders/:orderId) {{ pageParams.orderId }} // Values passed from another page {{ pageInputs.customerId }} // Current URL path {{ location.pathname }}

Frequently Asked Questions

Why does my binding show nothing when I'm sure the data source has data?

There are three common causes: (1) The data source is set to Manual run behavior and hasn't been triggered yet — it returns empty values until first run. (2) The binding path is wrong — the field name in your expression doesn't match the actual field name in the response. Run the data source and inspect its Output panel to confirm the exact path. (3) The top-level type is a mismatch — a single-record source returns an object, not an array; binding the object where an array is expected renders nothing.

Do I need to refresh bindings when data changes?

No — bindings recompute automatically when any referenced value changes. When a data source finishes loading, a variable is set, or a user types into an input, every expression that reads those values updates without any manual refresh action. The only exception is server-side data changes that don't change any bound input — for those, use polling or an explicit Trigger Data Source action.

Can I use bindings inside action parameters, not just block properties?

Yes — the same {{ }} syntax works in action parameters: navigation URLs, notification messages, API request bodies, and variable set values all accept binding expressions. This is how you pass the current user's ID to an API call, or build a dynamic navigation URL from page state.

What's the difference between data and rawData on a data source?

data is the result after any response transform has run — the shape your blocks should bind to. rawData is the untransformed HTTP response, exactly as the API returned it. When no transform is configured, the two are identical. Use rawData for debugging or when you need the original payload alongside a transformed result.