Unify Logo Footer.svg
Unify Applications
Logo
Page Functions

Page Functions

Logo

10 mins READ

What Is a Function?

A function is a named piece of JavaScript defined in the builder that computes a value from your page's live context. Where a variable stores a value, a function computes one. You bind to a function's result anywhere in the builder that accepts a binding expression, and the platform keeps that result up to date.

Functions let you encapsulate computation that is too complex or too verbose to write inline in a binding expression. Instead of scattering long JavaScript in dozens of component properties, you write it once in a function and reference the function by name wherever you need the result.

ConceptVariableFunction
Stores a valueYes — written by actionsNo — computed from context
Updated bySet Variable actionAutomatic (reactive) or Trigger Function action
Can be read in bindingsYesYes
Can be changed in bindingsNo (read-only in bindings)No (read-only in bindings)
Has side effectsN/ANo — pure computation only

Creating a Function

Functions are created from the Data panel in the builder's left rail (the same panel that lists data sources and variables).

  1. Open the Data panel: Click the Data icon on the left icon rail. The Data panel shows three tabs or sections: Data Sources, Variables, and Functions.

  2. Add a new function: Click the + Function button. A new function entry appears with a default name. Click the name to rename it — choose something descriptive like totalRevenue or filteredOrders.

  3. Write the function body: The function body is plain JavaScript. The last expression evaluated, or an explicit return statement, produces the function's value. You have access to the full page context: variables, data sources, and block state.

  4. Set the run behavior: Choose Automatic (re-runs whenever a referenced dependency changes) or Manual (only runs when triggered by the Trigger Function action). Automatic is the default.

  5. Save and test: Click Save & Run to evaluate the function immediately and see the output in the panel. The Output pane shows the computed value or any error with its message and stack trace.

Writing the Function Body

The body is standard JavaScript — no special syntax is required. The platform evaluates it against the page's live context, so you can reference any page state directly by name.

💻 Function body examples

// Sum line items from a data source query result const items = ordersQuery.data ?? []; return items.reduce((sum, item) => sum + item.amount, 0); // Filter a list based on a variable const query = searchVariable.toLowerCase(); return productsQuery.data.filter(p => p.name.toLowerCase().includes(query) ); // Format a date from a page parameter const raw = pageParams.startDate; return new Date(raw).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); // Derive a status label from multiple fields const { shipped, delivered, cancelled } = orderQuery.data ?? {}; if (cancelled) return 'Cancelled'; if (delivered) return 'Delivered'; if (shipped) return 'Shipped'; return 'Processing';

What Functions Cannot Do

Functions are intentionally constrained to pure computation. This makes them predictable and safe to re-run automatically. The following are not supported in a function body:

  • Navigating to a page

  • Writing to a variable

  • Checking authentication or session state

  • Making API calls directly (use data sources for this)

  • Importing external modules (import / require)

  • Using third-party libraries

Note: Custom scripts (a different concept from functions) do have access to an actions helper that provides navigation methods like actions.navigateToPage(…) and actions.setURLParameters(…). Functions do not have this helper. If you need side effects, use an action chain instead.

Function State

Every function exposes a state object that blocks and other functions can read in bindings. The state object has three fields:

FieldTypeDescription
valueAnyThe result of the last successful run. undefined if the function has never run or if it threw on every run.
errorObject or nullIf the last run threw an error, this contains the error message and stack trace. null if the last run succeeded.
isEvaluatedBooleantrue if the function has run at least once since the page opened. false until the first run completes.

{} Binding to function state

// Read the computed value {{ totalRevenue.value }} // Show a loading skeleton until the function has run {{ totalRevenue.isEvaluated ? totalRevenue.value : '...' }} // Display an error message if the function threw {{ totalRevenue.error?.message ?? '' }} // Use the value in a conditional {{ totalRevenue.value > 10000 ? 'High volume' : 'Normal' }}

Automatic Functions

An automatic function behaves like a spreadsheet cell with a formula — it re-evaluates whenever any of its dependencies change. This is the default run behavior.

How Dependency Tracking Works

The platform detects dependencies by parsing the binding syntax ({{ }}) in the function body. Any page state referenced in a standard binding expression becomes a tracked dependency. When that state changes, the function is scheduled for re-evaluation.

Warning: Dependency tracking reads only the bindings that appear in standard {{ }} syntax. If you construct a state reference dynamically inside your JavaScript (for example, by accessing window.someGlobal or by computing a property name at runtime), the platform does not detect that dependency and the function does not re-run when it changes.

Waiting for Data Sources

Automatic functions wait for automatic data sources to resolve before running for the first time. This prevents the function from computing a result based on an empty data set and then re-running once the data arrives. Manual data sources (those set to run only when triggered) do not hold back automatic functions.

Re-run Coalescing

When multiple dependencies change in rapid succession — for example, when a page loads and several variables initialize at once — the platform coalesces the pending re-runs into a single evaluation rather than running the function once for each change. This keeps the function's output stable and avoids unnecessary intermediate states.

Automatic Functions Have No Row Context

An automatic function runs once per page evaluation cycle. It does not run once per row of a list or table. If you need to compute a per-row value, write the expression directly in the cell's binding, or use a manual function triggered from a row event.

Note: For values that must be computed per row — for example, a formatted price label for each row of a table — write the expression inline in the cell's binding using the available row context variable (currentRow or similar). An automatic function bound to the cell's property would receive the whole list, not the individual row.

Manual Functions

A manual function does not re-run on its own. Its value stays undefined and isEvaluated stays false until the first Trigger Function action fires. Subsequent triggers re-evaluate it and update its state.

When to Use Manual Functions

Use a manual function when the computation should respond to an explicit user action rather than tracking data changes continuously. Common examples:

  • Validating a form when the user clicks Submit — compute validation errors on demand.

  • Computing a preview value when the user clicks a Preview button.

  • Running an expensive derivation only when explicitly requested, not on every keystroke.

The Trigger Function Action

Wire the Trigger Function action to any event — a button click, a form submit, a timer, or any other trigger. The action has a single required configuration: which function to run. After the function evaluates, you can branch on its result using the action chain's success and error branches.

BranchWhen it firesAvailable context
On successThe function evaluated without throwingThe function's value is available in downstream bindings
On errorThe function threw an exceptionThe function's error state is populated with message and stack

Function Scope: Page vs. App

Functions exist in two scopes. Choosing the right scope determines where a function's result can be referenced.

ScopeDefined inVisible toContext available
Page functionData panel on a specific pageOnly the page it belongs toFull page context: variables, data sources, block state, page parameters
App functionApp-level Data panel (accessible from the navigator)Any page in the applicationApp-level context only (no individual page variables or data sources)

Use page functions for logic that is specific to one page's data model. Use app functions for utility computations that multiple pages need — for example, a date formatting helper, a currency converter, or a permission checker.

Gotchas and Edge Cases

Functions behave predictably when you follow the constraints above, but there are a few edge cases worth knowing before you rely on them in production.

Asynchronous Code Is Not Supported

Function bodies run synchronously. If your body returns a Promise, the promise is discarded and the function's value stays undefined. Do not use async/await, .then(), or setTimeout inside a function body. For asynchronous operations, use a data source configured to run on a trigger.

⚠ Async patterns that do NOT work in function bodies

// WRONG — async function body; Promise returned, value stays undefined async function() { const result = await fetch('/api/data'); return result.json(); } // WRONG — .then() chain; Promise returned, value stays undefined return fetch('/api/data').then(r => r.json()); // CORRECT — read from a data source that was already queried return dataSourceName.data;

Errors Are Captured, Not Fatal

If a function body throws a JavaScript error, the platform captures it rather than crashing the page. The error message and stack trace are stored in the function's error state. For manual functions, the On error branch of the Trigger Function action fires. For automatic functions, the error is silently captured and the function's value remains at its last successful value until a subsequent re-run succeeds.

Tip: If a function is producing unexpected output, check its error state in a binding or in the Data panel's output viewer. Errors that appear silently in an automatic function are easy to miss without explicit monitoring.

Unchanged Result Does Not Trigger Downstream

If a function re-runs and produces a result that is identical to its previous value (by reference equality for objects, or by value equality for primitives), the platform does not notify downstream bindings. This prevents unnecessary re-renders, but it also means you cannot use a function as a "something ran" signal. Use a variable write or an action trigger for that purpose.

No Side Effects, Including in Manual Functions

Even manual functions are expected to be pure. Writing to a variable, navigating, or making an API call from inside a function body is not supported regardless of the run behavior. If you need those side effects, structure your logic as an action chain instead. Manual functions are about deferred computation, not deferred actions.

Functions vs. Variables: Choosing the Right Tool

The choice between a function and a variable often comes down to one question: is the value derived from other state, or is it maintained as its own state?

▶ Decision guide

SituationUse
You want to count selected rows in a tableFunction — it reads the table's state and computes a count
You want to track whether a modal is openVariable — the open/closed state is maintained by actions, not derived
You want to format a date from page parameters for displayFunction — it takes the raw parameter and produces a formatted string
You want to store the user's typed search textVariable — the value comes from user input, not computation
You want to filter a list based on the search variableFunction — it reads the variable and derives a filtered subset
You want to compute validation errors for a form on submitManual Function — run only when the user submits, not on every keystroke