Connect HTML visuals to live data
Register datasets and visuals, use the renderer API, build paged tables, and bind live interactions.
Build HTML visuals from registered datasets and a small renderer API. Unity executes the SQL, applies filters, derives lineage, and supplies results. Your visual's HTML, CSS, and JavaScript control how those results appear.
This guide is for precise chat instructions and source-level customization. The agent tool reference contains complete tool schemas.
Register datasets and visuals
The assistant normally uses SaveHtmlVisuals to save datasets, visual definitions, and optional page layout together. First create an HTML page with CreatePage, then inspect its current revision.
| Object | Required fields |
|---|---|
| Dataset | id, title, connectionId, sql, paging |
| Visual | id, title, bindings, html, css, script |
| Binding | dataId and roles |
| Role | role and column |
IDs are local to the page. A binding's column must exactly match a SQL output name. Roles are category, x, series, value, or hidden.
This example shows the shape of a complete tool argument for a simple live KPI. Replace the revision, page ID, connection ID, and model identifiers with inspected values.
{
"baseRevision": 3,
"pageId": "YOUR_PAGE_UUID",
"data": [{
"id": "revenue",
"title": "Revenue total",
"connectionId": 123,
"sql": "SELECT SUM(amount) AS revenue FROM orders",
"paging": false
}],
"visuals": [{
"id": "revenue-kpi",
"title": "Revenue",
"bindings": [{
"dataId": "revenue",
"roles": [{"role": "value", "column": "revenue"}]
}],
"html": "<div class='kpi-value'></div>",
"css": "[data-unity-visual='revenue-kpi'] .kpi-value { font-size: 2rem; }",
"script": "element.querySelector('.kpi-value').textContent = unity.format(unity.value('revenue', 'revenue'), {style: 'currency', currency: 'USD'});"
}],
"html": "<main><h1>Revenue</h1><section data-unity-visual='revenue-kpi'></section></main>",
"label": "Added revenue KPI"
}
A value-only KPI can be inspected without pretending that its aggregate is a filterable category. The example is a tool argument, not a standalone HTTP request.
Preserve existing work
SaveHtmlVisuals saves the supplied changes atomically. It executes dataset SQL and validates references and bindings before accepting the proposed mutation.
Top-level data: [] and visuals: [] preserve existing registrations; html: null preserves the page layout. Each supplied visual, however, replaces its complete source and bindings. Retain every dataset that visual still uses.
A visual with bindings: [] has no dataset access. A table that needs data but no chart roles uses:
[{"dataId": "details", "roles": []}]
Registering a dataset elsewhere on the page or naming it in JavaScript does not grant the visual access to it.
Use PatchHtmlSource for targeted source replacements. It checks exact matches and the expected revision. Inspect again after a conflict rather than overwriting someone else's changes.
Render actual data
A visual's script is the body of a synchronous function receiving element, data, and unity. It is not a function declaration or a script tag.
data[dataId] includes columns, rows, records, output-contract information, optional paging metadata, and errors. records contains actual values keyed by SQL output name and can be empty.
For a dataset named monthly with outputs Month and Revenue:
element.replaceChildren();
const result = data.monthly;
if (result.error) throw new Error(String(result.error));
if (!result.records.length) {
element.textContent = "No results for these filters.";
} else {
for (const row of result.records) {
const mark = document.createElement("button");
mark.textContent =
unity.formatDate(row.Month, {month: "short", year: "numeric"}) +
": " + unity.format(row.Revenue, {maximumFractionDigits: 0});
element.appendChild(mark);
unity.bind(mark, "monthly", row, {columns: ["Month"]});
}
}
Register Month as a category and Revenue as a value before using this example. Append each mark inside element before binding it.
Use textContent for data values. SQL decimals can arrive as exact strings; formatting should change the display, not the original values passed to interaction helpers.
Return a cleanup function for listeners, timers, or observers that your code creates. Do not return a Promise from the renderer. Handle errors explicitly when starting asynchronous work.
Renderer helpers
| Helper | Purpose |
|---|---|
unity.data(key) | Retrieve a bound dataset result. |
unity.value(key, column, row = 0) | Read a single returned value. |
unity.format(value, options) | Format numbers and numeric strings with number-format options. |
unity.formatDate(value, options) | Format dates; UTC is the default. |
unity.bind(element, dataId, row, options) | Associate a DOM mark with an actual returned row for inspection and interaction. |
unity.activate(dataId, row, options) | Activate a datum from a custom handler such as canvas hit testing. |
unity.inspect(dataId, row, pointElement) | Open explicit inspection for a datum. |
unity.table(container, options) | Render Unity's searchable, sortable, paged detail table. |
unity.query(dataId, options) | Request a result window for a custom data view. |
unity.onData(callback) | React to data updates in page scripts. |
unity.navigate(pageId) | Request navigation to a report page. |
unity.theme.get/set/onChange | Support an authored page-local theme control. |
Generic number formatting preserves nonnumeric text and represents null as an em dash. Use formatDate explicitly for human-readable dates.
A page-local theme choice can be maintained through data refreshes, but should not be treated as an account-wide preference that persists across every reopen.
Build a detail table
Register deterministic detail SQL with paging: true. Include a stable ordering and the complete source key, including every part of a composite key.
SELECT order_id, ordered_at, region, amount
FROM orders
ORDER BY ordered_at DESC, order_id DESC
Use your actual model fields. In the visual's HTML, create a bounded container:
<div class="details" style="height: 420px"></div>
Then use this renderer body:
unity.table(element.querySelector(".details"), {
dataId: "details",
columns: ["ordered_at", "region", "amount"]
});
The key can remain in the query while being omitted from visible columns. Unity can use a proven complete key for record selection. Without one, it can only filter by supported cell values; a column named “ID” is not by itself proof of unique identity.
The helper provides server-side search and sorting, additional result windows, and a windowed DOM. Style its unity-table, unity-table-toolbar, unity-table-scroll, and unity-table-status classes with scoped CSS.
Query a custom result window
Custom controls can request another window against the registered query:
unity.query("details", {
pageIndex: 0,
pageSize: 200,
sorting: [{id: "amount", desc: true}],
search: "North"
}).then(result => {
// Render this window; inspect result.window.hasMore for continuation.
// This result is not a complete-dataset total.
}).catch(error => {
if (error.name !== "AbortError") {
element.textContent = "The requested rows could not be loaded.";
}
});
Reset pagination after search or sorting changes. Requests from a replaced visual/data context can be cancelled with AbortError. Loading, sorting, and searching rows do not save report revisions.
Use a separate aggregate dataset for totals. Snapshot results are bounded at 10,000 rows; paged windows are also partial. An authored TOP or LIMIT remains a real restriction, not a pagination instruction.
Connect interactions and filters
Bind original returned rows, not formatted labels or invented predicates. Unity derives field lineage and applies report, page, dataset, visual, and runtime filter context.
Clicking a bound mark can select it and cross-filter related content. Clicking the same selection again or clearing its filter chip returns to the authored baseline. The runtime selection does not save a document edit.
Dataset filters affect its consumers; visual filters affect a particular registration. A shared dataset can serve several visuals with different effective contexts. Relationships across separate source connections are not inferred.
Compose and validate source
Keep each live component separately registered. Put its JavaScript in the script field, scope its CSS, and reference it once where intended through data-unity-visual. Do not place a visual's own placeholder inside itself.
HTML runs with inline browser APIs in a sandbox. Use report assets for images and registered datasets for data; external fetches, remote imports, CDN dependencies, and browser storage are unavailable.
After saving, inspect execution feedback and a real page preview. SQL validation cannot determine whether your labels, layout, or business interpretation are correct.
Questions about your environment? Contact us.