Custom visual SDK

An HTML + JS custom visual draws its tile with the uni global: the rows the report sends, selection, saved state, the report's theme, assets, libraries and the outside content the sandbox allows. This page is the developer reference for that global and the platform limits around it. For the editor around your code, see The custom visual Studio; for the tier that runs no JavaScript, Custom visual templates.

What the uni global carries

The groups behind the global:

GroupMembersUse it for
uni.datastatus, fields, dimensions, measures, rows, total, loaded, hasMore, isEmpty, error, field, role, value, text, number, sum, avg, min, max, count, groupBy, sortBy, requestMoreReading what the tile received
uni.themeappearance, tokens, chartColors, color, hex, chart, series, ramp, diverging, echarts, chartjs, vegaConfig, plotColours and chart-library themes
uni.formatvalue, number, percent, abbrev, dateFormatting values yourself
uni.selectionown, origin, isSelectedYour marks and the highlight state
uni.slicerenabled, field, values, set, clear, autoPickThe slicer role
uni.stateget, set, resetThe viewer's saved state
uni.exportHooksonSnapshot, onNative, pageCount, setPageThe export picture
uni.assetsurl, bytes, list, has, jsonThe report's asset files
uni.renderLifecyclestarted, finished, failedLong paints

Paint, react and clean up

js
uni.render((ctx) => {
  const label = ctx.data.isEmpty ? 'No rows' : `${ctx.data.count()} rows`;
  uni.text(ctx.root, label);
});

Read the rows and the fields

Select, and serve as a slicer

State, formatting and options

Helpers, tooltips and logs

Theme and colour

Assets, images, embeds and links

Libraries and icons

The export picture

Platform limits

WhatLimit
Template render20,000 nodes (see Custom visual templates)
Viewer state16 KB
Slicer set1,000 values
Embeds32 per visual

Questions, answered

How do I read the field bound to a data role?

uni.data.role('roleId', index) returns the field bound to that role, and the index picks among several fields in the same role. Bound fields also carry their role id in field.role, and uni.data.fields lists them all.

How do I know why my visual is being painted?

The paint's context carries reason: mount for the first paint, swap when the code is replaced, or the change that triggered it (data, size, theme, selection, filters, options, state, reset or mode). Use it to skip work a paint does not need.

Where do messages I write from the visual appear?

In the Studio's Diagnostics. uni.log(level, ...args) writes a console entry (debug and info read as log entries, warn and error keep their level), and plain console calls from your code are collected the same way.

How do I paint differently for light and dark?

Read uni.theme.appearance (light or dark) and draw with the tokens: the report's tokens are applied to the visual as CSS variables, with a --uni- mirror in hsl(...) form and a light or dark class on the frame. A theme switch runs the paint again, so a repaint picks the new values up.

Which colour names can I use?

Any token in uni.theme.tokens, plus the names that fall back when the report does not define them, such as status-good to status-success and status-bad to status-error. An unknown name throws uni.theme: unknown token "..." with the list of known names. Ask through uni.theme.color(), hex(), chart() or series() rather than reading raw channel values.

How do I draw an icon?

uni.icon('name') returns an inline SVG you can append to the drawing, and it accepts size, strokeWidth, className and label options. An unknown name throws uni.icon: unknown icon "..." with the available names.

What stops a paint that keeps scheduling itself?

The sandbox notices a paint that keeps repeating in one frame, stops it and records a render loop in the Diagnostics. A uni.state.set made inside a paint waits until the paint ends, so it cannot start a new paint on its own.

How can my visual provide its own export image?

With uni.exportHooks.onSnapshot(handler): the handler is asked for a canvas, an SVG or a blob when the report is exported or printed. onNative provides native pieces instead, and pageCount with setPage handle a visual that spans several pages.

Why does changing the libraries reload my visual?

The declared libraries install once per frame, so changing the set replaces the frame with one that carries the new set. Libraries the visual does not declare are refused, and the refusal names them.

What should my visual draw when the data is loading or denied?

Read uni.data.status: draw a loading state for loading and refreshing, the tile's empty or error state for error, and an empty state for rlsDenied, where row-level security withholds the rows. An empty result is isEmpty.