Authoring Overlay Applications
What are overlay applications?
Overlay applications (referred to as templates in the SDK and technical documentation), are ordinary, self-contained web pages designed to be rendered over live video using Nebular Overlay Server.
They may expose configurable values that can be edited in Studio or updated through the Overlay API while a live video session is running.
Template Repository
To simplify deployment, all templates for your organization live in a repository under your control.
Your design team or agency can create, test and maintain templates in this repository independently from the Overlay Server.
When a tested change is committed, your template library can be synchronized with the production Overlay Servers.
If enabled for your account, synchronization can also be requested through the API.
The repository contains presentation code and metadata only. Overlay sessions, saved parameter values, credentials and server configuration do not belong here.
Quick start
Refer to Overlay Studio SDK to learn how to:
- Initialize a new repository for your organization with example templates.
- Preview templates with the local preview tool.
- Install the Codex skill to create templates using natural language.
Repository structure
Use folders to categorize your templates. Templates may be organized at any depth:
.
├── broadcast/
│ └── lower-third/
│ ├── index.html
│ └── manifest.json
├── gaming/
│ └── gta/
│ └── mission/
│ ├── index.html
│ └── manifest.json
└── sports/
└── tennis/
└── scoreboard/
├── index.html
└── manifest.json
The corresponding template IDs are:
broadcast/lower-third
gaming/gta/mission
sports/tennis/scoreboard
Any directory containing index.html is treated as a template. It is therefore a leaf in the template tree: do not place other templates beneath it.
Creating a template
Create a directory containing both required files:
category/template-name/
├── index.html
└── manifest.json
Names may contain letters, numbers, hyphens and underscores. Use relative paths for assets belonging to the template.
The file index.html
This is the web page that will be rendered over the live video stream.
- The page should have a transparent background and completely fill the render surface.
- It listens for the
nebular-overlay:updateevent, which carries values supplied by an operator or API client, such as lottery numbers, match results or chat messages. - The template decides how those values modify the page. In the following example, it changes some text and its colour.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example Overlay</title>
<style>
* {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: transparent;
}
#title {
color: #ff9818;
}
</style>
</head>
<body>
<div id="title">Hello</div>
<script>
const title = document.getElementById("title");
// Every event is an incremental update. Change only the values that
// are present and leave all other presentation state untouched.
window.addEventListener("nebular-overlay:update", ({detail}) => {
if (detail.content?.title != null) {
title.textContent = detail.content.title;
}
if (detail.style?.accent != null) {
title.style.color = detail.style.accent;
}
});
</script>
</body>
</html>
How live updates reach the page
When an operator changes the displayed overlay through Nebular Overlay Studio—or an application sends an API request—the page receives a custom event:
window.addEventListener("nebular-overlay:update", ({detail}) => {
// Apply the fields supplied in detail.
});
Updates are incremental. An event may contain one value, several values or every declared value; the distinction is irrelevant to the template. Always test whether a field was supplied and preserve the current state of fields that are absent.
The HTML is the template's presentation baseline. Put the values and visual state that should appear before any update directly in the HTML. This also gives the local preview a useful state while the initial manifest values are being delivered.
The manifest defaults are the initial values offered by Studio. They should normally agree with the values authored in the HTML.
Complex templates are free to maintain whatever internal state they need: arrays, maps, animation queues, board state, timers or derived values. They must only honour the incremental-update contract and leave unrelated state untouched.
The complete flow is:
- The HTML loads with the presentation and values authored by the agency.
- The manifest tells Studio which controls to display and supplies their initial values.
- Studio initially sends the effective values: either persisted session values or its merge of the manifest defaults.
- Later operator or API changes arrive as incremental
nebular-overlay:updateevents. - The template applies only the supplied fields to its DOM, styles, animations or internal state.
- The Overlay Server merges the changes into the session's persisted values.
Requesting a frame after a static change
The Overlay Server normally detects visual changes and captures frames automatically. Some very small or entirely static DOM changes may not immediately produce a frame, however. A template can explicitly notify the server after applying such a change:
title.textContent = "Updated title";
window.invalidate?.();
In general you should not normally need to use it. It is provided as a safeguard in case you experience visual glitches.
The file manifest.json
Purpose
The manifest describes the dynamic values presented to an operator—for example, sports results, lottery numbers or chat messages. It may also provide concise operating instructions for the template.
Nebular Overlay Studio uses this description to create the appropriate interactive controls automatically. The same values can also be updated programmatically through the Overlay API.
Studio creates controls only for parameters declared in
manifest.json.API callers follow the same parameter contract. The Overlay Server performs basic validation against the manifest before accepting parameter updates.
The
nebular-overlay:updateevent carries changes while the live session is running.
Operator instructions
The optional top-level instructions field explains how the template should be prepared or operated in Studio:
{
"instructions": "Build the question deck in Edit mode. During the broadcast, select a question and press Update to display it.",
"meta": {
"control/current": {
"type": "number",
"label": "Question to show",
"default": 1
}
}
}
Studio displays this text to the operator alongside the template controls. Keep it short, practical and focused on actions that may not be obvious from the field labels—for example, which values are prepared before going live, which controls are used during play, and whether an animation advances automatically.
instructions is manifest documentation, not a template parameter. It is not rendered as a control, included in the session values or delivered in a nebular-overlay:update event. Values intended for the page must still be declared inside meta.
Manifest for the preceding index.html
The following manifest defines the two values used by the example above: the title text and its accent colour.
{
"instructions": "Enter the title and choose its accent colour, then press Update.",
"meta": {
"content/title": {
"type": "string",
"phase": "play",
"label": "Title",
"default": "Hello from Nebular"
},
"style/accent": {
"type": "color",
"phase": "edit",
"label": "Accent colour",
"default": "#ff9818"
}
}
}
Each entry in the meta object describes one parameter. Its key is the parameter path, while its object contains at least three fields:
typedetermines the kind of value and the Studio control used to edit it.labelis the human-readable name shown to the operator.defaultis the initial value offered by Studio.
Some parameter types accept additional fields, as described below.
Parameter types
Each parameter must define a type. Studio uses it to render a suitable real-time control and produce a value of the expected kind. API clients remain responsible for sending values that conform to this contract.
The following table lists the supported parameter types:
| Type | Purpose | Common options |
|---|---|---|
string |
Free text, such as a title, player name or message | default, pattern, maxLength, hint, readonly |
number |
A numeric value, such as the number of goals scored | default, min, max, hint, readonly |
range |
A numeric value displayed in Studio as a slider | default, min, max, step |
date |
A local date-and-time picker stored as a Unix epoch in milliseconds | default, hint, readonly |
color |
A colour selected through Studio's colour picker | default, noalpha, remove, caption |
choose |
One value selected from a fixed set | default, choose, icon |
select |
One value selected from a dropdown | default, choose |
multi |
Multiple values selected from a set, such as called bingo or lottery numbers | default, choose, cols, rows, responsive |
set |
An editable array of structured records, such as questions, answers, players or poll options | default, meta, max, |
selector |
A one-based position selector for an item in a set |
default, set, include |
action |
A button that emits the current timestamp as a trigger | text, icon, hint |
reset |
A button that assigns predefined values to several parameter paths | text, icon, hint, values |
For choose, select and multi, keep values in default consistent with the types used in choose.
Options shared by parameter definitions
The containing record reads the following options for every widget:
| Option | Meaning |
|---|---|
type |
Widget to render. When omitted or unknown, Studio falls back to a string input. |
label |
Caption shown above the control. When omitted, Studio derives a readable label from source or the parameter path. |
default |
Initial value offered by Studio when no persisted value exists at the effective path. |
source |
Overrides the parameter path used to read and write the value. This allows two differently presented controls to share one underlying property. |
phase |
Displays the field in "edit" or "play" mode. With no phase, it appears in both. This is presentational only. |
size |
Studio grid width for this control, from 1 (smallest) to 12 (full row width). |
break |
When true, starts the following parameter on a new editor row. |
Additional rules for selection controls:
chooseandselectselect one entry and therefore have a scalardefaultvalue.multiselects any number of entries and therefore has an array as itsdefaultvalue.colsandrowsdescribe the preferred editor grid for thechooseandmultiwidgets.- With
responsive: true, Studio may adapt the number of columns to the available width.
For example, a tennis-points selector can be declared as:
{
"type": "choose",
"label": "Player one points",
"choose": ["0", "15", "30", "40", "A"],
"default": "0"
}
A bingo-number selector uses multi because several values remain selected simultaneously:
{
"type": "multi",
"label": "Called numbers",
"choose": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"default": [3, 8],
"cols": 5,
"responsive": true
}
Edit and play phases
The optional phase field helps keep the operator interface focused when a template has many parameters. It accepts two values:
"edit"marks preparation and design values normally configured before going live, such as colours, titles, question collections or a fleet layout."play"marks operational values changed during the live session, such as scores, the current question, called numbers or player shots.
Studio provides an Edit/Play switch and hides fields belonging exclusively to the other phase. A parameter with no phase remains available in both modes.
{
"content/title": {
"type": "string",
"phase": "edit",
"label": "Game title",
"default": "Friday Night Quiz"
},
"control/current": {
"type": "number",
"phase": "play",
"label": "Question to show",
"default": 1
}
}
This distinction is purely presentational. phase does not impose permissions, freeze values, alter API behavior or change what reaches the template. Updates remain incremental regardless of the phase currently selected in Studio.
Conditional visibility
Every parameter type supports the optional when field. It keeps crowded Edit or Play panels focused by showing a control only when another value has a particular value. It contains a controlling parameter path in key and the required value in is:
{
"screen": {
"type": "choose",
"phase": "play",
"label": "Screen",
"default": "scores",
"choose": [
{"value": "scores", "caption": "Hi-Scores"},
{"value": "playing", "caption": "Now Playing"},
{"value": "idle", "caption": "Game Idle"}
]
},
"current/name": {
"type": "string",
"phase": "play",
"label": "Current player",
"default": "VEX",
"when": {"key": "screen", "is": "playing"}
},
"idle/headline": {
"type": "string",
"phase": "play",
"label": "Announcement",
"default": "ROOK ENTERS THE ARENA",
"when": {"key": "screen", "is": "idle"}
}
}
The comparison uses strict equality, so the JSON type of is must match the controlling value: 1 and "1" are different. phase and when are cumulative; both must match for the control to appear.
This is an editor presentation feature only. Hidden fields retain their values, remain persisted, and continue to be delivered to the template. Switching the controlling value never clears or resets them. Use one clear selector such as screen; when supports a single equality test rather than compound expressions.
At the top level, key refers to another declared canonical top-level parameter. Within a set record, it refers to a sibling field in that set's nested meta.
Parameter paths
A parameter may have a simple name such as clock, or a slash-separated path that groups related values. For example, content/title is delivered to the page as:
{
"content": {
"title": "Hello from Nebular"
}
}
Grouping becomes especially useful for related entities. A tennis scoreboard could receive:
{
"player1": {
"name": "Carlos Alcaraz",
"sets": 1,
"games": 3,
"points": 15
},
"player2": {
"name": "Jannik Sinner",
"sets": 1,
"games": 3,
"points": 30
}
}
The corresponding manifest keys would be player1/name, player1/sets, player1/games, player1/points, and the equivalent player2/... paths.
Structured sets
The set type represents an array of objects. Its nested meta object describes the fields in each item, using the same parameter definitions as the top-level manifest. Studio lets the operator move through the items and add or delete them.
For example, this definition creates an editable question-and-answer deck:
{
"questions": {
"type": "set",
"label": "Questions",
"max": 10,
"container": "big",
"default": [
{
"question": "What is the largest ocean on Earth?",
"answer": "The Pacific Ocean"
},
{
"question": "Which element has the symbol Au?",
"answer": "Gold"
}
],
"meta": {
"question": {
"type": "string",
"label": "Question"
},
"answer": {
"type": "string",
"label": "Answer"
}
}
}
}
Updates deliver the corresponding value as an ordinary array of objects:
{
questions: [
{question: "What is the largest ocean on Earth?", answer: "The Pacific Ocean"},
{question: "Which element has the symbol Au?", answer: "Gold"}
]
}
The nested definitions are recursive: a field inside a set may use any supported type, including another set. Use recursion deliberately, because deeply nested editors quickly become difficult for an operator to navigate.
defaultis the initial array of records offered by Studio.metadefines the shape and controls of each record.maxlimits how many records the operator may create. When omitted, the set has no explicit item limit.
As with every update, a supplied set replaces the value of that parameter; an absent questions field leaves the template's current question collection untouched. An empty array is a supplied value and clears the collection.
Selecting an item from a set
Use selector for a play-mode control that moves through the records in a set. Its required set field names the target parameter path. The optional include array lists fields from the selected record that Studio should show beneath the selector.
{
"control/current": {
"type": "selector",
"phase": "play",
"label": "Current question",
"set": "questions",
"include": ["question"],
"default": 1
}
}
The selector emits the selected one-based position. Templates should handle an empty set and a position that no longer points to an item after the set changes.
Actions and resets
An action renders a button and emits Date.now() at its parameter path. Treat each new timestamp as a trigger; its exact value has no other meaning.
A reset renders a button that assigns several declared parameter paths at once:
{
"game/reset": {
"type": "reset",
"phase": "play",
"text": "Reset game",
"icon": "undo",
"values": {
"game/score": 0,
"game/history": []
}
}
}
Dates
The date control displays a local date-and-time picker but stores a finite Unix epoch in milliseconds. This gives templates an unambiguous instant that can be compared directly with Date.now() without parsing a formatted date string.
Aliasing a control
The optional source field makes a control read and update another declared parameter path. This is useful when the same value needs a second operator control in a different phase or location. The source path remains canonical; the alias path is not persisted or delivered as a separate value.
Local assets and security restrictions
An overlay should render the same way on every Overlay Server. To make that possible—and to prevent a template from unexpectedly communicating with other systems—templates run in a deliberately restricted environment.
The simplest rule is: keep everything the overlay needs inside its own directory.
broadcast/lower-third/
├── index.html
├── manifest.json
└── assets/
├── overlay.css
├── logo.webp
└── inter.woff2
Reference those files with relative URLs from index.html:
<link rel="stylesheet" href="https://studio.nebular.tv/doc/sdk/assets/overlay.css">
<img src="https://studio.nebular.tv/doc/sdk/assets/logo.webp" alt="">
Supported assets
Supported local asset formats are:
- CSS:
.css - Images:
.jpg,.jpeg,.png,.webp - Fonts:
.otf,.ttf,.woff,.woff2
Additional remarks:
- Each served file is limited to 5 MiB.
- JavaScript must be written inline in
index.html; local.jsfiles are intentionally not served. - Keep the asset set small. Most templates should need only
index.html,manifest.jsonand a few lightweight images or fonts. - Refer to the starter templates for complete examples.
Intentionally unavailable capabilities
The rendering policy permits inline JavaScript, inline CSS and supported local files, but automatically blocks:
- External scripts, styles, fonts, images and, in general, outside communication.
- JavaScript connections to external services through
fetch, XHR, WebSocket or EventSource. As a rule of thumb, connections initiated from inside the page are forbidden. - Audio and video resources. Video and audio are already provided by the live stream; overlay media is not supported so that the incoming video signal remains smooth.
- Frames, iframes, embedded objects, form submission and Web Workers.
- Popups or browser windows, including those that point to local resources.
In summary, a template cannot load libraries or assets from public or private CDNs, fetch private data directly or open its own WebSocket. All operator-facing dynamic values must be declared in the manifest and enter through nebular-overlay:update events. API clients must follow this contract.
Other unavailable or forbidden capabilities
- WebGL, canvas rendering, WebAssembly modules and WebAudio.
- Access to devices and privileged browser capabilities, including cameras, microphones and the filesystem.
- Excessively large DOM trees.
- Complex JavaScript, especially tight computational loops.
Operator approval
Most restrictions above are enforced automatically, but rendering complexity is assessed separately.
Nebular Overlay Studio operators must approve a template before it can be used in a production environment. A template may be refused at the operator's discretion if it is considered too complex for the system.
Developing and testing a template
Design guidelines
- Design for a transparent render surface, normally 1920×1080.
- Use percentages, viewport units or responsive layout where appropriate.
- Keep animations bounded and inexpensive. The overlay capture frame rate is intentionally capped to preserve server performance.
- Avoid complex JavaScript. Ideally, use it only to modify displayed values and presentation state. The Nebular Overlay Server operator may refuse JavaScript considered too complex because it could reduce server performance.
- Update existing DOM elements instead of rebuilding the entire document.
- Treat all supplied values as untrusted data; prefer
textContentoverinnerHTML. - Do not include large assets. Each served file is limited to 5 MiB.
- Ensure
manifest.jsonis a well-formed JSON file before updating your templates.
Test updates locally
Run the SDK preview from the root of your template repository:
npx @nebularstreams/overlay-sdk preview
The preview application discovers templates directly from the repository, reads each manifest.json, builds the same parameter controls used by Studio and renders the selected template in an isolated iframe. Manifest defaults are delivered automatically when the frame loads; changing a control sends the resulting values through the nebular-overlay:update bridge.
Use it to verify that:
- The initial presentation agrees with the manifest defaults.
- Every control changes the intended visual or state.
- Edit- and play-phase controls appear under the appropriate Playing mode.
- Sets, selectors, actions and resets behave correctly at their boundaries.
- Empty strings, zeroes,
falsevalues and empty arrays are applied rather than mistaken for absent values. - The layout remains legible at the intended render resolution and with unusually short or long content.
After editing index.html, CSS, assets or manifest.json, refresh the browser to load the changed files. Refresh after adding, removing or renaming a template directory so the catalogue is scanned again.
The preview exercises the packaged manifest editor, update bridge, asset restrictions, Content Security Policy and Permissions Policy. It is therefore the normal development environment; opening index.html directly and manually dispatching events in the browser console is no longer necessary.
Before production, still verify the synchronized template in Nebular Overlay Studio and on the target Overlay Server. The local preview closely reproduces the template contract and browser restrictions, but it does not reproduce the complete live rendering, persistence and video-ingestion pipeline.
Synchronizing templates to an Overlay Server
This section is intended for the Overlay Server operator. Template designers normally commit and push their work; the operator—or an authorized API integration—decides when that version is synchronized to a server.
Public repositories
On each worker where the Nebular Overlay server is installed, run:
sudo nebular-overlay-server-install-templates \
ORGANIZATION \
https://git.example.com/agency/overlay-templates.git
For example:
sudo nebular-overlay-server-install-templates \
XQA0t \
https://git.example.com/agency/overlay-templates.git
The organization argument determines the directory in which the repository is installed; it must not be included as an extra directory inside the repository itself.
The first invocation clones the repository. Running the same command later updates it with a fast-forward-only pull.
Private repositories
Only HTTPS repository URLs are accepted. For a private repository, supply credentials through environment variables rather than embedding them in the URL:
sudo env \
TEMPLATE_REPOSITORY_USERNAME=git \
TEMPLATE_REPOSITORY_TOKEN='your-access-token' \
nebular-overlay-server-install-templates \
XQA0t \
https://git.example.com/agency/overlay-templates.git
What synchronization will and will not do
Synchronization deploys the committed repository state; it does not upload uncommitted files from an agency workstation. Push the desired commit to the configured remote before requesting an update.
For safety, synchronization refuses to:
- Overwrite local changes in the server copy
- Replace an existing organization's configured Git remote
- Merge diverging history instead of performing a fast-forward update
- Run two synchronizations for the same organization simultaneously
The repository is installed beneath the server's configured template root, normally:
/var/lib/nebular-overlay-server/templates/ORGANIZATION
The checked-out server copy is deployment state and should not be edited manually.
From design to live production
- Create or modify a template locally.
- Make its initial HTML state match the defaults in
manifest.json. - Run
npx @nebularstreams/overlay-sdk previewand exercise every manifest control in both Edit and Playing modes. - Validate the repository and visually inspect the intended render resolution, content extremes and empty states.
- Commit and push the tested version.
- Synchronize the repository for the organization.
- Verify its controls and rendering in Nebular Overlay Studio.
- Approve the template for use on air.