Evaluation and Safety
This page describes how UnifyApps evaluates binding expressions, the safety guarantees built into the evaluation engine, how to handle errors gracefully with fallbacks, and the performance characteristics of the reactive evaluation model.
Evaluation Model
UnifyApps evaluates expressions using a lazy, dependency-tracking model. When a block property contains a binding expression, the engine:
Parses the expression and identifies all referenced names (block IDs, variable names, data source names, namespace keys).
Registers those names as dependencies of this expression.
Evaluates the expression immediately with the current values of those dependencies.
Re-evaluates whenever any dependency's value changes.
This is the mechanism behind every reactive pattern in UnifyApps — a search input's change triggers re-evaluation of any expression that reads it, which may trigger a data source re-fetch, which in turn triggers re-evaluation of anything that reads the data source output. The chain runs automatically without event wiring.
Circular Dependency Detection
When expression A depends on expression B and expression B depends on expression A (directly or transitively), the engine detects the cycle before it can cause an infinite loop. The cycle is broken at the last link, and both expressions receive their last computed value. A warning appears in the builder's Output panel identifying the circular path.
Warning: When a circular dependency is detected and broken, the affected expressions are frozen at their last computed value. The page does not crash, but the values shown may be stale. Always resolve circular dependencies by restructuring — typically by introducing a page variable as an intermediary.
Null Safety and Auto-Coalescing
UnifyApps implements automatic null-coalescing for property access. When you write {{ user.address.city }} and address is null or undefined, the expression returns undefined rather than throwing a TypeError: Cannot read properties of null. This is equivalent to writing {{ user?.address?.city }} in plain JavaScript.
All property chains in binding expressions benefit from this protection automatically — you do not need to add ?. at every step (though doing so works too and may improve readability).
Safe deep property access in expressions
// Safe — returns undefined if any part of the chain is null {{ order.customer.billingAddress.city }} // Equivalent but more explicit {{ order?.customer?.billingAddress?.city }} // Array access with null safety {{ orders[0].status }} // undefined if orders is empty, not an error {{ orders.find(o => o.id === selectedId)?.status }} // safe .find + access
Expression Timeout
Each expression is subject to an evaluation timeout of 100 milliseconds (default). If an expression takes longer than 100ms to evaluate — typically caused by an expensive computation over a large dataset — it is terminated and returns undefined. This protects the UI from expressions that would block rendering.
Tip: If an expression times out because it is processing a large array (sorting, filtering, aggregating thousands of records), move the computation into a Page Function — a named JavaScript function defined in the page's Functions panel. Page Functions run outside the 100ms expression timeout and their results are cached between calls with the same arguments.
Error Handling in Expressions
Nullish Coalescing Fallback
Use the ?? operator to provide a fallback when an expression returns null or undefined:
Nullish coalescing for display fallbacks
// Show "Unnamed" when displayName is null or undefined {{ user.displayName ?? "Unnamed" }} // Show 0 when count is undefined (data source still loading) {{ orders.data.totalCount ?? 0 }} // Chain multiple fallbacks {{ record.nickname ?? record.displayName ?? record.email ?? "Unknown user" }}
The tryEval Built-in
The tryEval(expression, fallback) built-in wraps an expression evaluation in a try/catch and returns the fallback value if the expression throws or times out. Use it for expressions that may genuinely throw — for example, calling a function that may not exist:
tryEval for error-tolerant expressions
// Safely call a utility that may not always exist {{ tryEval(utils.parseJSON(rawString), {}) }} // Safely parse a date that may be in an unexpected format {{ tryEval(new Date(dateString).toLocaleDateString(), "Invalid date") }} // Safely access a deeply nested path that may not always exist {{ tryEval(response.data.metadata.tags.join(", "), "No tags") }}
onError Binding Property
Some data-bound components accept an onError property — a binding expression whose value is displayed when the component's primary binding fails. This is a component-level fallback, not an expression-level one:
Component onError fallback
// Table block data: {{ fetchOrders.data.objects }} onError: {{ "Failed to load orders: " + fetchOrders.error?.message }}
Performance Characteristics
Memoization
The evaluation engine memoizes expression results: if an expression's dependencies have not changed since the last evaluation, the cached result is returned without re-running the expression body. This means that even if many blocks reference the same expression, it only runs once per dependency change — not once per block that reads it.
What Triggers Re-evaluation
| Change | Re-evaluates |
| User types into a bound text input | All expressions that reference that input's .value |
| A data source finishes loading | All expressions that reference that data source's .data, .isLoading, or .error |
| A page variable is updated via Set Variable action | All expressions that reference that variable's .value |
| A URL parameter changes (navigation) | All expressions that reference pageParams or pageInputs |
| A table row is selected | All expressions that reference table.selectedRow or table.selectedRowIds |
Security Sandbox
Binding expressions run in a restricted execution sandbox that provides safety guarantees for multi-tenant app environments:
| Restriction | Enforcement |
No window access | Expressions cannot read or write window.* properties. The global namespace is limited to page-context values and built-ins. |
No document access | Direct DOM manipulation is blocked. Blocks must be controlled through builder properties, not script. |
No eval | Dynamic code evaluation inside an expression is blocked — e.g. eval("2+2") throws. |
No fetch / XMLHttpRequest | Expressions cannot make network requests. All data fetching goes through configured data sources. |
No localStorage / sessionStorage | Browser storage access is not available in expressions. Use page variables for transient state. |
Note: JavaScript defined in a Page Function runs with broader access than a binding expression — it can use fetch, browser APIs, and console.log for debugging. Use Page Functions when you need capabilities that expressions deliberately restrict.
Frequently Asked Questions
My expression silently returns nothing — how do I debug it?
Open the builder's Output panel and select the block whose expression is failing. The Output panel shows the last evaluated value of each bound property, plus any errors that occurred during evaluation. The most common silent failures are: (1) referencing a block by display name instead of ID, (2) the data source is set to manual run and hasn't been triggered yet, (3) the path is wrong — use the State Explorer to confirm the exact path by clicking the value you want, which inserts the correct expression automatically.
How do I write an expression that performs a heavy computation without hitting the 100ms timeout?
Define a Page Function in the page's Functions panel. Page functions run outside the expression timeout and support full JavaScript including loops, array methods, and async operations. Call your function from a binding expression: {{ myPageFunction(param1, param2) }}. The result is cached between calls with the same arguments, so the function body runs only when its inputs change — not on every render.
Does the sandbox prevent using lodash or other libraries in expressions?
Standard JavaScript built-ins are fully available in expressions — Array, Object, Math, Date, JSON, String, and all their methods. Third-party libraries like lodash are not available in binding expressions. If you need lodash utilities, add them in a Page Function using the platform's custom code import mechanism.