Pages data sources can now bind to runtime context — URL parameters, authenticated user info, parent record fields, and static values. This means a single page definition can render different data depending on who's viewing it and how they got there.
The Problem
Before variable binding, every data source was static. A data table showed the same collection with the same filters for every visitor. If you wanted a detail page for a specific record, you had to create a separate page per record (not practical) or hardcode a filter (not dynamic).
Variable binding solves this by letting data sources reference values that are resolved at runtime.
Four Variable Sources
URL Parameters
Bind a variable to a URL query parameter. When a user navigates to `/pages/customers?status=active`, the `status` variable resolves to "active" and filters the data source.
```json { "variables": { "status": { "source": "url", "param": "status" } } } ```
This is the most common binding — it powers filtered views, detail pages (via `?recordId=...`), and cross-page navigation with context.
Auth Context
Bind a variable to the authenticated user's ID, email, or name. This enables personalized views — show only records created by the current user, or filter a dashboard to the logged-in user's team.
```json { "variables": { "userId": { "source": "auth", "field": "userId" } } } ```
The publish validator warns you if you use auth-sourced variables on a public page, since unauthenticated visitors won't have auth context.
Record Context
Bind a variable to a field from the primary record on the page. This is the key to master-detail patterns — a detail page loads a customer record, and a related-list block uses the customer's ID to filter their orders.
```json { "variables": { "customerId": { "source": "record", "field": "customerId" } } } ```
Record-sourced variables are resolved in a second phase, after the primary record has been fetched.
Static Values
Bind a variable to a fixed value. Useful for setting default filters or scoping a block to a specific subset of data.
```json { "variables": { "status": { "source": "static", "value": "active" } } } ```
Two-Phase Resolution
Variable resolution happens in two phases to handle dependencies between blocks:
Phase 1 resolves blocks that don't depend on record context — URL, auth, and static variables. These can all be resolved immediately from the request context.
Phase 2 resolves blocks that depend on record context. Once the primary record block returns data from Phase 1, its fields become available for record-sourced bindings. This is what makes master-detail work: the detail block fetches the customer, then the related-list block uses the customer's fields to filter related records.
Example: Master-Detail Page
Here's a detail page that shows a customer and their related orders:
Block 1: Customer Detail (field-display, mode: single)
- Data source: `customers` collection
- Variable: `id` bound to URL param `recordId`
- Resolved in Phase 1: fetches the customer by ID from the URL
Block 2: Related Orders (related-list, mode: list)
- Data source: `orders` collection
- Variable: `customerId` bound to record field `id`
- Resolved in Phase 2: filters orders where `customerId` matches the customer record's ID
When a user navigates to `/pages/customer-detail?recordId=cust-123`:
- Phase 1 fetches customer `cust-123`
- Phase 2 uses the customer's ID to fetch their orders
- Both blocks render with their respective data
Works With Smart Queries Too
For query data sources, resolved variables are passed directly as query parameters. If your smart query has a `{{customerId}}` placeholder, a variable binding fills it at runtime:
```json { "dataSource": { "type": "query", "ref": "q-orders-by-customer", "variables": { "customerId": { "source": "url", "param": "customerId" } } } } ```
Cross-Page Navigation
Variable binding pairs naturally with the navigate-to-page action and paramMapping. A list page can navigate to a detail page, passing the record ID as a URL parameter:
```json { "type": "navigate-to-page", "targetRef": "customer-detail", "paramMapping": { "recordId": "{{id}}" } } ```
The detail page picks up `recordId` from the URL and uses it to fetch the right record. No hardcoded IDs, no extra API calls.
Configuring in the Editor
The page editor includes a Variable Binding section on any block with a data source. Add variables, choose the source type, and configure the binding — all from the visual editor.
The publish validator catches common issues:
- Query data sources with no variable bindings declared
- Auth-sourced variables on public pages
- Record-sourced variables without a single-record block to provide context
Smart variable binding is available now in the page editor for all workspaces.