In this section you can find short examples of how to use the scripting functionality.

Fields

Setting Default Values on Data Element Creation

vizrt.onCreate = () => {
vizrt.fields.$headline.value = "Enter headline"
vizrt.fields.$showLogo.value = true
vizrt.fields.$headline.decoration.maxlength = 60
}

Conditionally Hide Fields based on a Boolean

vizrt.fields.$showSubtitle.onChanged = show => {
vizrt.fields.$subTitle.hidden = !show
}

Accessing Tables

The following example shows how to populate a list or table of Formula 1 drivers when the user clicks a button named "fetch".

// Drivers list, usually fetched from an external resource
const drivers = [
{ name: "Lewis Hamilton", wins: "105" },
{ name: "Michael Schumacher", wins: "91" },
{ name: "Max Verstappen", wins: "68" },
{ name: "Sebastian Vettel", wins: "53" },
{ name: "Alain Prost", wins: "51" },
{ name: "Ayrton Senna", wins: "41" },
{ name: "Fernando Alonso", wins: "32" }
];
 
// Helper: extract last name, lowercase, and add .jpg
function getImageFileName(name: string): string {
const parts = name.trim().split(" ");
const lastName = parts[parts.length - 1].toLowerCase();
return `${lastName}.jpg`;
}
 
vizrt.onClick = (name: string) => {
if (name === "fetch") {
 
// Reference to the list/table field in the payload
const list = vizrt.fields.$list.value;
 
// Base path for image assets
const baseUrl = "http://myserver.com/imageshare/";
 
// Iterate and populate the rows
drivers.forEach((driver, i) => {
if (!list[i]) return; // Safety if fewer rows exist
 
const imageFile = getImageFileName(driver.name);
 
list[i].$name.value = driver.name;
list[i].$number.value = driver.wins;
list[i].$image.value = createImageAsset(baseUrl + imageFile);
});
}
};

Dynamic Drop-down and ControlOMO

This example shows how to fill a dynamic drop-down (a drop-down that can change run-time) with options, controlling a ControlOMO field in the template.

const select = [
{ label: "Image and text", value: 0 },
{ label: "Text", value: 1 },
{ label: "Bullets", value: 2 },
{ label: "Image and bullets", value: 9.0 }
];
 
vizrt.$data["choices"] = JSON.stringify(select)

In the template, Data Entry for the ControlOMO field must be set to Dynamic drop-down and the Linked source field should be $data.choices.

image-20260326-144235.png

React to Field Changes and Update other Fields

This example shows how to listen to user input (onChanged) and programmatically modify other fields, including dynamically controlling visibility via the hidden property.

// React when the user edits the headline field
vizrt.fields.$headline.onChanged = value => {
// Automatically update another field
vizrt.fields.$slug.value = value?.toUpperCase() ?? ""
 
// Show/hide a field based on input
vizrt.fields.$subtitle.hidden = !value
}

Note: Field names that contain dashes (-) cannot be accessed using dot notation in TypeScript. Instead, use bracket notation: vizrt.fields["$01-week"].

When defining new fields, it is recommended to use camelCase or underscores (for example, 01thisIsMyField or 01_week). This allows the fields to be accessed using standard dot notation, which is more concise and readable.

Validate Before Save

vizrt.onBeforeSave = () => {
const headline = vizrt.fields.$headline.value
if (!headline || headline.trim().length === 0) {
vizrt.fields.$headline.error = "Headline cannot be empty"
app.notify("error", "Please fill in the headline before saving.")
return false
}
vizrt.fields.$headline.error = ""
return true
}

Control Playout through MSE

const mse = app.createMseConnection("http://mse-server:8580")
 
vizrt.onClick = name => {
const target = { profile: "STUDIO_A", channel: "GFX", layer: "MAIN" as const }
switch (name) {
case "take":
mse.take(target)
break
case "update":
mse.update(target)
break
case "out":
mse.out(target)
break
}
}

Working with MOS Metadata

These patterns show how scripts can both consume metadata from the newsroom system and inject structured data back into the MOS item, enabling tighter integration between templates and editorial workflows.

Create MOS Metadata

This shows how to initialize MOS metadata, when a new Data Element is created, including automation-related properties, such as continueCount.

vizrt.onCreate = () => {
// Standard MOS fields
vizrt.$pilot.mos.slug = "BREAKING-NEWS"
vizrt.$pilot.mos.description = "Breaking news lower third"
 
// Control automation behavior
vizrt.$pilot.mos.continueCount = 1
 
// Optional: initialize Mosart timing
if (vizrt.$pilot.mos.mosart) {
vizrt.$pilot.mos.mosart.inMode = "auto"
vizrt.$pilot.mos.mosart.outMode = "auto"
}
}

Read Custom MOS External Metadata (MEM) from NRCS

This example reads a custom MEM block provided by the NRCS, parses the XML payload and uses it to populate template fields.

vizrt.onLoad = () => {
const schema = "http://example.com/schemas/suggested-headlines"
 
const block = vizrt.$pilot.mos.mem.get(schema)
if (!block) return
 
try {
// Example MEM block:
// <mosExternalMetadata>
// <mosSchema>http://example.com/schemas/suggested-headlines</mosSchema>
// <mosScope>PLAYLIST</mosScope>
// <mosPayload>
// <headlines>
// <headline>Main headline</headline>
// <headline>Alternative headline</headline>
// </headlines>
// </mosPayload>
// </mosExternalMetadata>
const parser = new DOMParser()
const xml = parser.parseFromString(block.payload, "application/xml")
 
const headlines = Array.from(xml.getElementsByTagName("headline"))
.map(node => node.textContent)
.filter(Boolean)
 
// Use the first suggested headline
if (headlines.length > 0) {
vizrt.fields.$headline.value = headlines[0]!
}
} catch (e) {
console.error("Failed to parse MEM block", e)
}
}

Create a Custom MOS External Metadata (MEM) Block

This example creates (or updates) a custom MEM block that can be consumed by the NRCS, or downstream systems. The payload can contain any agreed XML structure, such as playout instructions or editorial metadata.

vizrt.onAfterSave = () => {
const schema = "http://example.com/schemas/graphics-instructions"
 
const payload = `
<instructions>
<take mode="auto" />
<duration seconds="10" />
<transition type="fade" />
</instructions>
`.trim()
 
vizrt.$pilot.mos.mem.set({
schema,
payload,
scope: "PLAYLIST",
})
}

The resulting MOS XML is:

<mosExternalMetadata>
<mosSchema>http://example.com/schemas/graphics-instructions</mosSchema>
<mosScope>PLAYLIST</mosScope>
<mosPayload>
<instructions>
<take mode="auto"/>
<duration seconds="10"/>
<transition type="fade"/>
</instructions>
</mosPayload>
</mosExternalMetadata>

Handle Errors in Asynchronous Operations

fetch("https://api.example.com/config")
.then(res => res.json())
.then(config => {
vizrt.fields.$headline.value = config.defaultHeadline
})
.catch(e =>
reportErrors(() => {
throw e
}, "load remote config"),
)

Script Unit with Click Handler

The following example demonstrates how to use a script unit to add individual click handlers for buttons in HTML fragments. The exported method onClick replaces the usual pattern with something simpler and modular.

vizrt.onClick = (name) => {
switch (name) {
case "BtnA": ...
case "BtnB": ...
}
}

This method allows you to register individual click handlers by name, without writing a large switch block. Each call to onClick(name, callback) stores a callback in a map and installs a global vizrt.onClick dispatcher that forwards the event to the correct callback. It also returns a dispose function so you can unregister the handler.

The unit and the usage of this module may look like this in the template:

const onClickMap: Record<string, () => void> = {}
 
/**
* A better vizrt.onClick to avoid switch statements...
* It returns a dispose function when it's no longer needed
* @param name the vizrt-click-name to register
* @param callback The callback to call when clicked
* @returns a dispose function to stop updating
*/
export const onClick = (name: string, callback: () => void) => {
onClickMap[name] = callback
vizrt.onClick = (name) => onClickMap[name]?.()
 
//console.log("from onClick")
return () => delete onClickMap[name]
}

First add a new HTML fragment to the template and create a button with a click name:

<button vizrt-click-name="my-button">MY BUTTON</button>

Then, in scripting, the handler for this button is expressed as follows:

import { onClick } from "@unit/global-onclick"
 
const dispose = onClick("my-button", () => {
app.notify("info", "The button was clicked!");
});

You can later call the dispose() method if you need to disconnect the click handler.

Fetch Data from REST Service

In this example, the script automatically fills the title field by fetching data from a REST endpoint. It demonstrates how to use the standard fetch API and update fields based on an external response.

vizrt.onLoad = () => {
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => {
// Assume the response contains: { "title": "Some headline" }
vizrt.fields.$title.value = data.title ?? ""
})
.catch(error => {
console.error("Failed to fetch title:", error)
app.notify("error", "Could not load title from service")
})
}

Accessing Views (Tabs)

Templates can expose multiple views (tabs) to organize content or configurations. From scripting, it is possible to:

  • Iterate over available views.

  • Check which view is active.

  • Activate a view programmatically.

  • React when the user changes the selected view.

Note: The special All tab is a UI concept only. It is not included in the views API and cannot be activated from scripting.

// Activate a view
vizrt.views[1].activate();
 
// Check which view is active
const activeView = vizrt.views.find(view => view.isActive);
 
if (activeView) {
console.log("Active view:", activeView.title);
}
 
// Listen for activation
vizrt.onViewActivated((selected, old) => {
if (selected === undefined) {
console.log("All tab selected");
} else {
console.log("Active view:", vizrt.views[selected].title);
}
});
 
// Change the decorative title of a view
vizrt.views[0].title = "Overview - Q1";

Working with ViewItems

Each view exposes a collection of items representing fields and UI elements (fragments/iframes). These can be inspected, repositioned and conditionally hidden at runtime to create more dynamic layouts.

A ViewItem is either:

  • a field item (type: "field-item") identified by its path, or

  • a UI item (type: "ui-item") identified by its name.

All items share a common API for layout via position and setPosition().

Example: reposition and hide items

const items = vizrt.views[0].items;
 
// Move a field item (identified by field path)
const headline = items.find(
item => item.type === "field-item" && item.path === "headline"
);
 
headline?.setPosition({
x: 0,
y: 0,
width: 12,
height: 2
});
 
// Hide the headline if needed
if (headline) {
headline.hidden = true;
}
 
// Move a UI fragment (identified by name)
const fragment = items.find(
item => item.type === "ui-item" && item.name === "fragment"
);
 
fragment?.setPosition({
x: 6,
y: 0,
width: 5,
height: 14
});

Notes

  • setPosition() accepts a partial rectangle, this way, you are able to only update what you need:

    headline?.setPosition({ y: 2 });
  • Layout changes are applied in a single update per call.

  • Use type to distinguish between field items and UI items.

  • Field items use path, UI items use name for identification.

  • hidden can be used to programmatically show/hide items. If a field has a hidden-expression, that logic overrides the hidden property.

This allows scripts to dynamically rearrange and control visibility of the UI, based on user input or context.