A custom visual's code draws its tile with the uni global: the rows the report sends, selection, saved state, the report theme, assets and the outside content an administrator allows. This page is the reference for that global, for the template tier that draws without JavaScript, and for the limits and checks that go with them. For what a custom visual is and how it is shared, see Custom visuals and the visual library; for the editing workflow, see The custom visual Studio.
uni global belongs to the HTML + JS tier, and its version is SDK 1 (uni. version. sdk).uni. *.uni. render. The host runs it on mount, and again whenever the data, size, theme, selection, filters, options, state or mode change.uni. capabilities mirrors them: select, composite, openUrl, slicer, highlight and the allowlisted hosts. The Visual pane sets Acts as and Selection from other tiles; a new visual cross-filters other tiles from its marks, and links open only when Allow links is on and only for http(s) addresses, otherwise uni. openUrl warns uni. openUrl: links are disabled for this visual (interactions. openUrl = false).uni. render(fn) registers the paint. Its context carries root, data, size, theme, selection, state, options, mode and reason.uni. render. started() before it and uni. render. finished() when it is drawn; a callback that settles without started() counts as finished. uni. render. failed(reason) reports a failure, and uni. ready() signals readiness without a paint.uni. on(event, cb) listens to one render event; the events are data, size, theme, selection, filters, options, state, reset and mode. An unknown event warns uni. on: unknown event ". . . ".uni. onDispose(cb) runs your cleanup when the code is replaced or the tile closes, newest callback first. Register once at the top of the file, never inside the paint, and cancel timers, animation frames and observers here; a callback that throws is logged and the rest still run.uni. html builds a template from a tagged string and escapes every interpolated value; uni. unsafeHtml is the only entry that accepts raw markup, and it runs the sandbox's sanitiser. uni. repeat, uni. mount and uni. text build and place those templates, and uni. icon(name, options) renders a bundled Lucide icon as inline SVG, throwing on an unknown name.missing-ready; a paint that keeps re-triggering itself is stopped and recorded as a render loop.uni. mode is view, edit, preview, print or export. Run timers and animation only in the live modes, and paint the final frame when the mode is export or print.uni. data. fields lists the bound fields, and each field carries id, label, kind, type and its display format. field(idOrLabel) resolves by id, then by a unique label; otherwise it throws uni. data. field("Field"): no such field. Available fields: . . ..rows are read-only, and each row carries __index and __selected. Values are raw: wide integers arrive as strings and dates as wall-clock strings, so format them rather than doing arithmetic on them.value, text (formatted with the field's own format), number, sum, avg, min, max, count, groupBy (first-appearance order) and sortBy read the rows; aggregates return NaN on an empty column, so guard totals on zero rows.status is loading, ready, refreshing, error or rlsDenied; render an empty state for error and rlsDenied. total, loaded and hasMore say how much of the result arrived.requestMore() cannot load more in SDK v1: it resolves false and records the warning uni. data. requestMore() is not available in SDK v1; the row limit is fixed by the tile. Set the tile's row count under General > Limits; see Custom visual options, data and limits.uni. select(target, options) selects: a target is { field, value }, or { row, fields } to select every filterable field of one row. uni. selectComposite(targets) posts several fields as one tuple, for values that belong together. Only filterable fields count, and a value the delivered rows do not carry is dropped.uni. isAdditive(event) is true for Ctrl or Cmd; pass { additive: true } when you handle a click yourself. uni. clearSelection() clears the tile's own selection.__selected is on, off or neutral. Under Highlight from another tile, the rows the selection keeps read on and the rest read off; a selection that names a column you do not bind dims nothing, and the tile filters instead. A disabled selection warns uni. select: selection is disabled for this visual (interactions. select = false).uni. slicer takes over: field is the first filterable dimension in the Data well, set(values, operator) replaces the selection, clear() clears it and autoPick(values) applies a starting pick that is never saved as the report default.in, eq, neq, between, gt, lt, gte, lte, isnull and notnull, and a set holds at most 1,000 values.uni. slicer: this visual is not a slicer (set interactions. role to "slicer" in the Visual pane); with no dimension bound they warn uni. slicer. <method>: bind a dimension to the Data well first.uni. state. get(), uni. state. set(patch) and uni. state. reset() keep small state for the viewer, handed back on the next visit; a set inside the paint applies when the paint ends. State is capped at 16 KB; a bigger patch is refused with uni. state. set: state would be N bytes, above the 16384-byte cap; keep only what the visual needs to restore itself.uni. format. value(value, field) renders a value with the field's saved format, and number, percent, abbrev and date are stable helpers that do not depend on the viewer's locale.uni. theme carries tokens, color, hex, chart, series, ramp and diverging; echarts(), chartjs(), vegaConfig() and plot() return ready-made colour sets for those libraries. An unknown name throws and lists the known ones: uni. theme: unknown token ". . . ".hsl(var(--primary)) or var(--uni-primary); a bare var(--primary) is not a colour and the declaration is dropped. Use chart-1 to chart-12 for series colours, never for status.uni. assets. url(name) returns a data: URL for image, font and data assets and a blob: URL for binary ones; bytes(name) returns a copy of the raw bytes, list() and has(name) describe the attached files, and json(name) parses a JSON or GeoJSON asset. Only assets attached to this visual exist; uni. assets. json reports uni. assets. json: unknown asset "name" or the parse error.uni. image(url) loads an https image through the app and resolves to a data: URL, cached per address. It rejects with a code: host-not-allowed, not-https, not-an-image, too-large or upstream. A refusal by host, scheme, type or size is remembered; a temporary upstream failure is retried on the next load. A refused host reads uni. image: host ". . . " is not in the sandbox allowlist.uni. embed(el, url, options) marks an element as an embedded player's box; the app draws the player in view, edit and preview, and a thumbnail stands in for it in export and print. YouTube, Vimeo and Loom links are normalised, and any other https page embeds as it is. A visual can carry at most 32 embeds (uni. embed: at most 32 embeds per visual; extra ones are ignored) and only https addresses (uni. embed: only https URLs can be embedded, got ". . . ").import * as d3 from 'd3'. Only the libraries the visual declares are present, and only these twelve exist: d3 7, d3-sankey 0.12, chart. js 4, echarts 6, vega 6, vega-lite 6, @observablehq/ plot 0.6, three 0.186, lottie-web 5, topojson-client 3, d3-cloud 1 and world-atlas 2. Anything else is refused and the refusal names it; changing the declared set reloads the visual.The template tier draws HTML and CSS only. The directives:
| Directive | What it does |
|---|---|
data-uni-repeat="row in rows" | Repeats the element over an array, such as rows or groups('Region'); index is the item number. |
data-uni-if="expression" | Renders the element only when the expression is truthy. |
data-uni-select="Field" | A click selects the row's value, or a group's key; Ctrl or Cmd adds. The renderer writes data-uni-selected as on, off or neutral. |
data-uni-suppress="select" | Cancels the selection from clicks inside the element, for controls on a selectable card. |
data-uni-src | Writes an asset: reference or an allowlisted https image to src; unknown assets and http addresses never load. |
data-uni-embed | Marks the element as an embedded player's box. |
data-uni-class | Adds the classes whose expressions are truthy, as in { 'is-active': row.. |
data-uni-style-<property> | Sets one allowed CSS property; custom properties and the layout, colour, type and SVG paint families are allowed. |
{{ expression | filter }}, are text; they fit in attributes such as title, alt, aria-label, placeholder, lang and plain data-*, never in href, src, style or class. {{{ }}} inserts markup and runs the sanitiser.row, g, rows, total, page, options and index; the builtins are field, sum, avg, min, max, count, groups and token; the filters are number, percent, abbrev, date, text, upper, lower, default and format.div[0]/ ul/ li, and the render continues; scripts, on* attributes, javascript: addresses and prototype access are refused. page is not filled in this version, so a counter guarded by data-uni-if="page" stays hidden.| What | Cap |
|---|---|
| Source files | visual. 96 KB, visual. 64 KB, visual. 128 KB, 256 KB together |
| Template | 96 KB, 20,000 rendered nodes, expressions nesting up to 32 deep |
| Viewer state | 16 KB |
| Embeds | 32 per visual |
| Slicer set | 1,000 values |
| Native export | 400 elements; 2,000 characters of text; a table 30 columns by 100 rows; a chart 200 categories and 20 series |
ok when it ran without errors or blocked content, and a capture taken before the paint finished is noted as partial.uni. applies the format saved for that field, and uni. does the same when you read a row.
You do not need to register anything: the host runs your uni. callback again on every data, size, theme, selection, filters, options, state or mode change. uni. and the other events give you the same moments for side reads.
No. A Template visual runs no author JavaScript, so uni does not exist in it; read rows, groups and options through interpolations and the directives above. Switch to HTML + JS when you need libraries or the full uni global.
It cannot load more in SDK v1: the call resolves false and records the warning uni.. Set the tile's rows under General > Limits instead.
Up to 16 KB. A bigger patch is refused with uni., and a set made inside a paint is applied when the paint ends.
In uni., registered once at the top of the file. It runs when your code is replaced or the tile closes, newest first, so an old loop cannot keep running beside a new one.
The twelve listed above, by bare name, for example import * as d3 from 'd3'. Only the libraries the visual declares reach the frame; anything else is refused and the refusal names it.
Either selection is switched off for the tile, which warns uni., or the tile is in the slicer role, where uni. is disabled and uni. replaces it. Check uni. and uni..
on, off or neutral. Your own marks set it, and so does Highlight from another tile: the rows the selection keeps read on, the rest read off. neutral means nothing selects that row.
The rejection carries a code: host-not-allowed, not-https, not-an-image, too-large or upstream. A refusal by host, scheme, type or size is remembered, so a repeat fails the same way; an upstream failure is retried. Ask an administrator to allow the host, or load from one that is already allowed; see Sandbox, network and the allowlist.
No. It is the only entry that accepts raw markup, and the markup runs through the sandbox's sanitiser first; uni. escapes every interpolated value. Keep row values in text positions either way, because escaping does not make a value safe inside href, src or style.
Theme names are colour channels, not colours: var(--primary) holds channel numbers, so the browser drops the declaration. Write hsl(var(--primary)) or var(--uni-primary).