# Portal overview

An introduction to the Singular Developer Portal

The Singular Developer Portal contains everything you need to know about our REST API, high speed data with Data Streams, Composition Scripting, SDKs and more.

The documentation is organized into the following sections:

<table data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td><a href="/pages/ng8YA6ySlzfaRjm5gtUe"><strong>Quick start</strong></a></td><td>Create your first Singular graphics overlay and control it with the REST API.</td><td>→ <a href="/pages/ng8YA6ySlzfaRjm5gtUe">Get started</a></td></tr><tr><td><a href="/pages/zpYyJjAtCN3uhQjPsQdy"><strong>REST API</strong></a></td><td>Programmatically send and receive data to and from overlays, and animate them In and Out.</td><td>→ <a href="/pages/zpYyJjAtCN3uhQjPsQdy">Use the API</a></td></tr><tr><td><a href="/pages/dZcOon6fAPM2MZ7LYF6b"><strong>Data stream API</strong></a></td><td>Use our low-latency distribution infrastructure to send and receive rapidly changing data.</td><td>→ <a href="/pages/dZcOon6fAPM2MZ7LYF6b">Send high-speed data</a></td></tr><tr><td><a href="/pages/WdFEDGdRL5T118LzGkao"><strong>Composition scripting</strong></a></td><td>Automate overlay formatting, trigger event-based actions based on logic, and more.</td><td>→ <a href="/pages/WdFEDGdRL5T118LzGkao">Add logic to overlays</a></td></tr><tr><td><a href="/pages/dU4JZRZm4Bsrl2QNH0xv"><strong>SDKs</strong></a></td><td>Create and customize graphics, overlays, and widgets with Singular's SDKs.</td><td>→ <a href="/pages/dU4JZRZm4Bsrl2QNH0xv">Use the SDKs</a></td></tr><tr><td><a href="/pages/I9GACxc3k1lms6eBtcOt"><strong>Singular basics</strong></a></td><td>Learn essential Singular concepts to help you create, control, and share overlays. </td><td>→ <a href="/pages/I9GACxc3k1lms6eBtcOt">Understand Singular </a></td></tr><tr><td><a href="/pages/aXWgEtjBNxz9rAu9C4iO"><strong>Support</strong></a></td><td>Get resources to help you quickly get what you need to create overlays for your unique situation.</td><td>→ <a href="/pages/aXWgEtjBNxz9rAu9C4iO">Get help</a></td></tr></tbody></table>

{% hint style="success" %}
Can't find what you're looking for? Search the docs for instant results.
{% endhint %}


# Quick start

Control your first Singular graphic with the REST API

In this guide, you’ll create an overlay, see its output in a web browser, and control it with Singular's REST API.

This quick start uses a [lower thirds](/support/singular-terminology#lower-third) template, but the process is the same for other templates and even overlays you create from scratch.

{% hint style="info" %}
You’ll need a Singular account to follow this quick start. Get your free account [here](https://app.singular.live/users/signup).
{% endhint %}

### **1. Get a template** <a href="#id-55llaltqgpoc" id="id-55llaltqgpoc"></a>

In this step, you'll use the Singular Dashboard to get a control app template.

1. Open the[ Singular Dashboard](https://app.singular.live/dashboard).
2. Select **Templates** to open the template library.
3. In the template library, search for **UNO Lowers | Momentum**.
4. Double click on the template photo to open a preview.
5. Select **Use this template** to download it. UNO Lowers | Momentum will be listed under Apps in the Dashboard.

<figure><img src="/files/yL9f2ic9gYh7licbnp9t" alt=""><figcaption><p>Opening the UNO Lowers | Momentum template</p></figcaption></figure>

### 2. See the output in a web browser <a href="#id-8c38ihv7lccy" id="id-8c38ihv7lccy"></a>

In this step, you'll get the output URL of the UNO Lowers | Momentum control app and paste it into a web browser to see one of the control app's overlays.

1. Select **UNO Lowers | Momentum** under Apps to highlight it.
2. Click the **i** button at the top right of the page to open the [Dashboard Inspector](/support/singular-terminology#dashboard-inspector), which shows details about the app.
3. Under **App URLs and Token**, locate and copy the [Output URL](/support/singular-terminology#output-url).

   <figure><img src="/files/hw75dULkbHYwGEZ77UTE" alt=""><figcaption><p>Locating the output URL</p></figcaption></figure>
4. Paste the Output URL into your internet browser.

The webpage should look like the image below. Keep this tab open for steps three and four to see how the APIs change the overlay.

<figure><img src="/files/TBJh4VxKGUSZcqUjGXLn" alt=""><figcaption><p>UNO Lowers | Momentum overlay output</p></figcaption></figure>

### 3. Send your first Singular API request and see the payload <a href="#id-9mqbwnx4ntkp" id="id-9mqbwnx4ntkp"></a>

In this step, you'll use the Singular API to return JSON data about the UNO Lowers | Momentum overlay.

{% hint style="info" %}
This quick start makes API calls using [Postman](https://www.postman.com) and cURL, but you can also use other languages and API platforms.
{% endhint %}

{% tabs %}
{% tab title="Postman" %}

1. Open the [Singular API v2 Postman collection](https://api.singular.live) and press **Run in Postman**.
2. In the Singular Dashboard, open the Dashboard inspector and copy the **Shared APP Token** for the UNO Lowers | Momentum control app.

   <figure><img src="/files/17WzBcGth6W0DUG0Tx4P" alt=""><figcaption><p>Getting the Shared App Token</p></figcaption></figure>
3. Back in Postman, navigate to **Collections** > **Singular REST API** > **Singular API v2** > **App Instance API (Token)** > **Get Control App Details** > **Get Control App Control**.

   <figure><img src="/files/zumbvygv9MvfYfXUxwut" alt=""><figcaption><p>Locating the Get Control App Control API request</p></figcaption></figure>
4. Replace `:appToken` in the URL with the actual app token from step 3.2.

   <figure><img src="/files/C4dQBGQWn5GpeyVtFL92" alt=""><figcaption><p>Replacing the app token in the URL</p></figcaption></figure>
5. Click **Save** and **Send** to return a payload for the UNO Lowers | Momentum control app.

   <figure><img src="/files/OH33TcHTTdEQmcpQKak1" alt=""><figcaption><p>Saving and sending a request</p></figcaption></figure>
6. Inspect the payload and notice that this control app is set up so the API can access its color, text, and In/Out state. In particular, we'll use this snippet from in the next step:

```json
{
        "subCompositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea",
        "subCompositionName": "Lower - Header",
        "mainComposition": false,
        "state": "In",
        "payload": {
            "Header": "HEADER",
            "Main Text": "MAIN LINE OF TEXT"
        }
    }
```

{% endtab %}

{% tab title="cURL" %}

1. Open your favorite text editor and paste in the following command:&#x20;

{% code overflow="wrap" %}

```bash
curl --location --request GET 'https://app.singular.live/apiv2/controlapps/:appToken/control'
```

{% endcode %}

2\.  In the Singular Dashboard, open the Dashboard inspector and copy the **Shared APP Token** for the UNO Lowers | Momentum control app.

<figure><img src="/files/17WzBcGth6W0DUG0Tx4P" alt=""><figcaption></figcaption></figure>

3\. Back in your text editor, replace `:appToken` in the cURL request with the actual Shared App Token.

4\. Open the Command Prompt in Windows or Terminal in Mac.

5\. Paste the updated cURL request into the command line and press enter. \
The payload (in JSON) should look like this:&#x20;

```json
[{
	"compositionId": "-MwbugjuLzQ1fYlGAczq",
	"compositionName": null,
	"mainComposition": true,
	"animation": {
		"state": "Out1"
	},
	"controlNode": {
		"payload": {
			"Accent Text Color": {
				"a": 1,
				"b": 255,
				"g": 255,
				"r": 255
			},
			"Dropline Color": {
				"a": 1,
				"b": 66,
				"g": 66,
				"r": 66
			},
			"Primary Color": {
				"a": 1,
				"b": 255,
				"g": 255,
				"r": 255
			},
			"Primary Text Color": {
				"a": 1,
				"b": 51,
				"g": 51,
				"r": 51
			},
			"Secondary Color": {
				"a": 1,
				"b": 0,
				"g": 0,
				"r": 180
			}
		}
	}
}, {
	"compositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea",
	"compositionName": "Lower - Header",
	"animation": {
		"state": "Out2"
	},
	"controlNode": {
		"payload": {
			"Header": "HEADER",
			"Main Text": "MAIN LINE OF TEXT"
		}
	}
}, {
	"compositionId": "6cb2b49c-03e1-499b-89b1-93664560e92b",
	"compositionName": "Lower - Header + Dropline",
	"animation": {
		"state": "Out2"
	},
	"controlNode": {
		"payload": {
			"Dropline": "DROPLINE MESSAGE",
			"Header": "HEADER",
			"Main Text": "MAIN LINE OF TEXT"
		}
	}
}, {
	"compositionId": "f771e339-dde9-45ef-a8a4-5545f0bde56c",
	"compositionName": "Lower - Breaking News",
	"animation": {
		"state": "In"
	},
	"controlNode": {
		"payload": {
			"Header": "HEADER",
			"Text 1": "BREAKING NEWS",
			"Text 2": "MAIN LINE OF TEXT"
		}
	}
}]
```

6\. Inspect the payload and notice that this control app is set up so the API can access its color, text, and In/Out state.&#x20;
{% endtab %}
{% endtabs %}

### 4. Use the API to change an overlay's text <a href="#id-9mqbwnx4ntkp" id="id-9mqbwnx4ntkp"></a>

In this step, you'll change the text in the one of UNO Lowers | Momentum's overlays.

{% tabs %}
{% tab title="Postman" %}

1. Navigate to **Collections** > **Singular REST API** > **Singular API v2** > **App Instance API (Token)** > **Send Data to Control App** >**Update Control App Content**.&#x20;

   <figure><img src="/files/mQqANuy4VPyY5DbfH2OJ" alt=""><figcaption><p>Finding the Update Control App Content request</p></figcaption></figure>
2. Update `:appToken` in the URL as you did in [step 3.4](#_9mqbwnx4ntkp).
3. Select the Body tab and replace the JSON date with the code snippet from step 3.6 between the square brackets.

   <figure><img src="/files/dHvYqyHBo4nNIclm6Cly" alt=""><figcaption><p>Updating the request body</p></figcaption></figure>
4. In the JSON payload, change the text for `"HEADER"` and `"MAIN LINE OF TEXT"` to something else.

   <figure><img src="/files/llw4CYlZbtSk3JFIxryM" alt=""><figcaption><p>Updating the sub-composition's text fields</p></figcaption></figure>
5. Click **Save**.
6. With the output URL window from [step two](#_8c38ihv7lccy) visible, press **Send** and watch the text change in the web browser.
   {% endtab %}

{% tab title="cURL" %}
In the payload from the `GET`**Control App Control** request in [step three](#_9mqbwnx4ntkp), this is the sub-composition that you'll update:

<pre class="language-json"><code class="lang-json">{
<strong>    "subCompositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea",
</strong>    "state": "In",
    "payload": {
        "Header": "HEADER",
        "Main Text": "MAIN LINE OF TEXT"
    }
}
</code></pre>

1\. Paste the following cURL request into your text editor:

{% code overflow="wrap" %}

```bash
curl --location --request PATCH 'https://app.singular.live/apiv2/controlapps/:appToken/control'
--header 'Content-Type: application/json'
--data-raw '[ { "subCompositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea", "mainComposition": false, "state": "In", "payload": { "Header": "HEADER", "Main Text": "MAIN LINE OF TEXT" } } ]'
```

{% endcode %}

&#x20;2\. Replace `:appToken` with the actual Shared App Token you got in [step 3.2](#_9mqbwnx4ntkp).

3\. Find the key-value pairs `"Header":"HEADER"` and `"Text 2":"MAIN LINE OF TEXT"` and change the current values (`"HEADER", "MAIN LINE OF TEXT"`) to the text of your choice.

4\. With the output URL window from [step two](#_8c38ihv7lccy) visible, copy your updated `PATCH` request and past it into the command line and press enter. Then, watch the text update in the web browser.
{% endtab %}
{% endtabs %}

### 5. Use the API to trigger an overlay in and out <a href="#id-964rt98h5emt" id="id-964rt98h5emt"></a>

In this step, you'll animate the same overlay in and out.

{% tabs %}
{% tab title="Postman" %}

1. Navigate to the **Update Control App Animation State** request and select the **Body** tab.

   <figure><img src="/files/ZH1LHRnCsl6UvMXNm07L" alt=""><figcaption><p>Finding the Update Control App Animation State request</p></figcaption></figure>
2. Update `:appToken` in the URL as you did in [step 3.4](#_9mqbwnx4ntkp) and 4.2.
3. Change the value of `subCompositionId` to `"57d57254-f15b-42ca-b68e-75b69875f5ea"`, from the previous request, and value of `"state"` to `"In"` or `"Out"` to animate this overlay in or out respectively.

   <figure><img src="/files/8OhoiPLx9LzXF9uLzmi1" alt=""><figcaption><p>Updating an overlay's animation state</p></figcaption></figure>
4. With the output URL window from [step two](#_8c38ihv7lccy) visible, press **Save** and **Send** and then watch the overlay change its animation state.
   {% endtab %}

{% tab title="cURL" %}
In the payload from the `GET`**Control App Control** request in [step three](#_9mqbwnx4ntkp), this is the sub-composition that you'll update to trigger an overlay in and out.

```json
{
    "subCompositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea",
    "state": "In",
    "payload": {
        "Header": "HEADER",
        "Main Text": "MAIN LINE OF TEXT"
    }
}
```

1\. Paste the following cURL request into your text editor:

{% code overflow="wrap" %}

```bash
 curl --location --request PATCH 'https://app.singular.live/apiv2/controlapps/:appToken/control'
--header 'Content-Type: application/json'
--data-raw '[ { "subCompositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea", "state": "In" } ]'
```

{% endcode %}

2\. Replace `:appToken` with the actual Shared App Token you got in [step 3.2](#_9mqbwnx4ntkp).

3\. Find the key-value pair `"state": "In"` and change the value to `"Out"`.

4\. With the output URL window from [step two](#_8c38ihv7lccy) visible, copy your updated `PATCH` request, paste it into the command line, and press enter. Then, watch the overlay go to its Out state.

5\. Using the same API request, change the`"state"` value to `"Out"`.

6\. With the output URL window from [step two](#_8c38ihv7lccy) visible, copy your updated `PATCH` request, paste it into the command line, and press enter. Then, watch the overlay go to its Out state.
{% endtab %}
{% endtabs %}

### Next steps

Now that you've successfully used Singular, check out [Singular Basics](/singular-basics/overview-of-singular) to see how Singular works and learn some key concepts. Or jump right into the [REST APIs](/rest-api/introduction).


# Introduction

An overview of the REST API documentation

The Singular REST API v2 allows you to programmatically send and receive data and trigger animations from Singular overlays.

To help you quickly understand and learn how to use the API, this section of the documentation includes the following sections:

<table data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td><a href="/pages/kKyi6XhocLxM0MUMMXX6"><strong>Rates limits</strong></a></td><td>Learn about the API's daily and burst rate limits.</td><td>→ <a href="/pages/kKyi6XhocLxM0MUMMXX6">Check the rate limits</a></td></tr><tr><td><a href="/pages/nwfDDLwIWmyE8US7McFk"><strong>Authorization</strong></a></td><td>Get the REST API's authorization details.</td><td>→ <a href="/pages/nwfDDLwIWmyE8US7McFk">See how it's authorized</a></td></tr><tr><td><a href="/pages/Eht7MgOQlfw6ywPwJt9z"><strong>How-to guides</strong></a></td><td>See how to accomplish basic tasks with the API.</td><td>→ <a href="/pages/J7VCkPUCS3YiwQ3XJLJ4">Learn to use the API</a></td></tr><tr><td><a href="/pages/Xrppy3CNrWxrwsvWwPKz"><strong>API Reference</strong></a></td><td>Look up the details you need to make a request.</td><td>→ <a href="/pages/Xrppy3CNrWxrwsvWwPKz">Check the reference</a></td></tr><tr><td><a href="https://documenter.getpostman.com/view/2177855/2s83zcTSu6"><strong>Postman collection</strong></a></td><td>Try the Singular REST API v2 collection on Postman.</td><td>→ <a href="https://api.singular.live/">Visit the API collection</a></td></tr></tbody></table>

{% hint style="info" %}
Read this documentation carefully—especially the section describing [rate limits](/rest-api/rate-limits).
{% endhint %}

### Additional endpoints

If you plan to integrate Singular into your platform, [let us know](/support/support-resources#integration-support). We provide additional REST endpoints for Singular Integration Partners.


# Rate limits

Every day, the Singular API receives millions of requests. To manage the volume of requests, limits are placed on the number of requests that one account can make. The Singular API also employs several safeguards against bursts of incoming traffic. If you send a lot of requests in quick succession or if the data you send exceeds your account limit, you might receive error responses that show up as [status code 429](https://tools.ietf.org/html/rfc6585). These limits help to provide a reliable, scalable API.

The API has several limiters, including:

* **Daily limiters**, which limit the number of requests and the amount of data received by the API per day.
* **Burst rate limiters**, which limit the number of requests and the amount of data received by the API per minute.

The daily and burst rate limiters depend on your subscription type.

#### **Rate limits for enterprise subscriptions**

|                    | Daily API limit | Burst API limit (per minute) |
| ------------------ | --------------- | ---------------------------- |
| Number of requests | 100,000 calls   | 500 calls                    |
| Amount of data     | 500 MB          | 1 MB                         |

#### **Rate limits for professional and non-profit subscriptions**

|                    | Daily API limit | Burst API limit (per minute) |
| ------------------ | --------------- | ---------------------------- |
| Number of requests | 20,000 calls    | 200 calls                    |
| Amount of data     | 100 MB          | 500 KB                       |

#### **Rate limits for free subscriptions**

|                    | Daily API limit | Burst API limit (per minute) |
| ------------------ | --------------- | ---------------------------- |
| Number of requests | 5,000 calls     | 25 calls                     |
| Amount of data     | 25 MB           | 100 KB                       |

Treat these limits as maximums and don’t generate unnecessary load. See [Handling rate limit violations](#handling-rate-limit-violations) for advice on handling 429s.&#x20;

If you suddenly see a rising number of rate-limited requests, please contact [Singular Support](mailto:support@singular.live?subject=Rate-Limit-Increase). We may reduce limits to prevent abuse or increase limits to enable high-traffic applications.&#x20;

To request an increased rate limit, contact [Singular Support](mailto:support@singular.live?subject=Rate-Limit-Increase).

For integrating data feeds with high-volume, high-frequency, and low-latency updates, we recommend using [Singular Data Streams](/data-stream-api/introduction).

### Endpoints that use rate limits

* **POST / PATCH:** <https://app.singular.live/apiv2/controlapps/:appToken/control>
* **POST / PATCH:** <https://app.singular.live/apiv2/controlapps/:appToken/command>

### Header response codes

Requests to endpoints that use rate limits return custom headers response codes. The response code is a JSON string with detailed information about daily limits and burst limits.

<table><thead><tr><th width="304">Header</th><th>Value</th></tr></thead><tbody><tr><td><h4><strong>X-Singular-Ratelimit-Burst-Calls</strong></h4></td><td><pre class="language-json" data-overflow="wrap"><code class="lang-json">{"limit": 500, "remaining": 499, "reset": 1614430792}
</code></pre></td></tr><tr><td><h4><strong>X-Singular-Ratelimit-Daily-Calls</strong></h4></td><td><pre class="language-json" data-overflow="wrap"><code class="lang-json">{"limit": 100000, "remaining": 99854, "reset": 1614470401}
</code></pre></td></tr><tr><td><h4><strong>X-Singular-Ratelimit-Burst-Data</strong></h4></td><td><pre class="language-json" data-overflow="wrap"><code class="lang-json">{"limit": 1048576, "remaining": 1048452, "reset": 1614430792}
</code></pre></td></tr><tr><td><h4><strong>X-Singular-Ratelimit-Daily-Data</strong></h4></td><td><pre class="language-json" data-overflow="wrap"><code class="lang-json">{"limit": 524288000, "remaining": 522565096, "reset": 1614470401}
</code></pre></td></tr></tbody></table>

#### Rate limit values

<table><thead><tr><th width="312">Property</th><th>Description</th></tr></thead><tbody><tr><td>limit</td><td>Maximum number of requests per minute or day</td></tr><tr><td>remaining</td><td>Number of calls left within the current minute or day</td></tr><tr><td>reset</td><td>UTC time indicates the next reset of the burst or daily limit</td></tr></tbody></table>

### Common causes and mitigation strategies

Rate limits can occur under a variety of conditions, but it’s most common in these scenarios:

* Running a large volume of closely-spaced requests can lead to the burst rate limiting. This often happens when developing or testing a data feed integration. When engaging in these activities, you should try to control the client side's request rate and data volume (see Handling Rate Limit Violations).
* Sending huge amounts of data to the API can lead to the burst rate limiting. This happens when sending growing statistics data over a period of time. In such a case, you should control the data volume on the client-side and only send the required dynamic data to the API.

### Rate limit violations

Rate limit violations are logged to your [Account Usage Statistics](https://app.singular.live/users/settings/accountStatus) and processed according to your subscription level.

**Professional and enterprise subscriptions**: Rate limit violations are logged, but API requests that exceed the limits are still processed.

**Free subscriptions**: Rate limit violations are logged and API requests exceeding the limits are rejected and return [error status code 429](https://tools.ietf.org/html/rfc6585) `Too Many Requests`.

### How to handle rate limit violations

A basic technique for integrations to handle rate limiting is to watch for `429 status codes` and build in a retry mechanism. Your retry mechanism should follow an exponential backoff schedule to reduce request volume or increase the time between requests when necessary. Ideally, the client-side is self-aware of existing rate limits and can pause requests until the currently exceeded window expires.

We also recommend looking into the following techniques for handling limiting smoothly:

* Add randomness into the requests to avoid a [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem).
* Implement a [token bucket algorithm](https://en.wikipedia.org/wiki/Token_bucket) for controlling rate limits at a global scale.


# Authorization

The Singular API supports two main authentication options depending on the function you call.

* **Tokens:** `Tokens` are used as path variables in the query URL. Requests using `Tokens` require no authentication and support calls for sending data and trigger animations. Learn how to find `Tokens` for app instances [here](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url).&#x20;
* **Private and public tokens:** [Singular data streams](https://api.singular.live/#59563b90-8f36-453e-9f14-ee71d05813ff) use private and public tokens for sending data and receiving data.


# How-to guides

Learn how to accomplish common tasks with the Singular REST API

The how-to guides in this section describe most of the common tasks involved in updating and controlling overlays using the Singular REST API. In these guides, you'll learn how to get information about app instances and sub-compositions, as well as how to update overlay content and animate overlays In and Out. They include:

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/vVtvXw71KwjnRqGm32Ck">How to get a control app's API token</a></td></tr><tr><td><a href="/pages/MkwOpPnxrMRkXY4l96X4">How to get an overlay's sub-composition ID(s)</a></td></tr><tr><td><a href="/pages/9lhKoBHO3ipB0YzZ5uOy">How to get a control app's model</a></td></tr><tr><td><a href="/pages/xEo24fPdtDQkELNJ8xdL">How to get a control app's metadata</a></td></tr><tr><td><a href="/pages/vILoDbXjtlAp31bZtE16">How to update an overlay's content</a></td></tr><tr><td><a href="/pages/rDzOBn9F4cE7prkhunpH">How to animate overlays In and Out</a></td></tr><tr><td><a href="/pages/lq5JW43d06reeRovAr8b">How to simultaneously update and overlay and animate it In and Out</a></td></tr><tr><td><a href="/pages/icUf5chjDMO79ak9ZQeZ">How to update multiple sub-compositions in one request</a></td></tr></tbody></table>

These how-to guides are language and platform agnostic. Thus, you can follow along using cURL or whatever language you use to make API requests, as well as on Postman or the API platform of your choice.

For more basic instructions about getting started with Singular, check out the [quick start](/quick-start). The knowledge you gain going through it will enable you to follow the how-to guides here.

{% hint style="info" %}
These how-to guides require a Singular account, which you can get [here](https://app.singular.live/users/login).
{% endhint %}


# Get a control app's API token

Every API request requires an app token, which refers to a specific instance of a control app and is required for authorization.

The app token replaces `:appToken` in endpoints like this: `https://app.singular.live/apiv2/controlapps/:appToken/control`.

You can find a control app's shared app token here:

* [The Singular Dashboard](#on-the-dashboard) (for all control apps)
* [UNO](#in-uno) (for UNO apps)
* [Studio](#in-studio) (for all other Singular apps)

### How to get a control app's API token from the Dashboard

1. Select your desired app in Dashboard.
2. Press the **i** button at the top right to open the [Dashboard inspector](/support/singular-terminology#dashboard-inspector). The shared app token is listed under URLs and Token.

   <figure><img src="/files/AfgUi9rP7lgNgrdB1gMs" alt=""><figcaption><p>Getting the shared app token on the Dashboard</p></figcaption></figure>

### How to get the API token for an UNO app

* With the app open, press the **\</>** icon to copy the shared app token.

  <figure><img src="/files/Y6R3XuU9VgDpmIacysWc" alt=""><figcaption><p>Getting an Uno app's shared app token</p></figcaption></figure>

### How to get the API token for a Studio app

1. With the app open, press the **three-dot menu** at the top right of screen.
2. Select **Manage Access**.
3. Copy the App Token.

   <figure><img src="/files/62ddVZcTXf20dkvH3vxu" alt=""><figcaption><p>Getting the shared app token for a Studio control app</p></figcaption></figure>


# Get a composition's sub-composition IDs and names and their payload structures

To update graphics in a control app, you need their sub-composition IDs and payload structures.

There are two ways to get them:

* [In Composer](#to-get-a-sub-composition-id-from-composer)
* [Using the API](#to-get-a-sub-composition-id-from-the-api)&#x20;

### Get a composition's sub-composition IDs and names and their payload structures i**n Composer**

1. Open the app's composition in Composer. If the app is from a template and doesn't have an associated composition, you'll need to [extract a composition from the app](/singular-basics/managing-overlays-in-the-dashboard/how-to-extract-a-composition-from-an-app).
2. Navigate to the composition tree and click into each sub-composition you'd like to update.
3. In the lower right quadrant of Composer, click on the wrench icon to open a pop-up that shows the sub-composition's ID and payload structure.

   <figure><img src="/files/rCGCn2cthUzIC1wZuTm4" alt=""><figcaption><p>Getting a sub-composition ID in Composer</p></figcaption></figure>

### Get a composition's sub-composition IDs and names and their payload structures using the API

1. [Get the API token ](/rest-api/how-to-guides/get-a-control-apps-api-token)for the particular app instance that contains the sub-composition ID and payload structure you're looking for.
2. Send a `GET` request to the endpoint for [Get a control app's control data](/rest-api/api-reference/get-control-app-details/get-a-control-apps-control-data) `https://app.singular.live/apiv2/controlapps/:appToken/control` replacing `:appToken` with the actual app token for the app instance.
3. In the response, locate `subCompositionId` and `subCompositionName` and the payload structure of each sub-composition you'd like to update.


# Get a control app's model

By getting an app's model, or payload descriptions, you can see its control parameters in detail, including data types of the control node fields. With this data, you can integrate the Singular API into your app or platform.

To get a control app's model:

1. [Get the control app's app token](/rest-api/how-to-guides/get-a-control-apps-api-token).
2. Send a `GET` request to the endpoint for [Get a control app's model](/rest-api/api-reference/get-control-app-details/get-a-control-apps-model), `https://app.singular.live/apiv2/controlapps/:appToken/model` , replacing `:appToken` with the app token for your app instance.

The response should look like this:

<details>

<summary>The control app's model:</summary>

```json
[
    {
        "id": "-MwbugjuLzQ1fYlGAczq",
        "name": "UNO Lowers | Momentum",
        "model": [
            {
                "defaultValue": {
                    "a": 1,
                    "b": 51,
                    "g": 51,
                    "r": 51
                },
                "id": "Primary Text Color",
                "immediateUpdate": false,
                "index": 3,
                "resetValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "title": "Primary Text Color",
                "type": "color"
            },
            {
                "defaultValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "id": "Accent Text Color",
                "immediateUpdate": false,
                "index": 4,
                "resetValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "title": "Accent Text Color",
                "type": "color"
            },
            {
                "defaultValue": {
                    "a": 1,
                    "b": 66,
                    "g": 66,
                    "r": 66
                },
                "id": "Dropline Color",
                "immediateUpdate": false,
                "index": 2,
                "resetValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "title": "Dropline Color",
                "type": "color"
            },
            {
                "defaultValue": {
                    "a": 1,
                    "b": 0,
                    "g": 0,
                    "r": 180
                },
                "id": "Secondary Color",
                "immediateUpdate": false,
                "index": 1,
                "resetValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "title": "Secondary Color",
                "type": "color"
            },
            {
                "defaultValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "id": "Primary Color",
                "immediateUpdate": false,
                "index": 0,
                "resetValue": {
                    "a": 1,
                    "b": 255,
                    "g": 255,
                    "r": 255
                },
                "title": "Primary Color",
                "type": "color"
            }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": [
            {
                "id": "57d57254-f15b-42ca-b68e-75b69875f5ea",
                "name": "Lower - Header",
                "state": "Out",
                "model": [
                    {
                        "defaultValue": "MAIN LINE OF TEXT",
                        "id": "Main Text",
                        "immediateUpdate": false,
                        "index": 1,
                        "resetValue": "BIDEN DIRECTS STATES TO OPEN VACCINATIONS TO ALL ADULTS BY MAY 1",
                        "rows": 10,
                        "title": "Main Text",
                        "type": "text"
                    },
                    {
                        "defaultValue": "HEADER",
                        "id": "Header",
                        "immediateUpdate": false,
                        "index": 0,
                        "resetValue": "BREAKING NEWS",
                        "rows": 10,
                        "title": "Header",
                        "type": "text"
                    }
                ],
                "logicLayer": {
                    "delay": "auto",
                    "name": "Lower Third",
                    "tag": "#5e35b1"
                },
                "snapshot": null,
                "subcompositions": []
            },
            {
                "id": "6cb2b49c-03e1-499b-89b1-93664560e92b",
                "name": "Lower - Header + Dropline",
                "state": "Out",
                "model": [
                    {
                        "defaultValue": "HEADER",
                        "id": "Header",
                        "immediateUpdate": false,
                        "index": 0,
                        "resetValue": "Default Text",
                        "title": "Header",
                        "type": "text"
                    },
                    {
                        "defaultValue": "MAIN LINE OF TEXT",
                        "id": "Main Text",
                        "immediateUpdate": false,
                        "index": 1,
                        "resetValue": "Default Text",
                        "title": "Main Text",
                        "type": "text"
                    },
                    {
                        "defaultValue": "DROPLINE MESSAGE",
                        "id": "Dropline",
                        "immediateUpdate": false,
                        "index": 2,
                        "resetValue": "Default Text",
                        "title": "Dropline",
                        "type": "text"
                    }
                ],
                "logicLayer": {
                    "delay": "auto",
                    "name": "Lower Third",
                    "tag": "#5e35b1"
                },
                "snapshot": null,
                "subcompositions": []
            },
            {
                "id": "f771e339-dde9-45ef-a8a4-5545f0bde56c",
                "name": "Lower - Breaking News",
                "state": "In",
                "model": [
                    {
                        "defaultValue": "MAIN LINE OF TEXT",
                        "id": "Text 2",
                        "immediateUpdate": false,
                        "index": 2,
                        "resetValue": "BIDEN DIRECTS STATES TO OPEN VACCINATIONS TO ALL ADULTS BY MAY 1",
                        "rows": 10,
                        "title": "Text 2",
                        "type": "text"
                    },
                    {
                        "defaultValue": "BREAKING NEWS",
                        "id": "Text 1",
                        "immediateUpdate": false,
                        "index": 1,
                        "resetValue": "Default Text",
                        "title": "Text 1",
                        "type": "text"
                    },
                    {
                        "defaultValue": "HEADER",
                        "id": "Header",
                        "immediateUpdate": false,
                        "index": 0,
                        "resetValue": "BREAKING NEWS",
                        "rows": 10,
                        "title": "Header",
                        "type": "text"
                    }
                ],
                "logicLayer": {
                    "delay": "auto",
                    "name": "Lower Third",
                    "tag": "#5e35b1"
                },
                "snapshot": null,
                "subcompositions": []
            }
        ]
    }
]
```

</details>


# Get a control app's metadata

An app's metadata includes its Dashboard information and URLs.

To get a control app's metadata:

1. [Get the control app's app token](/rest-api/how-to-guides/get-a-control-apps-api-token).
2. Send a `GET` request to the endpoint for [Get a control app's metadata](/rest-api/api-reference/get-control-app-details/get-a-control-apps-metadata), `https://app.singular.live/apiv2/controlapps/:appToken` replacing `:appToken` with the app token for your app instance.

The response should look like this:

<details>

<summary>The control app's metadata:</summary>

```json
{
    "id": 1689254,
    "userId": 25290,
    "accountId": 22123,
    "thumbnail": "//image.singular.live/fit-in/150x150/7072b13f9e20b98034f48d6202400ff9/images/2uxfaGbSr90q1Xk35HSFX2.jpg",
    "updatedAt": "2022-10-07T08:12:20.000Z",
    "createdAt": "2022-10-07T04:27:59.000Z",
    "type": "controlapp",
    "outputUrl": "https://app.singular.live/output/2xD4oI77sW2KIbNugYoZYD/Output?aspect=16:9",
    "broadcastOutputUrl": "https://app.singular.live/output/2xD4oI77sW2KIbNugYoZYD/Broadcast?aspect=16:9",
    "publicControlUrl": "https://app.singular.live/control/6tM4SnlHlWp9Ll1iBNozQd",
    "publicControlApiUrl": "https://app.singular.live/apiv2/controlapps/6tM4SnlHlWp9Ll1iBNozQd/control",
    "publicCommandApiUrl": "https://app.singular.live/apiv2/controlapps/6tM4SnlHlWp9Ll1iBNozQd/command",
    "publicModelApiUrl": "https://app.singular.live/apiv2/controlapps/6tM4SnlHlWp9Ll1iBNozQd/model",
    "appTemplateId": 518,
    "appTemplateVersion": 10,
    "name": "UNO Lowers | Momentum",
    "folder": "5389373f-f476-4cdc-99c8-0593c046c5a3",
    "compositionId": 441440
}
```

</details>


# Update a sub-composition's content

To update a sub-composition's content, you'll need two things:

* The[ app token](/rest-api/how-to-guides/get-a-control-apps-api-token) for the particular instance of the control app you're using
* The[ sub-composition's name or ID and its payload structure](/rest-api/how-to-guides/get-a-compositions-sub-composition-ids-and-names-and-their-payload-structures)

With that data on hand

1. Prepare a `PATCH` request to `https://app.singular.live/apiv2/controlapps/:appToken/control`, which is the endpoint to [update a control app](/rest-api/api-reference/send-data-to-a-control-app/update-a-control-apps-content).&#x20;
2. Replace `:appToken` with the app token for your app instance.
3. Add a request body with the actual sub-composition ID and payload structure, and include the updates you'd like to make. The request body should look like this, but with sub-composition name or ID and payload data filled in.

{% tabs %}
{% tab title="Sub-composition name" %}

```json
[
    {
        "subCompositionName": "your-subcomposition-name",
        "payload": {
          "controlnode-field": "control node content"
        }
    }
]
```

{% endtab %}

{% tab title="Sub-composition ID" %}

```json
[
    {
        "subCompositionId": "your-subcomposition-id",
        "payload": {
          "controlnode-field": "control node content"
        }
    }
]
```

{% endtab %}
{% endtabs %}

After sending the request, you should get a `200 OK` response with a payload of `{ "success": true }`.


# Update a sub-composition's animation state

To send overlays to their In and Out states, you'll need two things:

* The[ app token](/rest-api/how-to-guides/get-a-control-apps-api-token) for the particular instance of the control app you're using
* The[ sub-composition ID and payload structure](/rest-api/how-to-guides/get-a-compositions-sub-composition-ids-and-names-and-their-payload-structures) of the overlay you want to update

With that data on hand

1. Prepare a `PATCH` request to `https://app.singular.live/apiv2/controlapps/:appToken/control`, which is the endpoint to [animate a control app In and Out](/rest-api/api-reference/send-data-to-a-control-app/update-a-control-apps-animation-state).
2. Replace `:appToken` with the actual app token for your app instance.
3. Add a request body with the actual sub-composition ID and update the value of the sub-composition's state to `In` or `Out`. The request body should look like this, but with sub-composition ID and state filled in.

{% tabs %}
{% tab title="Sub-composition name" %}

```json
[
    {
        "subCompositionName": "your-subcomposition-name",
        "state": "<toState>"
    }
]
```

{% endtab %}

{% tab title="Sub-composition ID" %}

```json
[
    {
        "subCompositionId": "your-subcomposition-id",
        "state": "<toState>"
    }
]
```

{% endtab %}
{% endtabs %}

After sending the request, you should get a `200 OK` response with a payload of `{ "success": true }`.


# Update a sub-composition's content and animation state in one call

To update a sub-composition's content and update animation state in one API request, you'll need two things:

* The[ app token](/rest-api/how-to-guides/get-a-control-apps-api-token) for the particular instance of the control app you're using
* The[ sub-composition ID and payload structure](/rest-api/how-to-guides/get-a-compositions-sub-composition-ids-and-names-and-their-payload-structures) of the overlay you want to update

With that data on hand

1. Prepare a `PATCH` request to `https://app.singular.live/apiv2/controlapps/:appToken/control`, which is the endpoint to [update and animate a control app In and Out](broken://pages/xE5E7LbfYihvan3VBARB).
2. Replace `:appToken` with the actual app token for your app instance.
3. Add a request body with the actual sub-composition ID and payload structure. Include the updates you'd like to make and change `state` to `In` or `Out`. The request body should look like this, but with the sub=composition ID, payload data, and state filled in.

{% tabs %}
{% tab title="Sub-composition name" %}

```json
[
    {
        "subCompositionName": "your-subcomposition-name",
        "payload": {
           "controlnode-field": "control node content"
         },
        "state": "<toState>"
    }
]
```

{% endtab %}

{% tab title="Sub-composition ID" %}

```json
[
    {
        "subCompositionId": "your-subcomposition-id",
        "payload": {
           "controlnode-field": "control node content"
         },
        "state": "<toState>"
    }
]
```

{% endtab %}
{% endtabs %}

After sending the request, you should get a `200 OK` response with a payload of `{ "success": true }`.


# Update multiple sub-compositions in one call

To update multiple sub-compositions in one API call, you'll need two things:

* The[ app token](/rest-api/how-to-guides/get-a-control-apps-api-token) for the particular instance of the control app you're using
* The[ sub-composition IDs and payload structures](/rest-api/how-to-guides/get-a-compositions-sub-composition-ids-and-names-and-their-payload-structures) of the overlays you want to update

With that data on hand

1. Prepare a `PATCH` request to `https://app.singular.live/apiv2/controlapps/:appToken/control`, which is the endpoint to [update a control app](/rest-api/api-reference/send-data-to-a-control-app/update-a-control-apps-content).&#x20;
2. Replace `:appToken` with the actual app token for your app instance.
3. Add a request body with the actual sub-composition IDs and payload structures, and include the updates you'd like to make. The request body should look like this, but with sub-composition IDs and payload data filled in.

{% tabs %}
{% tab title="Sub-composition name" %}

```json
[
    {
        "subCompositionName": "your-subcomposition-name-1",
        "payload": {
           "controlnode-field": "control node content"
         },
    },
    {
        "subCompositionName": "your-subcomposition-name-2",
        "payload": {
           "controlnode-field": "control node content"
         },
        "state": "<toState>"
    }
]
```

{% endtab %}

{% tab title="Sub-composition ID" %}

```json
[
    {
        "subCompositionId": "your-subcomposition-id-1",
        "payload": {
           "controlnode-field": "control node content"
         },
    },
    {
        "subCompositionId": "your-subcomposition-id-2",
        "payload": {
           "controlnode-field": "control node content"
         },
        "state": "<toState>"
    }
]
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
You can also send the final sub-composition in the list to its In or Out state with this request.
{% endhint %}

After sending the request, you should get a `200 OK` response with a payload of `{ "success": true }`.


# API reference

The API reference documentation in this section includes endpoints to [get a control app's details](/rest-api/api-reference/get-control-app-details) and [send data to a control app](/rest-api/api-reference/send-data-to-a-control-app), and [take out all of an app's input](/rest-api/api-reference/take-out-all-of-an-apps-output). Included are:

* A resource description
* Endpoints and methods
* Parameters
* A request example
* A response example and schema


# Get control app details

These API calls return information about control apps, including their [metadata](/rest-api/api-reference/get-control-app-details/get-a-control-apps-metadata), [model](/rest-api/api-reference/get-control-app-details/get-a-control-apps-model), and [controllable elements](/rest-api/api-reference/get-control-app-details/get-a-control-apps-control-data).


# Get a control app's metadata

## Get control app metadata

<mark style="color:blue;">`GET`</mark> `https://app.singular.live/apiv2/controlapps/:appToken`

Returns the metadata of a control app instance.

#### Path Parameters

| Name                                       | Type   | Description                                                                                                                                                                                                                      |
| ------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| appToken<mark style="color:red;">\*</mark> | String | The shared app token for the app. This is a unique token for this app instance. [How to find the shared app token](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url). |

{% tabs %}
{% tab title="200: OK JSON payload" %}

```json
{
  "id": 1630476,
  "userId": 25290,
  "accountId": 22123,
  "thumbnail": "//image.singular.live/fit-in/150x150/f12f184c9a0eb763beb40478e02a1250/images/4GcvLpm2JA5lUFrshMO7vK.png",
  "updatedAt": "2022-09-26T10:41:36.000Z",
  "createdAt": "2022-09-22T15:46:54.000Z",
  "type": "controlapp",
  "outputUrl": "https://app.singular.live/output/3AVwVLk51gGVCFWckgBcti/Output?aspect=16:9",
  "broadcastOutputUrl": "https://app.singular.live/output/3AVwVLk51gGVCFWckgBcti/Broadcast?aspect=16:9",
  "publicControlUrl": "https://app.singular.live/control/1H7sRGX6UhgQHlAgtAXTB6",
  "publicControlApiUrl": "https://app.singular.live/apiv2/controlapps/1H7sRGX6UhgQHlAgtAXTB6/control",
  "publicCommandUrl": "https://app.singular.live/apiv2/controlapps/1H7sRGX6UhgQHlAgtAXTB6/command",
  "publicModelApiUrl": "https://app.singular.live/apiv2/controlapps/1H7sRGX6UhgQHlAgtAXTB6/model",
  "appTemplateId": 518,
  "appTemplateVersion": 10,
  "name": "REST-Control Node Field Types app",
  "folder": "7623f754-e32a-48f8-8d2d-46105df16ea0",
  "compositionId": 454606
}
```

{% endtab %}
{% endtabs %}

### Response definitions

The following table describes each item in the response.

<table><thead><tr><th width="204">Response item</th><th width="377">Description</th><th width="111">Data type</th></tr></thead><tbody><tr><td>id</td><td>The app's internal ID in the database.</td><td>Integer</td></tr><tr><td>userId</td><td>The ID of the user that created the app.</td><td>Integer</td></tr><tr><td>accountId</td><td>The account that the app is stored in.</td><td>Integer</td></tr><tr><td>thumbnail</td><td>The URL of the app's thumbnail preview on the Dashboard.</td><td>URL</td></tr><tr><td>updatedAt</td><td>The most recent time the app was updated.</td><td>String</td></tr><tr><td>createdAt</td><td>When the app was created.</td><td>String</td></tr><tr><td>type</td><td>The type of overlay, which for control apps is <code>controlapp</code>.</td><td>String</td></tr><tr><td>outputUrl</td><td>The app's output URL.</td><td>URL</td></tr><tr><td>broadcastOutputUrl</td><td>The app's broadcast output URL.</td><td>URL</td></tr><tr><td>publicControlUrl</td><td>The app's public control URL.</td><td>URL</td></tr><tr><td>publicControlApiUrl</td><td>The app's public control API URL.</td><td>URL</td></tr><tr><td>publicCommandUrl</td><td>The app's public command URL.</td><td>URL</td></tr><tr><td>publicModelApiUrl</td><td>The app's broadcast API URL.</td><td>URL</td></tr><tr><td>appTemplateId</td><td>The app's template ID.</td><td>Integer</td></tr><tr><td>appTemplateVersion</td><td>The app template version.</td><td>Integer</td></tr><tr><td>compositionJson</td><td>The composition JSON URL</td><td>URL</td></tr><tr><td>name</td><td>The app's name.</td><td>String</td></tr><tr><td>folder</td><td>The ID of the folder where the app lives.</td><td>String</td></tr><tr><td>compositionId</td><td>The ID of the composition linked to this app.</td><td>Integer</td></tr></tbody></table>


# Get a control app's model

## Get control app model

<mark style="color:blue;">`GET`</mark> `https://app.singular.live/apiv2/controlapps/:appToken/model`

Returns the model of a control app instance, including the content, type, and structure of its controllable elements. Can be used to build a user interface.

#### Path Parameters

| Name                                       | Type   | Description                                                                                                                                                                                                                      |
| ------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| appToken<mark style="color:red;">\*</mark> | String | The shared app token for the app. This is a unique token for this app instance. [How to find the shared app token](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url). |

{% tabs %}
{% tab title="200: OK JSON payload" %}

```json
[
  {
    "id": "-NC_s8grzroliz2xbx7e",
    "name": "default",
    "model": [],
    "logicLayer": null,
    "snapshot": null,
    "subcompositions": [
      {
        "id": "1695dbd7-430c-40eb-82f7-85287a965849",
        "name": "compTextArea",
        "state": "Out",
        "model": [
          {
            "defaultValue": "Multi-line Text \nfrom Google Sheet",
            "id": "text-area-control-node-id",
            "immediateUpdate": false,
            "index": 1,
            "resetValue": "Default Text",
            "title": "text-area-control-node-id",
            "type": "textarea"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "1e602003-5bfa-45f4-8049-8892220fdff2",
        "name": "compTimer",
        "state": "Out",
        "model": [
          {
            "defaultValue": {
              "UTC": 1615816679354,
              "isRunning": false,
              "value": 0
            },
            "id": "time-control-node-id",
            "immediateUpdate": true,
            "index": 0,
            "resetValue": {
              "UTC": 0,
              "isRunning": false,
              "value": 0
            },
            "title": "time-control-node-id",
            "type": "timecontrol"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "41aaf130-468c-4f11-9bca-c1ea68ba1c8b",
        "name": "compColor",
        "state": "Out",
        "model": [
          {
            "defaultValue": {
              "a": 1,
              "b": 156,
              "g": 188,
              "r": 26
            },
            "id": "color-control-node-id",
            "immediateUpdate": false,
            "index": 2,
            "resetValue": {
              "a": 1,
              "b": 255,
              "g": 255,
              "r": 255
            },
            "title": "color-control-node-id",
            "type": "color"
          },
          {
            "defaultValue": "crimson",
            "id": "cnColorText",
            "immediateUpdate": false,
            "index": 1,
            "resetValue": "Default Text",
            "title": "cnColorText",
            "type": "text"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "755bd36f-fff0-4250-b703-4cbd310f07fb",
        "name": "compVideoClip",
        "state": "Out",
        "model": [
          {
            "defaultValue": {
              "__singularButton": true,
              "ts": 0
            },
            "id": "cnVideoClipPause",
            "immediateUpdate": true,
            "index": 2,
            "resetValue": {
              "__singularButton": true,
              "ts": 0
            },
            "title": "cnVideoClipPause",
            "type": "button"
          },
          {
            "defaultValue": {
              "__singularButton": true,
              "ts": 0
            },
            "id": "cnVideoClipPlay",
            "immediateUpdate": true,
            "index": 1,
            "resetValue": {
              "__singularButton": true,
              "ts": 0
            },
            "title": "cnVideoClipPlay",
            "type": "button"
          },
          {
            "defaultValue": {
              "__singularButton": true,
              "ts": 0
            },
            "id": "cnVideoClipStart",
            "immediateUpdate": true,
            "index": 0,
            "resetValue": {
              "__singularButton": true,
              "ts": 0
            },
            "title": "cnVideoClipStart",
            "type": "button"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "7ccc118d-5333-474d-9a33-b5fb951cd765",
        "name": "compJsonTable",
        "state": "Out",
        "model": [
          {
            "defaultValue": "{\"content\": [\r\n    {\"pos\": \"1\", \"name\": \"NYG\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/4BhR3iwVI7oTpuMPQFSNBE.png\"},\r\n    {\"pos\": \"2\", \"name\": \"SF\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/1PpN2FpuAutk3zeYILkDum.png\"},\r\n    {\"pos\": \"3\", \"name\": \"DEN\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/7Mg4XYFRQWiwT5IKIoH1E7.png\"},\r\n    {\"pos\": \"4\", \"name\": \"PIT\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/5K84dUL3ACt2l4pCCfwRP4.png\"}\r\n  ]\r\n}",
            "height": "100px",
            "id": "json-control-node-id",
            "immediateUpdate": false,
            "index": 0,
            "resetValue": "{}",
            "title": "json-control-node-id",
            "type": "json"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "816df4ac-3231-49a2-b744-ccd48aa33a40",
        "name": "compText",
        "state": "Out",
        "model": [
          {
            "defaultValue": "Text from Google Sheet",
            "id": "text-control-node-id",
            "immediateUpdate": false,
            "index": 0,
            "resetValue": "Default Text",
            "title": "text-control-node-id",
            "type": "text"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "9d0ebd84-bdae-6adc-2050-d54c36448d70",
        "name": "background",
        "state": "Out",
        "model": [],
        "logicLayer": {
          "name": "Background",
          "tag": "#5e35b1"
        },
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "ab6e39f9-5550-47c2-81be-fa07611a6666",
        "name": "compCheckbox",
        "state": "Out",
        "model": [
          {
            "defaultValue": true,
            "id": "checkbox-control-node-id",
            "immediateUpdate": false,
            "index": 0,
            "resetValue": true,
            "title": "checkbox-control-node-id",
            "type": "checkbox"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      },
      {
        "id": "ca6f12cf-d990-464b-948b-5d7ad9dd3d33",
        "name": "compNumber",
        "state": "Out",
        "model": [
          {
            "defaultValue": "1234.56",
            "id": "number-control-node-id",
            "immediateUpdate": false,
            "index": 0,
            "resetValue": 0,
            "step": "0.01",
            "title": "number-control-node-id",
            "type": "number"
          },
          {
            "defaultValue": "$1,234.56",
            "id": "cnNumberDisplayed",
            "immediateUpdate": false,
            "index": 1,
            "resetValue": "Default Text",
            "title": "cnNumberDisplayed",
            "type": "text"
          }
        ],
        "logicLayer": null,
        "snapshot": null,
        "subcompositions": []
      }
    ]
  }
]
```

{% endtab %}
{% endtabs %}

### Response definitions

The following table describes the top level key-value pairs for a sub-composition in the **Get control app model** request.

<table><thead><tr><th width="178">Response item</th><th width="441">Description</th><th>Data type</th></tr></thead><tbody><tr><td>id</td><td>Composition ID or sub-composition ID, depending where it is in the hierarchy.</td><td>String</td></tr><tr><td>name</td><td>Composition name or sub-composition name, depending where it is in the hierarchy.</td><td>String</td></tr><tr><td>state</td><td>The in/out state of a sub-composition. Supported values: <code>In</code>, <code>Out</code>, <code>Out1</code>, <code>Out2</code>. <code>In</code> and <code>Out</code> are default. When there are multiple timelines, <code>Out1</code>  plays in reverse of the In animation. <code>Out2</code> defines a separate <code>Out</code> animation.</td><td>String</td></tr><tr><td>model</td><td>The sub-composition's model. It contains a <code>defaultValue</code>, <code>id</code>, <code>immediateUpdate</code>, <code>index</code>, <code>resetValue</code>, <code>title</code>, and <code>type</code>. See the <a href="#model-contents">model content table</a> below for descriptions.</td><td>Array</td></tr><tr><td>logicLayer</td><td>The sub-compositions assigned logic layer, if any; default is null. See the <a href="#logic-layer-contents">logicLayer content table</a> below for a description of its content.</td><td>Object</td></tr><tr><td>snapshot</td><td>The URL of the snapshot shown when hovering over a sub-composition in the composition tree in Composer. Generate and invoke them from camera icon in Composer's preview toolbar.</td><td>URL</td></tr><tr><td>subcompositions</td><td>An array of sub-compositions (if any) nested within a composition or sub-composition.</td><td>Array</td></tr></tbody></table>

### Model content

The key-value pairs within the `model` array.

|                 |                                                                                                                                     |                         |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| defaultValue    | The sub-composition's default value. Defined in the overlay's composition.                                                          | String, Integer, Object |
| id              | The Model ID.                                                                                                                       | String                  |
| immediateUpdate | A flag used by control applications to immediately send data.                                                                       | Boolean                 |
| index           | Defines the control node field's position in the hierarchy of control nodes fields. 0 is the top position.                          | Number                  |
| resetValue      | The sub-composition's reset value. Defined in the overlay's composition.                                                            | String, Integer, Object |
| title           | The title of the control node field in Composer.                                                                                    | String                  |
| type            | Field type. Supported values: `text`, `textarea`, `number`, `image`, `color`, `checkbox`, `audio`, `json`, `timecontrol`, `button`. | String                  |

Field types

| Type        | Description                          | Data type      |
| ----------- | ------------------------------------ | -------------- |
| text        | The field type for a single of text. | String         |
| textarea    | The field type for multi-line text.  | String         |
| number      | The field type for a number.         | Integer        |
| image       | The field type for an image.         | URL            |
| color       | The field type for a color.          | Object, String |
| checkbox    | The field type for a checkbox.       | Boolean        |
| audio       | The field type for an audio file.    | URL            |
| json        | The field type for JSON data.        | Object         |
| timecontrol | The field type for a timer.          | Object         |
| button      | The field type for a button.         | Object         |

### `logicLayer` content

The content within the logicLayer object.

| Response item | Description                                                                                             | Data type |
| ------------- | ------------------------------------------------------------------------------------------------------- | --------- |
| name          | The logic layer's name.                                                                                 | String    |
| delay         | The logic later's delay type as defined in the composition. Supported values: `none`, `auto`, `custom`. | String    |
| tag           | The hex color value assigned to the logic layer. Example: `#e53935`                                     | String    |


# Get a control app's control data

## Get control app control

<mark style="color:blue;">`GET`</mark> `https://app.singular.live/apiv2/controlapps/:appToken/control`

Returns a control app instance's control properties, or a list of its sub-compositions, including all of its control nodes fields. Use this call to get the IDs you need to control various elements in a control app.

#### Path Parameters

| Name                                       | Type   | Description                                                                                                                                                                                                                      |
| ------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| appToken<mark style="color:red;">\*</mark> | String | The shared app token for the app. This is a unique token for this app instance. [How to find the shared app token](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url). |

{% tabs %}
{% tab title="200: OK JSON payload" %}

```json
[
  {
    "subCompositionId": "-NC_s8grzroliz2xbx7e",
    "mainComposition": true,
    "state": "Out1",
    "payload": {}
  },
  {
    "subCompositionId": "1695dbd7-430c-40eb-82f7-85287a965849",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "text-area-control-node-id": "Multi-line Text \nfrom Google Sheet"
    }
  },
  {
    "subCompositionId": "1e602003-5bfa-45f4-8049-8892220fdff2",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "time-control-node-id": {
        "UTC": 1664044077270,
        "isRunning": true,
        "value": 9917
      }
    }
  },
  {
    "subCompositionId": "41aaf130-468c-4f11-9bca-c1ea68ba1c8b",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "cnColorText": "crimson",
      "color-control-node-id": {
        "a": 1,
        "b": 230,
        "g": 216,
        "r": 173
      }
    }
  },
  {
    "subCompositionId": "57d57254-f15b-42ca-b68e-75b69875f5ea",
    "mainComposition": false,
    "state": "Out2",
    "payload": {}
  },
  {
    "subCompositionId": "755bd36f-fff0-4250-b703-4cbd310f07fb",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "cnVideoClipPause": {
        "__singularButton": true,
        "ts": 0
      },
      "cnVideoClipPlay": {
        "__singularButton": true,
        "ts": 0
      },
      "cnVideoClipStart": {
        "__singularButton": true,
        "ts": 0
      }
    }
  },
  {
    "subCompositionId": "7b7c8944-ba1f-4eec-a361-2c9ce308d462",
    "mainComposition": false,
    "state": "Out2",
    "payload": {
      "image": "https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/4BhR3iwVI7oTpuMPQFSNBE.png",
      "name": "NYG",
      "pos": "88"
    }
  },
  {
    "subCompositionId": "7ccc118d-5333-474d-9a33-b5fb951cd765",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "json-control-node-id": "{\"content\": [\r\n    {\"pos\": \"1\", \"name\": \"NYG\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/4BhR3iwVI7oTpuMPQFSNBE.png\"},\r\n    {\"pos\": \"2\", \"name\": \"SF\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/1PpN2FpuAutk3zeYILkDum.png\"},\r\n    {\"pos\": \"3\", \"name\": \"DEN\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/7Mg4XYFRQWiwT5IKIoH1E7.png\"},\r\n    {\"pos\": \"4\", \"name\": \"PIT\", \"image\": \"https://image.singular.live/f12f184c9a0eb763beb40478e02a1250/images/5K84dUL3ACt2l4pCCfwRP4.png\"}\r\n  ]\r\n}"
    }
  },
  {
    "subCompositionId": "816df4ac-3231-49a2-b744-ccd48aa33a40",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "text-control-node-id": "Text from Google Sheet"
    }
  },
  {
    "subCompositionId": "9d0ebd84-bdae-6adc-2050-d54c36448d70",
    "mainComposition": false,
    "state": "Out1",
    "payload": {}
  },
  {
    "subCompositionId": "ab6e39f9-5550-47c2-81be-fa07611a6666",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "checkbox-control-node-id": true
    }
  },
  {
    "subCompositionId": "ca6f12cf-d990-464b-948b-5d7ad9dd3d33",
    "mainComposition": false,
    "state": "Out1",
    "payload": {
      "cnNumberDisplayed": "$1,234.56",
      "number-control-node-id": "1234.56"
    }
  }
]
```

{% endtab %}
{% endtabs %}

### Response definitions

The following table describes each item in the response.

| Response item      | Description                                                                    | Data type |
| ------------------ | ------------------------------------------------------------------------------ | --------- |
| subCompositionId   | The sub-composition ID.                                                        | String    |
| subCompositionName | The name of the sub-composition.                                               |           |
| mainComposition    | The main composition ID.                                                       | String    |
| state              | The in/out state of a sub-composition. Supported values: `In`, `Out1`, `Out2`. | String    |
| payload            | A JSON object that contains control-level elements.                            | Object    |


# Send data to a control app

The API request

**`PATCH`**` ``https://app.singular.live/apiv2/controlapps/:appToken/control`,

is used to do the following:

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/GvaUCghhKuzsOMYpFbFO">Update a control app's content</a></td></tr><tr><td><a href="/pages/H1ao7yGBN02RzpMbCJTQ">Update a control app's animation state</a></td></tr><tr><td><a href="/pages/xE5E7LbfYihvan3VBARB">Update a control app's content and animation state</a></td></tr></tbody></table>


# Update a control app's content

## Update control app

<mark style="color:purple;">`PATCH`</mark> `https://app.singular.live/apiv2/controlapps/:appToken/control`

Updates the data of a sub-composition in an overlay. Either `subCompositionID` or `subCompositionName` is required.

#### Path Parameters

| Name     | Type   | Description                                                                                                                                                                                                        |
| -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| appToken | String | The shared app token is a unique token for a single app instance. [How to find the shared app token](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url). |

#### Request Body

| Name               | Type      | Description                                                                                                                                                                                                                                                             |
| ------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| subCompositionId   | String    | The sub-composition ID. Either the `subCompositionID` or the `subCompositionName` must be included in the request. Get them from the payload of the [Get control app model](/rest-api/api-reference/get-control-app-details/get-a-control-apps-model) request.          |
| payload            | Object    | A JSON object that contains control-level elements of the sub-composition.                                                                                                                                                                                              |
| controlnodeID      | String    | The ID of a control node.                                                                                                                                                                                                                                               |
| controlNodeContent | FieldType | New control node content.                                                                                                                                                                                                                                               |
| subCompositionName | String    | The name of the sub-composition. Either the `subCompositionID` or the `subCompositionName` must be included in the request. Get them from the payload of the [Get control app model](/rest-api/api-reference/get-control-app-details/get-a-control-apps-model) request. |

{% tabs %}
{% tab title="200: OK Success" %}

```json
{
    "success": true
}
```

{% endtab %}
{% endtabs %}

### Example request bodies by sub-composition name or ID&#x20;

{% tabs %}
{% tab title="By name" %}

```json
[
  {
    "subCompositionName": "<subCompositionName>",
    "payload": {
      "<controlnodeId>": "<controlnode content>",
      "<...>": "<...>"
    }
  }, {
    ... 
  }
]
```

{% endtab %}

{% tab title="By ID" %}

```json
[
  {
    "subCompositionId": "<subcompositionId>",
    "payload": {
      "<controlnodeId>": "<controlnode content>",
      "<...>": "<...>"
    }
  }, {
    ... 
  }
]
```

{% endtab %}
{% endtabs %}

### Control node field types

<table data-full-width="true"><thead><tr><th>&#x3C;fieldType></th><th>Example</th><th>Description</th></tr></thead><tbody><tr><td>&#x3C;Text></td><td>"John Doe"</td><td>A single line of text.</td></tr><tr><td>&#x3C;Text Area></td><td>"Title/nSubtitle"</td><td>Multiple lines of text.</td></tr><tr><td>&#x3C;Number></td><td>123.45</td><td>A number.</td></tr><tr><td>&#x3C;NormalizedNumber></td><td>50.0</td><td>A value between 0 and 100.</td></tr><tr><td>&#x3C;Counter></td><td>1</td><td>A number.</td></tr><tr><td>&#x3C;Selection></td><td>"option"</td><td>The id of the selected item from the option list.</td></tr><tr><td>&#x3C;Image></td><td>"https://www.gstatic.com/webp/gallery3/1.png"</td><td>An image URL.</td></tr><tr><td>&#x3C;Color></td><td>"lightgray", "#33AAFF", "#33AAFF55", rbg(150, 150, 150)", rgba(255, 150, 150, 0.5)"</td><td>An HTML color name, a color HEX code, a color HEX code with alpha, a color rgb value, or a color rgba value.</td></tr><tr><td>&#x3C;Checkbox></td><td>true, false</td><td>A checkbox checked (true) or unchecked (false).</td></tr><tr><td>&#x3C;Audio></td><td>"https://domain.com/audio.mp3"</td><td>The URL of an audio file.</td></tr><tr><td>&#x3C;JSON Text></td><td>"{"id": 1,"firstname": "John","lastname": "Doe"}"</td><td>A stringified JSON object.</td></tr><tr><td>&#x3C;Time Control></td><td>{"command": "play"}, {"command": "pause"}, {"command": "reset"},<br>{"command": "start"}</td><td>Plays, pauses, starts, or resets the timer.</td></tr><tr><td>&#x3C;Button></td><td>"execute"</td><td>Triggers a button click event.</td></tr></tbody></table>


# Update a control app's animation state

## Update control app animation state

<mark style="color:purple;">`PATCH`</mark> `https://app.singular.live/apiv2/controlapps/:appToken/control`

Animates an overlay to its In or Out state.

#### Path Parameters

| Name                                       | Type   | Description                                                                                                                                                                                                                      |
| ------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| appToken<mark style="color:red;">\*</mark> | String | The shared app token for the app. This is a unique token for this app instance. [How to find the shared app token](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url). |

#### Request Body

| Name                                                 | Type   | Description                                                                                                                                                                                                                                                             |
| ---------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| subCompositionId<mark style="color:red;">\*</mark>   | String | The sub-composition ID. Either the `subCompositionID` or the `subCompositionName` must be included in the request. Get them from the payload of the [Get control app model](/rest-api/api-reference/get-control-app-details/get-a-control-apps-model) request.          |
| state<mark style="color:red;">\*</mark>              | String | <p>Animates an overlay to its In or Out state.</p><p>Supported values: <code>In</code>, <code>Out1</code>, <code>Out2</code></p>                                                                                                                                        |
| subCompositionName<mark style="color:red;">\*</mark> | String | The name of the sub-composition. Either the `subCompositionID` or the `subCompositionName` must be included in the request. Get them from the payload of the [Get control app model](/rest-api/api-reference/get-control-app-details/get-a-control-apps-model) request. |

{% tabs %}
{% tab title="200: OK Success" %}

```json
{
    "success": true
}
```

{% endtab %}
{% endtabs %}

### Example request bodies by sub-composition name or ID

{% tabs %}
{% tab title="Subcomposition Name" %}

```json
[
    {
        "subCompositionName": "<subCompositionName>",
        "state": "<toState>"
    }
]
```

{% endtab %}

{% tab title="Subcomposition Id" %}

```json
[
    {
        "subCompositionId": "< string >",
        "state": "< toState >"
    },
    {
        "subCompositionId": "<...>",
        "state": "<...>"
    }
]
```

{% endtab %}
{% endtabs %}

### Animation state

<table><thead><tr><th width="174"> Animation state</th><th>Description</th></tr></thead><tbody><tr><td>In</td><td>Sends an overlay to its <code>In</code> state.</td></tr><tr><td>Out</td><td>Sends an overlay to its <code>Out</code> state. This is the default <code>Out</code> animation.</td></tr><tr><td>Out1</td><td>When there are multiple timelines, <code>Out1</code> animates an overlay <code>Out</code> in reverse of the <code>In</code> animation.</td></tr><tr><td>Out2</td><td>When there are multiple timelines, <code>Out2</code> defines an independent <code>Out</code> animation.</td></tr></tbody></table>


# Take out all of an app's output

## Take out all output

<mark style="color:green;">`POST`</mark> `https://app.singular.live/apiv2/controlapps/:appToken/command`

Animates all overlays in a control app to their `Out` state.

#### Path Parameters

| Name                                       | Type   | Description                                                                                                                                                                                                                      |
| ------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| appToken<mark style="color:red;">\*</mark> | String | The shared app token for the app. This is a unique token for this app instance. [How to find the shared app token](/singular-basics/managing-overlays-in-the-dashboard/how-to-find-an-apps-shared-app-token-and-shared-api-url). |

#### Request Body

| Name                                     | Type   | Description                                               |
| ---------------------------------------- | ------ | --------------------------------------------------------- |
| action<mark style="color:red;">\*</mark> | String | The command property. Required value: `TakeOutAllOutput`. |

{% tabs %}
{% tab title="200: OK Success" %}

```javascript
{
    "success": true
}
```

{% endtab %}
{% endtabs %}

### Request body

```json
{
    "action": "TakeOutAllOutput"
}
```


# Introduction

A faster way to send data

Singular REST API calls are sent over the public internet, which takes about a second depending on how they are routed, and works well in most situations. However, when you need to send data quickly, Singular also offers the data stream API.&#x20;

The data stream API sends data to compositions through a direct web socket connection so they are stable and low latency (below 300 ms).

Use data streams for any situation that requires updating data at 1 Hz or faster, from hockey and basketball clocks to sub-second penalty clocks, race car telemetry, the geo-location of planes, in-game e-sports statistics, and more.

### Understanding data streams

Data streams are set up on the Dashboard and updated with the data stream API. Payloads sent to data streams persist until new payloads arrive. Clients connecting to a data stream immediately receive the latest payload. The data streams backend includes a UTC timestamp to payloads. The client can use the timecode to calculate the latency and evaluate the age of the payload.


# Rate limits

The **maximum size** of one data package is **limited to 60 KB**.

The **maximum number of simultaneous connections** for one data stream is **limited to 100**.

Data streams have no daily or burst rate limiters.


# Authorization

The data stream API uses a private token and a public token to provide authorization.

**Private token**

The private token is used by the REST API to send data to the data stream and must be kept secret. Anyone with that token can write to the data stream.

**Public token**

The public token is used in the composition script and can be known by anyone. With the public token, you can listen to the data coming from the data stream.

To locate data stream tokens, or create new ones, navigate to the Dashboard and select Data Stream Manager in the user menu.


# How-to guides

The guides in this section describe most of the common tasks involved in updating overlays using the Singular data stream API.

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/kuuVWfXDO8JzuM0zahm0">How to create a data stream</a></td></tr><tr><td><a href="/pages/kGSGvC9pnZUHGO9jYdJM">How to link a data stream to a composition</a></td></tr><tr><td><a href="/pages/V1QuI4rdpl2Yrc2dZky1">How to send data to a data stream</a></td></tr></tbody></table>

{% hint style="info" %}
These how-to guides require a Singular account, which you can get [here](https://app.singular.live/users/login).
{% endhint %}


# Create a data stream

The first step in sending data to a control app with the data stream API is to create a data stream. Here's how:

1. Open the Singular Dashboard and select **Data Stream Manager** in the user menu.

   <figure><img src="/files/xe7MCpHvzhTXfgWIkAqP" alt=""><figcaption><p>Opening the data stream manager</p></figcaption></figure>
2. Select **New** to create and name a new data stream.

   <figure><img src="/files/KdRsI6IKH6qj51gOBfN6" alt=""><figcaption><p>Creating a new data stream</p></figcaption></figure>
3. Locate the private token and public token, which you'll need to [link a data stream to a composition](/data-stream-api/how-to-guides/link-a-data-stream-to-a-composition) and [send data to a control app using the data stream API](/data-stream-api/how-to-guides/send-data-to-an-app-using-the-data-stream-api).

   <figure><img src="/files/vjhsBXm1zDChDUQ9RIbE" alt=""><figcaption><p>Getting a data stream's tokens</p></figcaption></figure>

### Next steps

Now that you've set up a new data stream, continue to the next how-to-guide to learn how to [link a data stream to a composition](/data-stream-api/how-to-guides/link-a-data-stream-to-a-composition).


# Link a data stream to a composition

{% hint style="info" %}
This how-to guide requires composition scripting to prepare the composition to accept data from the data stream API. For an overview of composition scripting and to learn how to accomplish common tasks with it, visit the [composition scripting](/composition-scripting/introduction) section of this portal.&#x20;
{% endhint %}

1. Open the composition you want to connect to a data stream in Composer.
2. If you haven't already created a control node for the widget you want to update with the data stream API, [create a control node](/singular-basics/building-overlays-in-composer/how-to-set-up-control-nodes-to-make-widget-properties-available-to-a-control-app) for it.
3. Open the [composition script editor](/singular-basics/building-overlays-in-composer/composer-reference#areas-of-the-property-panel) and paste this code snippet into it. You'll need to connect it to the control node in strep 2 and also add the public data stream token.

{% code title="Composition Script" %}

```javascript
context.utils.createDataStream(
  data_stream_public_token, 
  (status, payload) => 
  {
    console.log("we have received: ", status, payload);
    if (status === "message") {
      	// process your data here
    }
  });
```

{% endcode %}

{% hint style="info" %}
For more help, see the composition scripting section of this portal.
{% endhint %}


# Send data to an app using the data stream API

In this how-to guide, you'll learn how to send data to an app using the data stream API.

### Requirements

1. You have [created a data stream](/data-stream-api/how-to-guides/create-a-data-stream)
2. You have [linked the data stream to a composition](/data-stream-api/how-to-guides/link-a-data-stream-to-a-composition)

This guide is language and platform agnostic. Thus, you can follow along using cURL or whatever language you use to make API requests as well as on Postman or the API platform of your choice. To try it out on Postman, visit the [Singular Postman collection](https://documenter.getpostman.com/view/2177855/2s83zcTSu6).

### How to send data to an app using the data stream API

1. Prepare a `PUT` request to send to `https://datastream.singular.live/datastreams/:datastreamPrivateToken`, which is the endpoint for the [Singular data stream API](/data-stream-api/api-reference).&#x20;
2. Replace `:datastreamPrivateToken` with the actual private token. (This can be found on the [data stream manager](https://app.singular.live/datastreammanager), located on the user menu on the Singular Dashboard.)&#x20;
3. Add a request body that matches the format of, and updates, the [payload of the sub-composition](/rest-api/how-to-guides/get-a-compositions-sub-composition-ids-and-names-and-their-payload-structures) that is connected to the data stream in the overlay's composition.
4. With the composition editor in Composer open, send the request and watch the data update.


# API reference

## Update data stream content

<mark style="color:orange;">`PUT`</mark> `https://datastream.singular.live/datastreams/:datastreamPrivateToken`

Send data to a data stream.

#### Path Parameters

| Name                                                     | Type   | Description               |
| -------------------------------------------------------- | ------ | ------------------------- |
| datastreamPrivateToken<mark style="color:red;">\*</mark> | String | Private data stream token |

{% tabs %}
{% tab title="200: OK " %}

{% endtab %}
{% endtabs %}

### Example request body

```json
{
    "number": 123,
    "text": "this is text",
    "isActive": true,
    "color": "#3333ff"
}
```

{% hint style="info" %}
You can send any JSON data in the request body and then define how it is processed in the composition script editor.
{% endhint %}


# Introduction

Composition scripting documentation overview

To help you harness the full power of Singular, the Singular developer portal contains the following sections on composition scripting:

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><a href="/pages/bpQRLtmQy43nfr5HT3Ku"><strong>Overview</strong></a></td><td>Understand key concepts about composition scripting and get a deeper idea of how it works.</td><td><a href="/pages/3RmoGNfWgsrSF7PsDefQ">/pages/3RmoGNfWgsrSF7PsDefQ</a></td><td></td></tr><tr><td><a href="/pages/HSQyv2Q2VKUfyyVbjPUn"><strong>Quick starts</strong></a></td><td>Learn essential composition scripts that perform common yet powerful tasks.</td><td><a href="/pages/BwGznT7Wh7kC04ZVUYnx">/pages/BwGznT7Wh7kC04ZVUYnx</a></td><td></td></tr><tr><td><a href="/pages/9A8YCufvZKLhen8xwBgI"><strong>Cheat sheets</strong></a></td><td>Get essential reference for composition scripts for a variety of use cases from basic to advanced</td><td></td><td></td></tr><tr><td><a href="/pages/ysUED03QehknZQdNfNYE"><strong>Use cases</strong></a></td><td>Learn to read control node content and update widget properties</td><td></td><td></td></tr><tr><td><a href="/pages/VR90qrV5rs12JLSuQ3gp"><strong>Composition script editor</strong> </a></td><td>Learn the layout of the composition script editor, where you'll write and edit composition scripts.</td><td></td><td></td></tr></tbody></table>


# Overview

An overview of composition scripting

Singular overlays are essentially an HTML/CSS layer that sits on top of video, and composition scripting enables you to add JavaScript code to that layer. Thus, composition scripting allows you to make overlays interactive and set them up to update based on logic.

Composer includes a built-in [composition script editor](/composition-scripting/composition-script-editor-reference) where you can write composition scripts and test their functionality directly with a composition's output without having to deploy code to a web environment.

{% hint style="info" %}
Working with composition scripts requires experience in JavaScript and, in some cases, CSS. For recommendations for learning JavaScript, visit the [Support resources page](/support/support-resources).
{% endhint %}

### Common uses for composition scripts

Composition scripts can be written to perform countless functions. These are the most common:

* Interpreting and formatting data, e.g., data time, colors, add unit symbols, etc.
* Triggering animations depending on data, such as playing an animation when a team scores
* Defining lookup-tables and handle tri-codes
* Reading and writing data from/to servers
* Connecting to external data sources
* Creating interactive user experiences
* Defining custom animations and transitions

With a basic understanding of composition scripting and some of the functions they can serve, let's take a closer look into the following areas:

* [Types of composition scripts](#types-of-composition-scripts)
* [Initialization order](#initialization-order)
* [How composition scripts interact](#interaction-between-composition-scripts)
* [Essential functions of composition scripts](#essential-functions-of-every-composition-script)
* [Using the Chrome debugger to work with composition scripts](#using-the-chrome-debugger)

### **Types of composition scripts**

There are four types of composition scripts:

* The [global script](#global-script-1)
* The [root script](#root-script)
* [Sub-composition scripts](#sub-composition-scripts)
* The [overlay script](#overlay-script)

#### Global script

The global script is used to define global variables and library functions that are needed throughout the entire composition. Variables and functions are exposed using the global context of the composition scripts.

#### Root script

The root script contains root-specific functions, such as receiving or fetching data, interpreting them, and deploying the content to relevant sub-compositions.

#### Sub-composition scripts

Sub-composition scripts—there can be one per sub-composition—contain data and functions required within their scope. They are used to access control nodes and widgets within them.

{% hint style="info" %}
The root composition script and sub-compositions scripts are the same except that the root composition doesn't have a parent.
{% endhint %}

#### Overlay script

The overlay script provides the interface to communicate with parent web pages, player embed code, and native iOS and Android apps. It is only required for expert use cases and integration with video players.

#### Interaction between composition scripts

Composition scripts can share data and communicate with each other. For example, you can send data from one sub-composition to another or call functions from one sub-composition to another sub-composition.&#x20;

<figure><img src="https://lh6.googleusercontent.com/n35fnSFGc2VUMySWLRbafUdb1aH6O90lMnokesmlIMimGumnSIz6PdanHOaN2KfZYAKiMgB2zMnoSMLohU5LOkA86-HPTgPA4irsvkQFei4C2eK4sj4WC0JPP-__0t0QUHRdWKw_0W-3r_4wH71cAQ8waipVPcLsO9L-z1oxBJniW7bmLCG6awuyVmEI8g" alt=""><figcaption><p>How composition scripts interact with sub-compositions, widgets and each other</p></figcaption></figure>

### Initialization order

Composition scripts are initialized in the following order:

1. Overlay script
2. Global script
3. Sub-compositions starting from lowest child up to parent
4. Root script

Because of this initialization order, sub-composition scripts have access to the global script, and when the root script is initialized, it has access to all the data in the sub-compositions and can call them. This order ensures that when the root script is initialized, all the parent scripts already exist and it can interact with them.

{% hint style="info" %}
When the sub-compositions scripts are initialized, widgets associated with them are already there. In other words, widgets are loaded and initialized before composition scripts.
{% endhint %}

### Essential functions of every composition script

At minimum, every composition script needs two functions: `init()` and `close()`.

The `init()` function is where you write most of the code. It provides access to a composition object and context.

The composition object contains numerous methods. It can:

* Add listeners
* Access children within that sub-composition
* Find sub-compositions
* Find group widgets
* Access the `dom` element to create various animation effects
* Get payloads
* Set a payloads
* Use jump and play to control the animation

The `close()` function is used to clean up memory, clear timers, and close data streams.

### Working in Composer and the composition script editor simultaneously

While you're working in the composition script editor, another person can work independently in Composer. To bring updates made in Composer into the composition editor, select the [Reload Composition button](https://developer.singular.live/composition-scripting/pages/VR90qrV5rs12JLSuQ3gp#1.-menu-bar) in the menu bar.

As for work done in the composition editor, it won't affect the composition until it is saved.&#x20;

### Global script

The global script is used to define global variables and library functions. Variables and functions are exposed using the “global context” of the composition scripts.

<details>

<summary>Global script boilerplate</summary>

```javascript
(function() {

  // Function must return the init function. close is optional
  return {

    // the init function is called after the script has been evaluated
		// context: gives access to common objects
    init: function(context) {
      console.log("Initialize Global script ");
    },

    // the close function will be called when the script will be unloaded. 
    // use this function to cleanup timeouts, intervals, XHR request and so on
    close: function() {
      console.log("Close Global script");
    }
  };
})();
```

</details>

**`init: function(context)`**

The `init` function is called after the script has been evaluated.

The context gives access to global objects and utilities. Use the global object to manage custom data, lookup tables, and functions with a global scope. Utility functions include tools and libraries provided by Singular R\&D.

```javascript
context
    global: {}
    utils:
        createConditionalSubCompLink: ƒ ()
        createDataStream: ƒ (t,e,i)
        createMoment: ƒ i()
        createNumeral: ƒ (n)
        createTimeControl: ƒ ()
        createTinyColor: ƒ c(e,t)
```

**`close: function()`**

The `close` function is called when the script is to be unloaded. Use this function to clean up timeouts, intervals, and XHR, and fetch requests, etc.

### **Root and** s**ub-composition scripts**

The root script contains root specific functions. For example, the root script receives or fetches data, interprets them, and deploys the content to the relevant su&#x62;**-**&#x63;ompositions.

<details>

<summary>Root and sub-composition boilerplate</summary>

```javascript
(function() {

  // Function must return the init function. close is optional
  return {

    // the init function is called after the script has been evaluated
    // comp: the composition object the script is attached to
		// context: gives access to common objects
    init: function(comp, context) {
      console.log("Initialize Composition script " + comp.name);

      // this listener receives messages from graphics SDK
      // these messages are usually triggered by the "send message to JavaScript" option
      // in the event panel of the composer. 
      // widgets can also send custom messages to the composition script using the  
      // sendCustomMessage of the widget SDK
      comp.addListener('message', (event, msg, e) => {
        console.log("Composition message " + comp.name, event, msg, e);
        e.stopPropagation();
      });
      
      // when the animation state of this comp or a sub comp changes
      comp.addListener('state_changed', (event, msg, e) => {
        console.log("Composition state " + comp.name, event, msg, e);
        e.stopPropagation();
      });

      // when the control nodes of this comp or a sub comp changes
      comp.addListener('payload_changed', (event, msg, e) => {
        console.log("Composition payload " + comp.name, event, msg);
        e.stopPropagation();
      });
      
      // when the payload of a datanode of this comp or a sub comp changes
      comp.addListener('datanode_payload_changed', (event, msg, e) => {
        console.log("Composition datanode payload " + comp.name, event, msg);
        e.stopPropagation();
      });
    },

    // the close function will be called when the script will be unloaded. 
    // use this function to cleanup timeouts, intervals, XHR request and so on
    close: function(comp, context) {
      console.log("Close Composition script " + comp.name);
    }
  };
})();
```

</details>

**`init: function(context)`**

* comp: composition object
* context: context object

**Available listeners**

* `'message'` listener
* `'state_changed'` listener
* `'timeline_event'` listener&#x20;
* `'payload_changed'` listener
* `'datanode_payload_changed'` listener&#x20;

**`'message'` listener**

* Triggered when:
  * The composition uses interactive events
  * Widgets generate custom events
  * Composition script generates custom events

```javascript
      comp.addListener('message', (event, msg, e) => {
        console.log("Composition message " + comp.name, event, msg, e);
        e.stopPropagation();
      });
```

**`'state_changed'` listener**

* Triggered when the animation state of the sub-composition has changed, e.g., when the In or Out state is reached.

```javascript
      comp.addListener('state_changed', (event, msg, e) => {
        console.log("Composition state " + comp.name, event, msg, e);
        e.stopPropagation();
      });
```

**`'timeline_event'` listener**&#x20;

* Triggered on the start and the end of an animation.

```javascript
      comp.addListener('timeline_event', (event, msg, e) => {
        console.log(comp.name + ".timeline_event() - msg =", msg);
        e.stopPropagation();
      });
```

**`'payload_changed'` listener**

* Triggered when the content of any control node of the sub-composition changes.

```javascript
      comp.addListener('payload_changed', (event, msg, e) => {
        console.log("Composition payload " + comp.name, event, msg);
        e.stopPropagation();
      });
```

**`'datanode_payload_changed'` listener**&#x20;

* Triggered when the content of any data node added to the sub-composition changes.

```javascript
      comp.addListener('datanode_payload_changed', (event, msg, e) => {
        console.log("Composition datanode payload " + comp.name, event, msg);
        e.stopPropagation();
      });
```

**`close: function()`**

* comp: composition object
* context: context object

Cleans up memory and clears timers in the close function.

### Overlay script

The overlay script provides the interface to communicate with parent web pages, player embed code, and native iOS and Android apps.

{% hint style="info" %}
The overlay script is general only required in very specific cases and requires advanced composition scripting skills.
{% endhint %}

<details>

<summary>Overlay script boilerplate</summary>

```javascript
(function() {

  // Function must return the init function. close is optional
  return {
		
    // the init function gives you access to the graphics SDK object and the overlay SDK object
    // more information in the resources menu
    init: function(graphics, overlay) {

      console.log("Initialize Overlay script ");

      // this listener receives messages from graphics SDK
      // these messages are usually triggered by the "send message to JavaScript" option
      // in the event panel of the composer. 
      // widgets can also send custom messages to the composition script using the  
      // sendCustomMessage of the widget SDK
      graphics.addListener('message', (event, msg) => {
        console.log("Graphics SDK message", event, msg);
      });
      
      // when the animation state of a sub composition changes
      graphics.addListener('state_changed', (event, msg) => {
        console.log("Graphics SDK state changed", event, msg);
      });

      // when the control nodes of a sub composition changes
      graphics.addListener('payload_changed', (event, msg) => {
        console.log("Graphics SDK payload changed", event, msg);
      });
      
      // when the payload of a datanode in the composition changes
      graphics.addListener('datanode_payload_changed', (event, msg) => {
        console.log("Graphics SDK datanode payload changed", event, msg);
      });

      // errors will be reported here
      graphics.addListener('error', (event, msg) => {
        console.log("Graphics SDK error", event, msg);
      });
      
      // overlay only exists if the composition is instanciated using the Overlay SDK
      // more info in the resources menu
      if (overlay) {
        overlay.addListener((event, msg) => {
          console.log("Overlay SDK message", event, msg);
        });
      }
    },

    // the close function will be called when the script will be unloaded. 
    // use this function to cleanup timeouts, intervals, XHR request and so on
    close: function() {
      console.log("Close Overlay script");
    }
  };
})();

```

</details>

**`init: function(graphics, overlay)`**

* graphics: Graphics SDK object
* overlay: overlay object

**Available listeners**

* ‘`message`’ listener
* ‘`state_changed`’ listener
* ‘`timeline_event`’ listener
* ‘`payload_changed`’ listener
* ‘`datanode_payload_changed`’ listener
* Listener ‘`error`’ listener

**`close: function()`**

* comp: composition object
* context: context object&#x20;

### Using the Chrome debugger

An important tool in singular composition script is the chrome debugger. It gives you access to all your scripts. To open the chrome debugger click F12 on your keyboard or go to the development tools and select the development tools button.

If you don't have any of the files open, enter ctrl b in the chrome debugger and write the name of your sub-composition to open the composition script.

You can add breakpoints and look at the variables.

To trigger that, just save it and have the windows side by side.


# Quick start

Follow along with these quick starts to learn the most common and fundamental composition scripts in real-world situations.

Once you're familiar with these scripts, you'll be able to streamline your overlay workflows. You'll also be able to use our [cheat sheets](/composition-scripting/cheat-sheets) to create powerful composition scripts and get the most out of Singular overlays for your specific use cases.

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/Uzvvi2q4Jov1QOHFy61t"><strong>Find sub-compositions and widgets</strong></a></td></tr><tr><td><a href="/pages/cdyIAp9fT2QPLa1t1nJa"><strong>Read and update control nodes</strong></a></td></tr><tr><td><a href="/pages/UbNX7DStn9hBEdLgBSEs"><strong>Read and update widget properties</strong></a></td></tr><tr><td><a href="/pages/zdLyFiLD3BO8q3trHhgj"><strong>Read control nodes and update widget properties</strong></a></td></tr><tr><td><a href="/pages/cd8AXpqciCcHoLxIKiTI"><strong>Set text widget text property</strong></a></td></tr><tr><td><a href="/pages/PCvyHgqLdSeuMf8qwaxf"><strong>Set image widget URL property</strong></a></td></tr><tr><td><a href="/pages/qAvj58Pvn5XLNIFX5FWA"><strong>Set table widget content property</strong></a></td></tr></tbody></table>


# Find sub-compositions and widgets

This example explains how to get composition's sub-compositions and widgets.&#x20;

### Functions covered

* `find()`&#x20;
* `findWidget()`&#x20;

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=826) to follow along in a real composition.

### Composition structure

The example composition has one sub-composition called **Lower**, as you can see in the composition tree.

Select the Lower sub-composition and **Control Properties** in the property panel to see the sub-composition's objects.

<figure><img src="https://lh3.googleusercontent.com/Lg_kWIXGrlXEvUy1qbOz1guO5uTFWizduI7zZh5xSFtZYkE79am8dOFOHDaGjHv-gt9zt2QBrdn6zeCpbiQx2qVXI1TOItOzv05AplparhYjEtBF-k8UnHRcvGIdGQ1Mfkt3M9PEMYtcrQYTHkjShhazrjQxz0nkRbmM-PCml4LnErcOh83zHw5T7KDT9g" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To go deeper and open the sub-composition's widgets:

* Double-click the Lower sub-composition.&#x20;

This sub-composition contains three widgets.

* lowerTitle: a **text** widget
* lowerSubtitle: a **text** widget
* lowerLogo: an **image** widget

<figure><img src="https://lh5.googleusercontent.com/dzWk1bNlQGk7nVWHMAtmt0ftx4DQQEdvZvq6hWE5vvLnOCkkfg1vtzJne-DDe1nWVCS05rpQlKs52Im7NbDnS1xaJKv19PHaAfeGTlrvkaWzAQfYYhPcMsuRkoNFFkJ3e0g_j-ngcB0KdmMUUUKiYCJjxvR7ycZlEC6_sWgWQwza-3szyh48eaDUFHny3w" alt=""><figcaption><p>The sub-composition's widgets</p></figcaption></figure>

### Composition scripts

To get the same information using composition scripts, [open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458658/edit) as above.&#x20;

Then, read the comments in the root and Lower composition scripts to see the composition scripts needed to find sub-compositions and widgets.&#x20;

You can also copy and paste the composition scripts below into your own compositions.&#x20;

{% code title="Root Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the subcomposition "Lower"
      const compLower = comp.find("Lower")[0];

      // log composition object to the console
      console.log(compLower);

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}

{% code title="Lower Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the title, subtitle, and logo widget
      const wiLowerTitle = comp.findWidget("lowerTitle")[0];
      const wiLowerSubtitle = comp.findWidget("lowerSubtitle")[0];
      const wiLowerLogo = comp.findWidget("lowerLogo")[0];

      // log widget objects to the console
      console.log(wiLowerTitle);
      console.log(wiLowerSubtitle);
      console.log(wiLowerLogo);

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Read and update control nodes

This example explains how to use composition scripting to read and update a composition's control nodes.

### Functions covered

* `comp.find()`
* `comp.getPayload()`
* `comp.getPayload2()`
* `comp.setPayload()`

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=546) to follow along in a real composition.

### **Composition structure**

The example composition has one sub-composition called **Lower**, as you can see in the composition tree.

To see the sub-composition's objects:

* Select the Lower sub-composition and **Control Properties** in the property panel.

<figure><img src="https://lh3.googleusercontent.com/Lg_kWIXGrlXEvUy1qbOz1guO5uTFWizduI7zZh5xSFtZYkE79am8dOFOHDaGjHv-gt9zt2QBrdn6zeCpbiQx2qVXI1TOItOzv05AplparhYjEtBF-k8UnHRcvGIdGQ1Mfkt3M9PEMYtcrQYTHkjShhazrjQxz0nkRbmM-PCml4LnErcOh83zHw5T7KDT9g" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To see this sub-composition's control nodes:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458659/edit) as above and open the **Lower composition script.**
2. In the Composition Control tab, select **Lower** and then the **Sub-comp info** tab.

This composition contains three control nodes:

* Title: a **text** control node
* Subtitle: a **textarea** control node
* Station Logo: an **image** control node

<figure><img src="https://lh5.googleusercontent.com/iaTgtG9Q9UVDJ_YW8RX_DvVJ84yMf_nWSMwLBDJpQgK_s62mKeZ25KdPVwcfaolVZ9xFUSAyMCmhbEqLDwjtdlO_eh3nKlu915DH5rCu0JhDzhHH8D6MDnL94LM5pN1MvUOiDxMSgublKiZY75gxOJ0qxQJenmbDlQduxDFLMHyxiPGXFKeg5Rmq1TKMHg" alt=""><figcaption><p>The sub-composition's control nodes</p></figcaption></figure>

### Composition script

Now that you have located the composition's control nodes, learn how to read and update them by reading the comments in the Lower composition script.&#x20;

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get control node payload as array of key-value pairs
      const payload = comp.getPayload();
      console.log(payload);

      // get control node payload as JSON object
      const payload2 = comp.getPayload2();
      console.log(payload2);

      // set new control node payload
      const newPayload = {
        "Title": "New title",
        "Subtitle": "New subtitle line 1\nNew subtitle line 2",
        "Station Logo": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
      };
      comp.setPayload(newPayload);

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Set text widget text properties

This example explains how to update single-line and multi-line text in a text widget.

### Functions covered

* `comp.findWidget()`
* `widget.getPayload()`
* `widget.setPayload()`

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=550) to follow along in a real composition.

### Composition structure

The example composition has one sub-composition called **Lower**, as you can see in the composition tree.

<figure><img src="https://lh6.googleusercontent.com/tqb-_aq0hgxDSFLSERmw9ZjxyvJWOHG5gXm98kQADOo07QzO3dYNeRfsjpkI62pCG-A2JmseSxAGxngufy39p2PuivPeBDetzC0MpnmK7n03jDVIfhkLcJbh2clFRk5u-KbkEwysF4ZfvNfwqt5p_KIHEA4k10zbx2LpqMttdD3E9Crj18XZNSUObi6U" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To see this sub-composition's widgets:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458662/edit) as above and select the Lower composition script.
2. In the composition navigator, select the **Widget Explorer** tab.

This composition contains three widgets, but for this quick start, you'll look at the text widgets, which in the composition are called **lowerTitle** and **lowerSubtitle**.

<figure><img src="https://lh6.googleusercontent.com/9Hw336O5Sgc1m-OO1j2GgWJ6yywxCEBf_vkS64tLijgH532zgv-hSK4iMC34mItDryjS_kGdKFGA26iVIWkF-ErJoJnxU5dCaIsfyOJcvhtIwEwFK0WJOqrvHbmpP0vP0TPTxtq6gemIg0dFIJMimN6NPzuf31OUKEJZq1pMnvwqryqSi7vJc4blIjZd" alt=""><figcaption><p>The sub-composition's widgets</p></figcaption></figure>

### Composition script

Now that you have located the composition's text widgets, learn how to set them by reading the comments in the Lower composition script.

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the title and subtitle widget
      const wiLowerTitle = comp.findWidget("lowerTitle")[0];
      const wiLowerSubtitle = comp.findWidget("lowerSubtitle")[0];

      // update the single-line title
      wiLowerTitle.setPayload({
        "text": "This is the new title"
      });

      // update the multi-line subtitle
      wiLowerSubtitle.setPayload({
        "text": "New subtitle line 1\nNew subtitle line 2"
      });

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Read and update widget properties

This example explains how to use composition scripting to read and update a composition's widget properties.

### Functions covered

* `comp.findWidget()`
* `widget.getPayload()`
* `widget.setPayload()`

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=547) to follow along in a real composition.

### Composition structure

The example composition has one sub-composition called **Lower**, as you can see in the composition tree.

<figure><img src="https://lh6.googleusercontent.com/tqb-_aq0hgxDSFLSERmw9ZjxyvJWOHG5gXm98kQADOo07QzO3dYNeRfsjpkI62pCG-A2JmseSxAGxngufy39p2PuivPeBDetzC0MpnmK7n03jDVIfhkLcJbh2clFRk5u-KbkEwysF4ZfvNfwqt5p_KIHEA4k10zbx2LpqMttdD3E9Crj18XZNSUObi6U" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To go deeper and open the sub-composition's widgets:

* Double-click the Lower sub-composition.

This sub-composition contains three widgets.

* lowerTitle: a **text** widget
* lowerSubtitle: a **text** widget
* lowerLogo: an **image** widget

<figure><img src="https://lh5.googleusercontent.com/3-ZMOmymcdor5ltLX6kFt45MLVrAGykCXtqKzjRf_6HTlEuAZB27r-XN-iiy5R9H09fDCjrA9R0fYMmdPlwmLIA5xSN1C5fPcOaVXxiKzkIKMoDMNNEk6PLg0kv0R-9Q27Q5Gka0eoEg8SCkOlU2ORgVR-TInzr5sbPNiyD4ff8KLIZa3XhF6e3L9z9l" alt=""><figcaption><p>The sub-composition's widgets</p></figcaption></figure>

### **Composition script**

To get the same information using composition scripts, [open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458660/edit) as above.&#x20;

Then, read the comments in the Lower composition script to see the composition scripts needed to read and update widget properties.

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the title widget
      const wiLowerTitle = comp.findWidget("lowerTitle")[0];

      // get widget properties
      const p = wiLowerTitle.getPayload();
      console.log(p);

      // get title text
      const titleText = p.text;

      // change title to uppercase
      wiLowerTitle.setPayload({
        "text": titleText.toUpperCase()
      });

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Read control nodes and update widget properties

This example explains how to read control nodes, concat strings, and update a widget’s text property.

### Functions covered

* `comp.findWidget()`
* `comp.getPayload()`
* `widget.setPayload()`

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=828) to follow along in a real composition.

### Composition structure

The example composition has one sub-composition called **Lower**, as you can see in the composition tree.

<figure><img src="https://lh6.googleusercontent.com/tqb-_aq0hgxDSFLSERmw9ZjxyvJWOHG5gXm98kQADOo07QzO3dYNeRfsjpkI62pCG-A2JmseSxAGxngufy39p2PuivPeBDetzC0MpnmK7n03jDVIfhkLcJbh2clFRk5u-KbkEwysF4ZfvNfwqt5p_KIHEA4k10zbx2LpqMttdD3E9Crj18XZNSUObi6U" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To see this sub-composition's widgets:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458661/edit) as above select the Lower composition script.
2. In the composition navigator, select the **Widget Explorer** tab.

This composition contains three widgets, but for this quick start, you'll look at its **text** widget, which in the composition is called **lowerFullname**.

<figure><img src="https://lh5.googleusercontent.com/5VTzpNPg4ZRy9I78AehkFz4OcgE7eUKiQF_LXuhMFKG4bluouqT14fcEBeo4sroaWB8b9tIb9jnrOh7WjpJbBTiVcug-fRTNZme2IgsjOEQMMN51WXv_LcNfEzXFL72MIRApGhxi4fg-SkDVZmZMLjvKJjdS_oV-TELxtRmQO40PnLZyD4bxYDVv4KTw" alt=""><figcaption><p>The sub-composition's widgets</p></figcaption></figure>

### Composition script

Now that you have located the composition's widgets, learn how to read their assigned control nodes and update them by reading the comments in the Lower composition script.&#x20;

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the fullname widget
      const wiLowerFullname = comp.findWidget("lowerFullname")[0];

      // get control node payload as JSON object
      const p = comp.getPayload2();
      console.log(p);

      // concat control node content and change the lastname to upper case.
      const fullname = `${p["Firstname"]} ${p["Lastname"].toUpperCase()}`;

      // update fullname text
      wiLowerFullname.setPayload({
        "text": fullname
      });

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Set image widget URL property

This example explains how to set a composition's image widget URL property.

### Functions covered

* `comp.findWidget()`
* `widget.getPayload()`
* `widget.setPayload()`

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=548) to follow along in a real composition.

### Composition structure

The example composition has one sub-composition called **Lower**, as you can see in the composition tree.

<figure><img src="https://lh6.googleusercontent.com/tqb-_aq0hgxDSFLSERmw9ZjxyvJWOHG5gXm98kQADOo07QzO3dYNeRfsjpkI62pCG-A2JmseSxAGxngufy39p2PuivPeBDetzC0MpnmK7n03jDVIfhkLcJbh2clFRk5u-KbkEwysF4ZfvNfwqt5p_KIHEA4k10zbx2LpqMttdD3E9Crj18XZNSUObi6U" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To see this sub-composition's widgets:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458663/edit) as above and select the Lower composition script.
2. In the composition navigator, select the **Widget Explorer** tab.

This composition contains three widgets, but for this quick start, you'll look at its **image** widget, which in the composition is called **lowerLogo**.

<figure><img src="https://lh3.googleusercontent.com/ABdW8sgtON2bTNZA9RZzlHi4jZByWuRuvXGGD1TgsdUaFz4eoLN2sPebtxk-j6Co5d-GlrgLtuTozGoOSu72cKyNRY8NQCvMBV4Ybs2hP-qKC8MHBmkykhrDMWlcRacsyCRzn2iucFJc0PKVAlE0J8tHdD4rSCTgZW0Dh0uAurvihO8kioBLlgpBCsr7" alt=""><figcaption><p>The sub-composition's widgets</p></figcaption></figure>

### Composition script

Now that you have located the composition's widgets, learn how to set the image widget URL property by reading the comments in the Lower composition script.

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the logo widget
      const wiLowerLogo = comp.findWidget("lowerLogo")[0];

      // log widget object to the console
      console.log(wiLowerLogo);

      // update the image URL
      wiLowerLogo.setPayload({
        "image": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
      });

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Set table widget content property

This example explains how to update a composition's table widget’s content.

### Functions covered

* `comp.findWidget()`
* `widget.setPayload()`

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=549) to follow along in a real composition.

### Composition structure

The example composition has one sub-composition called **Standings**, as you can see in the composition tree.

<figure><img src="https://lh4.googleusercontent.com/OIp1hjeLDnV5z9J65tzjfRyaf_AiTO_4p5TW5aNIeDu5HDzUk-fFdpi0GHMl7rAFDjIE5Md7xG4hMLwrPNAxYZUHQpq62CcOHNmSiJodJxfLE4Db1ewiIgH7_UvCnLmMhRoKA1ZzihkLyrl14Sy09Fq0MY52AEznvmaro8V3Jmk4MWqrZD9KYQFBxJXe" alt=""><figcaption><p>The Standings sub-composition</p></figcaption></figure>

To see the sub-composition's payload:

1. Double-click the Standings sub-composition in the composition tree.
2. Select the standingsTable widget and then the table tab in the property panel.

<figure><img src="https://lh5.googleusercontent.com/E6fKewx_5P8xg7sxg6JtO0ntjIcqqXVSHDGCntkccnKbQEn2cHDzC_ocTi6O7xBH3a8UG6mX-Xblid-p6PiMAksfGm1GpQoWcQdDmpXy2AP6iOuRt-gMM2McaF6dbVE8JWWdhw_2Yis5Ixehp07oZApCR59RioQTiz4DQg8xeDlntYJeg_-up-72NnNq" alt=""><figcaption><p>standingsTable's payload</p></figcaption></figure>

To see this sub-composition's widgets:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/458664/edit) as above and select the Lower composition script.
2. In the composition navigator, select the **Widget Explorer** tab.

This composition contains three widgets, but for this quick start, you'll look at its **Table** widget, which is called standingsTable in the composition.

<figure><img src="https://lh3.googleusercontent.com/GjIYkhJziq_8YuqxgW9uKzDNOe9CIPFShfpZXv2CUY85JjFsZmKOcIVPxvdntkbDCRJT2dwqChAedhQ5lXKkrqUrS-pzJJx-y9c6vKnuPwrvwo2a7gg9bjayQnHH2RraJMsqII0ZZ40iQQKW-1HTdnh6GFddfgx_M0hdbn7HPUNwfqg6a-f07kEzXrZr" alt=""><figcaption><p>The sub-composition's widgets</p></figcaption></figure>

### Composition script

Now that you've located the composition's table widget, learn how to set its content by reading the comments in the Standings composition script.

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Standings Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {

      // get reference to the standingsTable widget
      const wiStandingsTable = comp.findWidget("standingsTable")[0];

      // log widget objects to the console
      console.log(wiStandingsTable);

      // define standings data
      const tableData = [{
          "Position": 1,
          "Name": "The Winner",
          "Points": 987
        },
        {
          "Position": 2,
          "Name": "Silver medal",
          "Points": 876
        },
        {
          "Position": 3,
          "Name": "Bronce medal",
          "Points": 765
        },
        {
          "Position": 4,
          "Name": "4th place",
          "Points": 654
        },
        {
          "Position": 5,
          "Name": "5th place",
          "Points": 543
        }
      ];

      // build table content format
      const tableContent = {
        "content": tableData
      };
      
      // update tableContent widget property
      // we stringify the tableContent!
      wiStandingsTable.setPayload({
        "tableContent": JSON.stringify(tableContent)
      });

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}


# Cheat sheets

In this section of the composition scripting documentation, you'll find cheat sheets for:

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><a href="/pages/ER8mHXgveMi1cwrEt6q3"><strong>Fundamentals</strong></a></td><td>Basic concepts and fundamental composition scripts</td></tr><tr><td><a href="/pages/vlBPuog3BkdPyvqmRJdj"><strong>Best practices</strong></a></td><td>Best practices in common scripting tasks</td></tr><tr><td><a href="/pages/sEiEdJN94EM6jOQ96gW1"><strong>Interactive</strong></a></td><td>Interactive overlays and user interaction</td></tr><tr><td><a href="/pages/hHoA1OeVhFqZ3ik8SPKe"><strong>Adaptive overlays</strong></a></td><td>Device agnostic overlays</td></tr><tr><td><a href="/pages/QmxcYjOK1cTDAqtLjoVR"><strong>Overlay SDK</strong></a></td><td>Integration with web pages, video players, and native apps</td></tr></tbody></table>


# Fundamentals

This section of the composition scripting documentation includes essential cheat sheets for the following:

* [Composition scripting fundamentals](#fundamentals)
* The [global script](#the-global-script)
* [Sub-composition scripts](#sub-composition-scripts)
* [Object types, properties, and methods](#object-types-properties-and-methods)
* The [composition object](#the-composition-object)
* [The widget object](#the-widget-object)
* [Context and utility functions](#context-and-utility-functions)
* [Event listeners](#event-listener)

### Composition scripting fundamentals

The initialization of composition scripts works from the bottom up and  starts from the lowest child up to the root.

All composition scripts have an `init()` and a `close()` function. Initialize your custom code and variables in the `init()` function and use the `close()` function to clean up memory and clear timeouts and intervals.

### The global script

{% code title="Global Script" %}

```javascript
(function() {

  return {

    init: function(context) {
      // ...
    },

    close: function() {
      // ...
    }
  };
})();
```

{% endcode %}

The `init` function is called after the script is evaluated. The context provides access to common objects, including global storage and utility functions.

The `close` function is called when the script is unloaded, for example when a browser tab is closed. Use it to clean up memory and clear timeouts, intervals, XHR requests, etc.

### Sub-composition scripts

{% code title="Root & sub-composition Script" %}

```javascript
(function() {

  return {

    init: function(comp, context) {
      // ...
    },

    close: function(comp, context) {
      // ...
    }
  };
})();
```

{% endcode %}

The `init` function is called after the script is evaluated. The `comp` argument contains the composition object the script is attached to. The context provides access to common objects, including custom global objects and utility functions.

The `close` function is called when the script is unloaded, e.g. when closing the browser tab. Use it to clean up memory and clear timeouts, intervals, XHR requests, etc.

### **Object types, properties, and methods**

### **The composition object**

The composition object provides methods to navigate through the composition structure, read and update content, transformation and effects parameters, access the DOM element, and trigger animations.

The most frequently used methods are:

{% code title="Composition object properties and methods" %}

```log
> find: ƒ ()
> findGroup: ƒ ()
> findWidget: ƒ ()
> getPayload: ƒ ()
> getPayload2: ƒ ()
> getState: ƒ ()
> getSubcompositionById: ƒ (t)
> id: "1234-5678-abcd-efgh"
> jumpTo: ƒ (i)
> listSubcompositions: ƒ ()
> name: "subCompName"
> parent: ƒ ()
> playTo: ƒ (i)
> setPayload: ƒ (i)
```

{% endcode %}

#### **`find()`**

Returns an array of composition objects that matches the defined search string.&#x20;

```javascript
// We use the first composition with the matching name
const compClock = comp.find("Clock")[0];
```

#### findGroup()

Returns an array of group objects that matches the defined search string.

```javascript
// We use the first group with the matching name
const groupLowers = comp.findGroup("Lowers Group")[0];
```

#### findWidget()

Returns an array of widget objects that matches the defined search string.

<pre class="language-javascript"><code class="lang-javascript"><strong>// We use the first widget with the matching name
</strong>const wiTitle = comp.findWidget("Title")[0];

// Find the first matching widget in a group
const wiTeam1Name = comp.findWidget("Team1 Group", "teamName")[0];
const wiTeam2Name = comp.findWidget("Team2 Group", "teamName")[0];
</code></pre>

#### **`getPayload()`**

Returns the control node content from the composition as an array of key value pairs.

```javascript
const payload = comp.getPayload();
console.log(payload);
```

{% code title="Debug console" %}

```log
> [
>   {
>     "key": "Title",
>     "value": "The Title"
>   }
> ]
```

{% endcode %}

#### `getPayload2()`

Returns the control node content from the composition as a JSON object.

```javascript
const payload = comp.getPayload2();
console.log(payload);
```

{% code title="Debug console" %}

```log
> {
>   "Title": "The Title"
> }
```

{% endcode %}

#### `getState()`

Returns the animation state of the composition.&#x20;

<table><thead><tr><th width="180" align="center">Animation state</th><th width="361">Description</th></tr></thead><tbody><tr><td align="center"><code>In</code></td><td>Timeline at In state</td></tr><tr><td align="center"><code>Out</code></td><td>Timeline at Out state</td></tr><tr><td align="center"><code>Out1</code></td><td>Timeline 1 at Out state</td></tr><tr><td align="center"><code>Out2</code></td><td>Timeline 2 at Out state</td></tr></tbody></table>

```javascript
const animState = comp.getState();
console.log(animState );
```

{% code title="Debug console" %}

```log
> In
```

{% endcode %}

#### `getSubcompositionById()`

Returns the composition object defined by its ID.

```javascript
const compClock = comp.getSubcompositionById("ceedfea8-c060-6d2e-2199-c5f794dcbd16");
```

#### `id`

The composition ID.

```javascript
console.log(compClock.id);
```

{% code title="Debug console" %}

```log
> ceedfea8-c060-6d2e-2199-c5f794dcbd16
```

{% endcode %}

#### `jumpTo()`

Jumps to the specified animation state.

```javascript
comp.jumpTo("In");
```

#### `listSubcompositions()`

Returns an array of JSON objects containing sub-composition names and IDs.

```javascript
const subcomps = comp.listSubcompositions();
```

{% code title="Debug console" %}

```log
> [
>   {
>     "id": "-NHTgDrKFKdlaexZeerp",
>     "name": "Clock"
>   }, {
>     ...
>   }
> ]
```

{% endcode %}

#### `name`

The composition name.

```javascript
console.log(compClock.name);
```

{% code title="Debug console" %}

```log
> Clock
```

{% endcode %}

#### `parent()`

Returns the parent composition object.

```javascript
const compParent = comp.parent();
```

#### `playTo()`

Plays the animation to the specified state.

```javascript
comp.playTo("In");
```

#### `setPayload()`

Sets the control node content of the composition.

```javascript
const payload = {"Title": "The Title"};
comp.setPayload(payload);
```

### The widget object

The widget object provides methods to read and update widget specific properties, transformation and effects parameters, and access the `Dom` element.&#x20;

The most frequently used methods are:

{% code title="Widget Object Properties and Methods" %}

```log
> getDomElement: ƒ (t)
> getPayload: ƒ ()
> getPositionX: ƒ (o)
> getPositionY: ƒ (o)
> getRotateZ: ƒ (o)
> getSizeX: ƒ (o)
> getSizeY: ƒ (o)
> getVisibility: ƒ (o)
> id: "262ec4f4-3a1d-43ee-90c5-20116e8b2e49"
> setPayload: ƒ (o)
> setPositionX: ƒ (o)
> setPositionY: ƒ (o)
> setRotateZ: ƒ (o)
> setSizeX: ƒ (o)
> setSizeY: ƒ (o)
> setVisibility: ƒ (o)
```

{% endcode %}

#### `getDomElement()`

Returns the HTML `Dom` element for the widget.

{% code overflow="wrap" %}

```javascript
const wiDom = wiText.getDomElement();
console.log(wiDom);
```

{% endcode %}

{% code title="Debug console" %}

```log
> <div data-singular-type="widget" data-singular-name="Text" id="onair262ec4f4-3a1d-43ee-90c5-20116e8b2e49" 
> style="position: absolute; left: 25%; top: 42.5%; height: 15%; width: 50%; visibility: inherit; z-index: -1; pointer-events: none; opacity: 1;">
> ...
> subComp</div></div></div>
```

{% endcode %}

#### `getPayload()`

Returns the widget type specific properties as a JSON object.

```javascript
const propsText= wiTitle.getPayload();
console.log(props.Text);
```

{% code title="Debug console" %}

```log
> {
>   "color": {...},
>   "font": {...},
>   "indent": 0,
>   "letterSpacing": 0,
>   "lineHeight": "normal",
>   "lineHeightCustom": 115,
>   "maximumOfLines": 1,
>   "minimumOfLines": 1,
>   "overflow": "adjustLetterWidth",
>   "paddingActive": false,
>   "paddingBottom": 0,
>   "paddingLeft": 0,
>   "paddingRight": 0,
>   "paddingTop": 0,
>   "shadowActive": false,
>   "shadowBlur": 2,
>   "shadowColor": {...},
>   "shadowDirection": 45,
>   "shadowDistance": 3,
>   "text": "The Title",
>   "transform": "none",
>   "verticalAdjustment": 0,
>   "verticalAlignment": "baseline",
>   "wordSpacing": 0
> }
```

{% endcode %}

#### `getPositionX()`, `getPositionY()`

Returns the `positionX` or `positionY` value in a percentage.

```javascript
const posX = wiText.getPositionX();
const posY = wiText.getPositionY();
console.log("posX = %d, posY = %d", posX, posY);
```

{% code title="Debug console" %}

```log
> posX = 50, posY = 50
```

{% endcode %}

#### `getRotationZ()`

Returns the `rotationZ` value in degrees.

```javascript
const rotZ = wiText.getRotationZ();
console.log("rotZ = %d", rotZ);
```

{% code title="Debug console" %}

```log
> rotZ = 90
```

{% endcode %}

#### `getSizeX()`, `getSizeY()`

Returns the sizeX or sizeY value in a percentage.

```javascript
const sizeX = wiText.getSizeX();
const sizeY = wiText.getSizeY();
console.log("sizeX = %d, sizeY = %d", sizeX, sizeY );
```

{% code title="Debug console" %}

```log
> sizeX = 25, sizeY = 80
```

{% endcode %}

#### `getVisibility()`

Returns the visibility as a boolean.

```javascript
const isVisible = wiText.getVisibilty();
console.log("isVisible =", isVisible );
```

{% code title="Debug console" %}

```log
> isVisible = true
```

{% endcode %}

#### `id`

Returns the widget ID.

```javascript
console.log(wiText.id);
```

{% code title="Debug console" %}

```log
"262ec4f4-3a1d-43ee-90c5-20116e8b2e49"
```

{% endcode %}

#### `setPayload()`

Sets one or multiple widget specific properties.

```javascript
wiText.setPayload({"text": "The new title"});
```

#### `setPositionX()`, `setPositionY()`

Sets the `positionX` or `positionY` value in a percentage.

```javascript
wiText.setPositionX(75);
wiText.setPositionY(50);
```

#### `setRotateZ()`

Sets the `rotationZ` value in degrees.

```javascript
wiText.setRotationZ(180);
```

#### `setSizeX()`, `setSizeY()`

Sets the `sizeX` or `sizeY` value in a percentage.

```javascript
wiText.setSizeX(50);
wiText.setSizeY(25);
```

#### `setVisibility()`

Sets the visibility as a boolean.

```javascript
wiText.setVisibility(false);
```

### **Context and utility functions**

```javascript
{
  "global": {},
  "utils": {
    createDataStream: ƒ (t,e,i)
    createMoment: ƒ i()
    createTinyColor: ƒ c(e,t)
    getSingularWindow: ƒ ()
  }
}
```

#### `global:{}`

A custom global object including variables, objects, and functions.

#### `utils.createDataStream()`

Creates a data stream listener.&#x20;

{% hint style="info" %}
Create a Data Stream in the [Data Stream Manager](https://support.singular.live/hc/en-us/articles/360056901272-Data-Stream-Manager)!
{% endhint %}

{% code title=" Create and close a Data Stream" %}

```javascript
(function() {

  //  https://support.singular.live/hc/en-us/articles/360056901272-Data-Stream-Manager
  const data_stream_public_token = "your-data-stream-public-token";
  
  // we define the datas tream variable in the global scope
  let datastream = undefined;

  return {

    init: function(comp, context) {

      // we create the data stream object using the public token
      datastream = context.utils.createDataStream(data_stream_public_token,
        (status, payload) => {
          switch (status) {
            case "message":
              console.log("we have received data:", status, payload);
              break;
            case "connecting":
            case "connect":
            case "open":
            case "close":
            case "disconnect":
              console.log("status:", status);
              break;
            case "error":
              console.error("error:", status);
              break;
          }
        });

    },

    close: function(comp, context) {
      // we close the data stream connection
      if (datastream != undefined) {
        datastream.close();
      }
    }
  };
})();

```

{% endcode %}

#### `utils.createMoment()`

Creates a momentjs object.

{% hint style="info" %}
For a detailed description of the momentjs library visit <https://momentjs.com/>.
{% endhint %}

{% code title="Create a momentjs object." %}

```javascript
const myMoment = context.utils.createMoment(1694178435003);
console.log("myMoment =", myMoment.format("HH:MM:SS"));
```

{% endcode %}

{% code title="Debug console" %}

```log
> myMoment = 20:09:00
```

{% endcode %}

#### `utils.createTinyColor()`

Creates a tinycolor object.

{% hint style="info" %}
For a detailed description of the tinycolor library visit <https://github.com/bgrins/TinyColor>
{% endhint %}

<pre class="language-javascript" data-title="Create a tinyColor object"><code class="lang-javascript"><strong>const myColor = context.utils.createTinyColor("rgb (255, 0, 0)");
</strong>const hsl = myColor.toHsl();
const hslString = myColor.toHslString();

console.log("hsl =", JSON.stringify(hsl));
console.log("hslString =", hslString);

// calculate text color depending on the background color
const bgColor = "crimson";
const textColor = context.utils.createTinyColor(bgColor).isDark() ? "#FFFFFF" : "000000";
console.log("textColor =", textColor);
</code></pre>

{% code title="Debug console" %}

```log
hsl = {"h":0,"s":1,"l":0.5,"a":1}
hslString = hsl(0, 100%, 50%)
textColor = #FFFFFF
```

{% endcode %}

#### `utils.getSingularWindow()`

Returns the render window name.

```javascript
const singularWindow = context.utils.getSingularWindow();
console.log("Singular window name:", singularWindow);
```

{% code title="Debug console" %}

```log
> Singular window name: app_output or app_control or script_editor
```

{% endcode %}

### Event listener

#### `comp.addListener`(eventType, callbackFunction)

The `addListener()` method attaches an event handler to the composition without overwriting existing event handlers. You can add multiple event handlers for the same event type.

The first parameter defines the type of the event. Event types include:

* [`payload_changed`](/composition-scripting/cheat-sheets/fundamentals#payload_changed)
* [`state_changed`](/composition-scripting/cheat-sheets/fundamentals#state_changed)&#x20;
* [`timeline_event`](/composition-scripting/cheat-sheets/fundamentals#timeline_event)
* [`button_clicked`](#button_clicked)
* [`datanode_payload_changed`](/composition-scripting/cheat-sheets/fundamentals#datanode_payload_changed)
* [`message`](/composition-scripting/cheat-sheets/fundamentals#message)

The second parameter is a function that you call when an event occurs.

#### `payload_changed`

This event occurs when the control nodes of the composition or a sub-composition changes.

The callback function receives three parameters:

* **event**: the type of the event
* **msg**: a JSON object containing the composition ID, name, and payload
* **e**: an event handle<br>

<pre class="language-javascript"><code class="lang-javascript">comp.addListener('payload_changed', (event, msg, e) => {
  if (msg.compositionId === comp.id) {
    console.log("listen to:", event);
<strong>    console.log("msg:", msg);
</strong><strong>  }
</strong>  e.stopPropagation();
});
</code></pre>

{% code title="Debug console" %}

```log
> listen to: payload_changed
> msg: {
>   "compId": "...",
>   "compName": "subComp",
>   "compositionId": "...",
>   "compositionName": "subComp",
>   "payload": {
>     "Title": "The Title."
>   }
> }
```

{% endcode %}

#### `state_changed`

This event occurs at the end of an animation when the animation state of the composition or a sub-composition changes.

The callback function receives three parameters:

* **event**: the type of the event
* **msg**: a JSON object containing the composition ID, name, and new animation state
* **e**: an event

<pre class="language-javascript"><code class="lang-javascript">comp.addListener('state_changed', (event, msg, e) => {
  if (msg.compositionId === comp.id) {
    console.log("listen to:", event);
<strong>    console.log("msg:", msg);
</strong><strong>  }
</strong>  e.stopPropagation();
});
</code></pre>

{% code title="Debug console" %}

```log
> listen to: state_changed
> msg: {
>   "compositionId": "...",
>   "compositionName": "subComp",
>   "state": "In", "Out", "Out1", "Out2"
> }
```

{% endcode %}

#### `timeline_event`

This event occurs at the **start and end of an animation** of the composition or a sub-composition.

The callback function receives three parameters:

* **event**: the type of the event
* **msg**: a JSON object containing the composition ID, name, and timeline event details
* **e**: an event

```javascript
comp.addListener('timeline_event', (event, msg, e) => {
  if (msg.compositionId === comp.id) {
    const message = msg.message;
    console.log("listen to:", event);
    console.log("timeline event:", message.event);
    console.log("msg:", msg);
  }
  e.stopPropagation();
});
```

{% code title="Debug console" %}

```log
> listen to: timeline_event
> timeline event: start
> msg: {
>   "compositionId": "...",
>   "compositionName": "subComp",
>   "message": {
>     "event": "start",
>     "direction": "forward",
>     "timeline": "In",
>     "currentTime": 0,
>     "duration": 0.8,
>     "targetState": "In"
>   }
> }

> listen to: timeline_event
> timeline event: stop
> msg: {
>   "compositionId": "...",
>   "compositionName": "subComp",
>   "message": {
>     "event": "stop",
>     "duration": 0.8,
>     "currentTime": 0.8,
>     "direction": "forward",
>     "timeline": "In",
>     "targetState": "In"
>   }
> }
```

{% endcode %}

#### `button_clicked`

This event occurs when a button control node is clicked.

The callback function receives three parameters:

* **event**: the type of the event
* **msg**: a JSON object containing the composition ID, name, and data node details
* **e**: an event

```javascript
comp.addListener('button_clicked', (event, msg, e) => {
  if (msg.compositionId === comp.id) {
    console.log("listen to:", event);
    console.log("msg:", msg);
  }
  e.stopPropagation();
});
```

{% code title="Debug Console" %}

```
> listen to: button_clicked
> msg: {
>   "compositionId": "...",
>   "compositionName": "subComp",
>   "buttonId": "Button 1"
> }
```

{% endcode %}

#### `datanode_payload_changed`

This event occurs when a data node of the composition or a sub-composition changes.

The callback function receives three parameters:

* **event**: the type of the event
* **msg**: a JSON object containing the composition ID, name, and data node details
* **e**: an event

```javascript
comp.addListener('datanode_payload_changed', (event, msg, e) => {
  console.log("listen to:", event);
  console.log("msg:", msg);
  e.stopPropagation();
});
```

{% code title="Debug Console" %}

```log
> listen to: datanode_payload_changed
> msg: {
>   "compId": "...",
>   "compName": "subComp",
>   "compositionId": "...",
>   "compositionName": "subComp",
>   "payload": {
>     ...
>   }
> }
```

{% endcode %}

#### `message`

This event occurs when the graphics SDK, widgets, and interactive events send custom messages to the comp or a sub comp.

The callback function receives three parameters:

* **event**: the type of the event
* **msg**: a JSON object containing the composition ID, name, details and payload
* **e**: an event

```javascript
comp.addListener('message', (event, msg, e) => {
  if (msg.params && msg.params.compId === comp.id) {
    console.log("listen to:", event);
    console.log("msg:", msg);
  }
  e.stopPropagation();
});
```

The following logs show the `msg` object returned by interactive events.

{% code title="Debug console" %}

```log
> listen to: message
> msg: {
>  "params": {
>    "type": "widget", or "group"
>    "name": "bgButton",
>    "id": "e9a3f5e5-7c19-6f83-8a0e-ecc43dca3997",
>    "compId": "-NHgm9qQim1PjR0Ecnxa",
>    "event": {
>      "button": 0,
>      "shiftKey": false,
>      "altKey": false,
>      "ctrlKey": false,
>      "metaKey": false,
>      "screenX": 848,
>      "screenY": 370,
>      "clientX": 344,
>      "clientY": 269,
>      "offsetX": 334,
>      "offsetY": 2,
>      "layerX": 307,
>      "layerY": 109,
>      "movementX": 0,
>      "movementY": 1,
>      "pageX": 344,
>      "pageY": 269,
>      "x": 344,
>      "y": 269
>    },
>    "info": ""
>  },
>  "type": "mouseenter" or "mousedown" or "mouseleave" or "mouseenter"
> }
```

{% endcode %}


# Interactive overlays

Outline your project and contact us on <https://support.singular.live/hc/en-us/requests/new>


# Adaptive overlays

Outline your project and contact us on <https://support.singular.live/hc/en-us/requests/new>


# Overlay SDK

Coming soon ...


# Best practices

Use these cheat sheets to follow best practices in common scripting tasks.

### Communication between sub-compositions

An efficient option for exchanging data between sub-compositions is by adding functions to the composition object.

#### Receiving data in a sub-composition

Extend a composition object by adding the function comp.updateContent()&#x20;

{% code title="Lower subcomposition Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {
      console.log("Initialize Composition script " + comp.name);

      // extend the composition object
      // receive data in the updateContent function
      comp.updateContent = function(data) {
        console.log("updateContent() - data =", data);
      }

    },

    close: function(comp, context) {
      console.log("Close Composition script " + comp.name);
    }
  };
})();
```

{% endcode %}

#### Sending data to a sub-composition

Get reference to the receiving composition and send data by calling its updateContent() function.

{% code title="Root Script" %}

```javascript
((function() {

  return {

    init: function(comp, context) {
      console.log("Initialize Composition script " + comp.name);

      // get composition reference
      const compLower = comp.find("Lower")[0];

      comp.addListener('payload_changed', (event, msg, e) => {
        console.log("Composition payload " + comp.name, event, msg);
        const payload = comp.getPayload2();

        // send data to destination
        compLower.updateContent(payload);
        e.stopPropagation();
      });

    },

    close: function(comp, context) {
      console.log("Close Composition script " + comp.name);
    }
  };
})();
```

{% endcode %}

### Updating overlay content from the composition script

A typical use case is receiving data in sub-composition control nodes, interpreting them, and updating properties within the sub-composition.

In such a case, it is recommended to unlink the receiving control nodes from properties and directly update widget properties.

#### **Reading control nodes, generating HTML, and updating the widget’s text property**

{% code title="Lower subcomposition Script" %}

```javascript
(function() {
  return {

    init: function(comp, context) {
      console.log("Initialize Composition script " + comp.name);

      const wiFullname = comp.findWidget("fullname")[0];

      comp.addListener('payload_changed', (event, msg, e) => {
        console.log("Composition payload " + comp.name, event, msg);
        
        // read control nodes content
        const p = comp.getPayload2();
        const htmlText = `<HTML>${p.firstname} <b>${p.lastname}</b></HTML>`;

        // update text widget
        wiFullname.setPayload({
          "text": htmlText
        });
        
        e.stopPropagation();
      });

    },

    close: function(comp, context) {
      console.log("Close Composition script " + comp.name);
    }
  };
})();
```

{% endcode %}


# Use cases


# Read control nodes and generate HTML text

This example explains how to read control nodes and use the text widget’s HTML feature to style text.

<figure><img src="/files/OOMLKAeAWms1OS2tLPoI" alt=""><figcaption><p>Screenshot</p></figcaption></figure>

### Widget used

* <img src="/files/nqik8Br3wgBj9hmqaRiW" alt="" data-size="line">[Text widget](https://support.singular.live/hc/en-us/articles/360025486472-Text-Widget)

### Functions covered

* [comp.findWidget()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#findwidget)
* [comp.getPayload2()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#getpayload2)
* [widget.setPayload()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#setpayload-1)

Load this [example composition](https://app.singular.live/compositions/458984/edit) to follow along in a real composition.

### Composition structure

The composition has one sub-composition called **Lower**, as you can see in the composition tree.

<figure><img src="https://lh4.googleusercontent.com/JJBvJAYO2GdqSmZRmEkjRegdhqZg03HWURdSA5uiAMI_VLmyWfOH6uRDrQXqayfxmcmybzWq37KG4OtfIELUWGQd9vrR6FpiFNS-KcDjpdRbhB3Yiz-OFI265KXkAoZ5-TwxaeJXrioE8hHnl8PgK4MAYfji3xVmxI_0e4ofI2uvHGmeU3UE-Dulh_WS" alt=""><figcaption><p>The Lower sub-composition</p></figcaption></figure>

To see this sub-composition's widgets:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/dashboard?templateLibraryItem=829) as above and select the **Lower composition script**.
2. In the composition navigator, select the **Widget Explorer** tab.

This composition contains three widgets, but for this quick start, you'll look at the text widget, which in the composition is called **lowerFullname**.

<figure><img src="https://lh3.googleusercontent.com/UqFw5vz5z3X5QtmSwsULcJxPT61eI6TZbGhBreJ7XgMuyKM5c4C9N7Bw1nkR7WJTQKc7ins-CGwlIGD1sKwlpj9_lwD5RzqXAF8vJI2mZTAXwp_L0x5vILV3CqqR18aiBQpaWp6hs2mB_rNRcnt2nYtegLvbLJsZ_nW40OFjjmEeyg4s0JiwnBTXvMDk" alt=""><figcaption></figcaption></figure>

### Composition script

Now that you have located the composition's text widget, learn how to use the text widget’s HTML feature to style text and set a background color in the Lower composition script.

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower script" %}

```javascript
(function() {

  const HTML_TEMPLATE = "<html>{{firstname}} <b>{{lastname}}</b></html>";

  return {

    init: function(comp, context) {

      // get reference to the fullname widget
      const wiLowerFullname = comp.findWidget("lowerFullname")[0];

      /**********************************************************************/

      // we define a function to update the composition
      function updateComposition() {
        // get control node payload as JSON object
        const p = comp.getPayload2();
        console.log(p);

        // build HTML text.
        let htmlFullname = HTML_TEMPLATE.replace("{{firstname}}", p["Firstname"]);
        htmlFullname = htmlFullname.replace("{{lastname}}", p["Lastname"]);

        // update fullname text
        wiLowerFullname.setPayload({
          "text": htmlFullname
        });

      }

      /**********************************************************************/

      // we listen to payload_changed events
      comp.addListener('payload_changed', (event, msg, e) => {
        updateComposition();
        e.stopPropagation();
      });

      /**********************************************************************/

      // update the composition when loading the output URL
      updateComposition();

    },

    close: function(comp, context) {}
  };
})();

```

{% endcode %}

### Singular control app

{% embed url="<https://app.singular.live/control/0gbwClbSWGNG5nfErPQZ52>" %}
Read control nodes, generate HTML styled text app
{% endembed %}


# Read control nodes, generate HTML text with background

This example explains how to read control nodes and use the text widget’s HTML feature to style text with auto-sizing background.

<figure><img src="/files/AKMNkwWspbJh3ZTWFALY" alt=""><figcaption><p>Screenshot</p></figcaption></figure>

### Widget used

* <img src="/files/nqik8Br3wgBj9hmqaRiW" alt="" data-size="line">[Text widget](https://support.singular.live/hc/en-us/articles/360025486472-Text-Widget)

### Functions covered

* [comp.findWidget()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#findwidget)
* [comp.getPayload2()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#getpayload2)
* [widget.setPayload()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#setpayload-1)

Load this [example composition](https://app.singular.live/dashboard?templateLibraryItem=830) to follow along in a real composition

### Composition structure

The composition has one sub-composition called **Lower**, as you can see in the composition tree.

<figure><img src="/files/rsKSAq49D506L8GUWHN2" alt=""><figcaption><p>The Lower sub-composition with control nodes</p></figcaption></figure>

To see this sub-composition's widgets:

1. [Open the composition script editor](/composition-scripting/composition-script-editor-reference#accessing-the-composition-script-editor) within the same [example composition](https://app.singular.live/compositions/459184/edit) as above and select the **Lower composition script**.
2. In the composition navigator, select the **Widget Explorer** tab.

This composition contains three widgets, but for this quick start, you'll look at the text widget, which in the composition is called **lowerText**.

<figure><img src="/files/s4nI3TqDTLYU7Pr6EDUq" alt=""><figcaption><p>Lower sub-composition payload JSON</p></figcaption></figure>

### Composition script

Now that you have located the composition's text widget, learn how to use the text widget’s HTML feature to style text and set a background color in the Lower composition script.

You can also copy and paste the composition script below into your own compositions.&#x20;

{% code title="Lower script" %}

```javascript
(function() {

  const HTML_TEMPLATE = '<html><span style="background:{{background-color}}; padding: 0px {{padding-right}}px 0px {{padding-left}}px">{{firstname}} <b>{{secondname}}</b></span></html>';

  // convert color JSON to CSS rgba()
  const parseColor = function(color) {
    const colorRgba = `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;
    // console.log("colorRgba =", colorRgba);
    return colorRgba;
  }

  return {

    init: function(comp, context) {

      // get reference to the fullname widget
      const wiLowerText = comp.findWidget("lowerText")[0];

      /**********************************************************************/

      // we define a function to update the composition
      function updateComposition() {
        // get control node payload as JSON object
        const p = comp.getPayload2();
        console.log(p);

        const colorRgba = parseColor(p["Background Color"]);

        // build HTML text.
        let htmlText = HTML_TEMPLATE.replace(/{{background-color}}/gi, colorRgba);
        htmlText = htmlText.replace(/{{padding-right}}/gi, p["Padding Right"]);
        htmlText = htmlText.replace(/{{padding-left}}/gi, p["Padding Left"]);
        htmlText = htmlText.replace(/{{firstname}}/gi, p["Firstname"]);
        htmlText = htmlText.replace(/{{secondname}}/gi, p["Lastname"]);

        // update fullname text
        wiLowerText.setPayload({
          "text": htmlText
        });

      }

      /**********************************************************************/

      // we listen to payload_changed events
      comp.addListener('payload_changed', (event, msg, e) => {
        updateComposition();
        e.stopPropagation();
      });

      /**********************************************************************/

      // update the composition when loading the output URL
      updateComposition();

    },

    close: function(comp, context) {}
  };
})();
```

{% endcode %}

### Singular control app

{% embed url="<https://app.singular.live/control/0abwCjnV0aen75c87cuKDD>" %}
Read control nodes, generate HTML text with background app
{% endembed %}


# Text Ticker - Start ticker on "In" animation

This example explains how to start the Text Ticker widget crawl on the subcomposition's "In" animation.

<figure><img src="/files/yrywgZiILWtr8uNmOa06" alt=""><figcaption><p>Screenshot</p></figcaption></figure>

## Widgets featured&#x20;

* <img src="/files/HJTS7Rpcu78hD3gL09hm" alt="" data-size="line"> [Text Ticker](https://support.singular.live/hc/en-us/articles/360025487532-Text-Ticker-Widget)

## Functions covered

* [comp.findWidget()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#findwidget)
* [comp.getPayload2()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#getpayload2)
* [widget.getPayload()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#getpayload-1)
* [widget.setPayload()](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#setpayload-1)

## Composition Structure

1. Add a subcomposition called **Text Ticker** into the **Root** composition.

   <figure><img src="/files/QqarmRGCPFGqI9eh40fy" alt=""><figcaption><p>Root composition structure</p></figcaption></figure>
2. Add a Text Ticker widget, a rectangle as background, and create a control node.

   <figure><img src="/files/ORJPwnTPi8rppbCUB0Rz" alt=""><figcaption><p>Text Ticker subcomposition structure</p></figcaption></figure>
3. Open the composition script editor, select the Text Ticker script, and copy & paste the script below.&#x20;

   <figure><img src="/files/V4rMEyTTH6oBqiImx6Af" alt=""><figcaption><p>Text Ticker composition script</p></figcaption></figure>
4. Save the composition scripts and test the script by animating the "Text Ticker" subcomosition "In" and "Out".

   <figure><img src="/files/XNoOEqB6x81YJLt0yooO" alt=""><figcaption><p>Text Ticker composition control</p></figcaption></figure>

## Composition Script

{% code title="Text Ticker script" %}

```javascript
(function() {

  return {

    init: function(comp, context) {
      console.log("Initialize Composition script " + comp.name);

      // we get the Text Ticker widget object
      const wiTextTicker = comp.findWidget("Text Ticker")[0];

      /**********************************************************************/

      // we listen to timeline events
      comp.addListener('timeline_event', (event, msg, e) => {
        // quick exit if a child composition propagated the event
        if (msg.compositionId != comp.id) return;

        const m = msg.message;
        // we reset and set the Text Ticker's speed on the "In" animation start
        if (m.event == "start" && m.targetState == "In") {
          const p = comp.getPayload2();
          // we remember the current ticker speed
          const speed = wiTextTicker.getPayload()["speed"];
          // we reset the speed and the message text
          wiTextTicker.setPayload({
            "speed": 0
          });
          // we set the ticker speed and update messages
          wiTextTicker.setPayload({
            "speed": speed,
            "text": p["Messages"]
          });
        }

        e.stopPropagation();
      });

      /**********************************************************************/

      // we listen to payload / control node changes
      comp.addListener('payload_changed', (event, msg, e) => {
        // quick exit if a child composition propagated the event
        if (msg.compositionId != comp.id) return;

        const p = comp.getPayload2();
        wiTextTicker.setPayload({
          "text": p["Messages"]
        });

        e.stopPropagation();
      });

    },

    close: function(comp, context) {
      console.log("Close Composition script " + comp.name);
    }
  };
})();
```

{% endcode %}

## Singular Control App

{% embed url="<https://app.singular.live/control/5kYH7oYCt94a2gnG44O9ev>" %}
Read control nodes, update ticker messages, and restart ticker on In animation
{% endembed %}


# Composition script editor reference

Get to know the composition script editor

The composition script editor is the place to write and edit composition scripts.&#x20;

### Composition script editor modes

There are two ways to run the composition script editor:

* Normal mode
* Safe mode

### Accessing the composition script editor

* Normal mode can be accessed in Composer by selecting the **\</> button** above the property panel.
* Safe mode can be accessed by selecting the **ambulance button**. This is available so that if a script contains a bug or sets off an infinite loop, you can close the browser and fix the bug in safe mode before returning to the normal mode editor again.

Both open the composition script editor in a new browser tab.

<figure><img src="/files/DYW6bligDJxhERwfMlDp" alt=""><figcaption><p>Accessing the composition script editor</p></figcaption></figure>

### Composition script editor layout

The composition script editor provides a code editor, composition navigator, and output preview in a single view.&#x20;

In addition, the menu bar offers a boilerplate script template that provides access to the composition's SingularPlayer object and starter code for the object's most commonly integrated events (message, state\_changed, payload\_changed, datanode\_payload\_changed, error).

1. [Menu bar](#menu-bar)
2. [Code editor](#code-editor)
3. [Preview panel](#preview-panel)
4. [Composition navigator panel](#bottom-right)

<figure><img src="/files/xZn8oeHOP0dyFyVemEDw" alt=""><figcaption><p>Composition script editor layout</p></figcaption></figure>

### 1. Menu bar

1. **Save all scripts**: compiles and executes all the composition scripts in the editor. The latest version of the composition scripts are also uploaded to the composition.
2. **Reload composition**: updates the incoming link from the composition so that the composition script access shows the current state of the composition.
3. **Beautify JavaScript**: beautifies/reformats code in the code editor.
4. **Script Library**: provides JavaScript boilerplate code for each type of composition script that describes its structure and available event listeners.
5. **Developer resources:** provides links to the following:
   * Singular graphics SDK documentation
   * Singular overlay SDK documentation
   * Code editor keyboard shortcuts
6. **Code editor settings**: provides option to customize the look of the code editor. Options include tab and font size, toggling the dark theme on and off, line numbers, wrap lines, indent with tabs, and render whitespace.
7. **Toggle interactive zones**: toggles a composition's interactive zones if any have been set up.
8. **Output orientation**: Toggles between landscape and portrait mode.
9. **Screen size**: Provides options for setting the output to the screen size of various devices.
10. **Support**: Opens a support window.

<figure><img src="/files/IXc5lF6s5Ygc2CEVP3eA" alt=""><figcaption><p>Menu bar</p></figcaption></figure>

### 2. Code editor

This is the place to write composition scripts.&#x20;

**The + button**

Selecting the **+** **button** opens a drop-down list of possible composition scripts to add to a composition. They include:

* An overlay script
* A global script
* A script for the root composition
* One script for each sub-composition

{% hint style="info" %}
The mark next to the name also conveys valuable information:

* **Circle**: a composition script has been created.
* **Checkmark**: the composition script is error-free.
* **Red circle**: the composition script contains an error.
  {% endhint %}

**Error checker**

The bottom left of the code editor shows any errors in the code and displays **OK** when there are none.

<figure><img src="/files/JrIesOIbhhvHSxhC6WtT" alt=""><figcaption><p>The code editor</p></figcaption></figure>

### 3. Preview panel

This panel displays a preview of the composition scripts' effect on the sub-composition.

<figure><img src="/files/4XFTIkC7H85uJIgTQyZ6" alt=""><figcaption><p>The preview panel</p></figcaption></figure>

### 4. Composition navigator

The compostion navigator contains various information about compositions that are needed to write composition scripts.

From left to right, the panels are:

* [Composition control](#composition-control-panel)
* [Globals](#globals-panel)
* [Widget explorer](#widget-explorer-panel)
* [Overlay SDK simulator](#overlay-sdk-simulator-panel)

<figure><img src="/files/u3whlCZTmuaK5XBtyfk1" alt=""><figcaption><p>Composition navigator</p></figcaption></figure>

#### Composition control panel

The composition control panel shows information about the composition's root composition and sub-compositions that are needed when writing composition scripts.

The left side of the panel shows the sub-composition's assigned logic layer and current In-Out state.

The right side of the panel shows additional information in the following tabs:

* **Fill-in form**: the editable text and its positioning of the selected root or sub-composition. When changed, the payload updates.
* **Sub-comp info**: the sub-composition's name and ID, as well as the ID, name, type, and current value of the elements of the sub-composition that have been made editable though a control node.
* **Model**: the model of the app in JSON, which is required for specific use cases.
* **Payload**: the JSON data for the selected sub-composition, which is needed in many Singular REST API requests.

<figure><img src="/files/Nkv0YM7OgSrYI69QDKwe" alt=""><figcaption><p>Composition control panel</p></figcaption></figure>

#### Globals panel

The globals panel contains the composition's screen size, layout, text direction, interactive status, and language. Globals are required for adaptive graphics and other specific use cases.

<figure><img src="/files/MNHSXgXWm44FrPXnaBk4" alt=""><figcaption><p>Globals panel</p></figcaption></figure>

#### Widget explorer panel

The widget explorer shows all the widgets used in a composition, including their name, ID, version, and status. It also shows the ID, name, and type of each widget's editable fields.

<figure><img src="/files/Zctqq2KExw6EnterV0nu" alt=""><figcaption><p>Widget explorer</p></figcaption></figure>

{% hint style="info" %}
Widgets can be updated to the published version in Composer by selecting the widget in the composition tree panel. This opens a drop down menu that shows available widget versions.&#x20;
{% endhint %}

<figure><img src="/files/IQLiS5kbkWmwGZQUKj3c" alt=""><figcaption><p>Updating widgets</p></figcaption></figure>

#### Overlay SDK simulator panel

The SDK overlay simulator is used to integrate Singular overlays with video players.

<figure><img src="/files/9SD3pDHL4KwWr8mvuSbJ" alt=""><figcaption><p>The overlay SDK simulator panel</p></figcaption></figure>


# Graphics SDK

The Graphic SDK is a JavaScript library for integrating Singular graphics into web pages and apps, and implementing client-side use cases.

This section provides a reference to all Singular graphic SDK function calls.

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/6kBGr7EE0ofNzoHfkxQr">Getting started</a></td></tr><tr><td><a href="/pages/Fm7sqHA6mC0ikzNgrL77">Reference</a></td></tr><tr><td><a href="/pages/jG1bwqU9wjcAa3frMyIK">Guides and examples</a></td></tr></tbody></table>


# Getting started

Include the Singular Graphics SDK JavaScript library in your HTML code:

```html
<head>
  <!-- Include the Singular Graphic SDK JavaSrcipt library -->
  <script src="https://app.singular.live/libs/singulargraphics/1.0.0/singulargraphics.js"></script>
</head>
```

Add a `div` to your HTML code for a non-interactive Singular overlay instance:

```html
<body>
    <!-- define iframe for a non-interactive Singular player instance -->
    <iframe id="SingularOverlay"
      style="width:720px; height: 400px;"
      src="https://app.singular.live/singularplayer/client">
    </iframe>
</body>
```

Or add a `div` to your HTML code for an interactive Singular overlay instance:

```html
<body>
    <!-- define iframe for a non-interactive Singular player instance -->
    <iframe id="SingularOverlay"
      style="width:720px; height: 400px;"
      src="https://app.singular.live/singularplayer/client?interactive=1">
    </iframe>
</body>
```


# Reference

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/WIY7XutXej7ZbBwDtVQ2">SDK functions</a></td></tr><tr><td><a href="/pages/obiH285JBIloTqjYBFTD">Composition object</a></td></tr><tr><td><a href="/pages/IMxSpLKp6DSlNsDz2EgJ">Sequencer object</a></td></tr></tbody></table>


# SDK functions

### SingularGraphics(domElement) <a href="#singulargraphicsdomelement" id="singulargraphicsdomelement"></a>

Use the following code to create an instance of a Singular overlay:

```javascript
const overlay = SingularGraphics("SingularOverlay");
```

<table><thead><tr><th width="131">Attributes</th><th width="82">Type</th><th>Required</th><th width="375">Description</th></tr></thead><tbody><tr><td>domId</td><td>String</td><td>Yes</td><td>The target <code>div</code> that the SingularOverlay will replace.</td></tr></tbody></table>

### SDK properties <a href="#sdk-properties" id="sdk-properties"></a>

<table><thead><tr><th width="121">Name</th><th width="94">Type</th><th>Example</th><th width="274">Description</th></tr></thead><tbody><tr><td>host</td><td>String</td><td>"https://app.singular.live"</td><td>Domain of the SDK player client.</td></tr><tr><td>id</td><td>String</td><td>"SingularOverlay"</td><td>iframe ID.</td></tr></tbody></table>

```javascript
const compMain = overlay.getMainComposition();
console.log("compMain =", compMain);
```

#### **Return**

`object`: Graphic SDK object

{% code title="overlay:" %}

```log
> addListener: ƒ (event, callback)
> getAdaptationGlobals: ƒ ()
> getCompositionById: ƒ (compId)
> getCompositionInfo: ƒ ()
> getConfig: ƒ ()
> getMainComposition: ƒ ()
> getSequencer: ƒ ()
> host: "https://app.singular.live"
> id: "SingularOverlay"
> loadComposition: ƒ loadComposition(compositionId, cb)
> noTrack: ƒ ()
> onmessage: ƒ (e)
> removeAllListener: ƒ ()
> removeListener: ƒ (event, callback)
> renderAppOutput: ƒ renderAppOutput(appId, output, cb)
> renderComposition: ƒ renderComposition(compositionObject, cb)
> setAdaptationGlobals: ƒ setAdaptationGlobals(data)
> setConfig: ƒ (_config)
> setFrameNumber: ƒ setFrameNumber(frame)
> videoCommand: ƒ videoCommand(cmd, time)
```

{% endcode %}

### SDK functions <a href="#sdk-functions" id="sdk-functions"></a>

The reference documentation provided in the following sections describes the Graphic SDK structure and methods.

<table><thead><tr><th width="308.3333333333333">Method</th><th width="126">Return type</th><th>Description</th></tr></thead><tbody><tr><td><a href="#addlistener">addListener(event, callback)</a></td><td>-</td><td>Adds an event listener.</td></tr><tr><td><a href="#getadaptationglobals">getAdaptationGlobals()</a></td><td>Object</td><td>Returns adaptation global parameters.</td></tr><tr><td><a href="#getcompositionbyid">getCompositionById(compId)</a></td><td>Object</td><td>Returns the composition object requested by its ID.</td></tr><tr><td><a href="#getcompositioninfo">getCompositionInfo()</a></td><td>Object</td><td>Returns a detailed description of a composition's structure, control and data nodes, animation, and timeline states.</td></tr><tr><td><a href="#getconfig">getConfig()</a></td><td>Object</td><td>Returns configuration details.</td></tr><tr><td><a href="#getmaincomposition">getMainComposition()</a></td><td>Object</td><td>Returns the main composition object.</td></tr><tr><td><a href="#getsequencer">getSequencer()</a></td><td>Object</td><td>Returns the sequencer object.</td></tr><tr><td><a href="#loadcomposition">loadComposition(compositionId, callback)</a></td><td>-</td><td>Loads a composition defined by its ID or JSON URL.</td></tr><tr><td><a href="#notrack">noTrack()</a></td><td>-</td><td>Deactivates tracking of analytics.</td></tr><tr><td><a href="#onmessage">onmessage(event)</a></td><td>-</td><td>Depreciated.</td></tr><tr><td><a href="#removealllistener">removeAllListener()</a></td><td>-</td><td>Removes all event listeners.</td></tr><tr><td><a href="#removelistener">removeListener(event, callback)</a></td><td>-</td><td>Removes an event listener defined by its name.</td></tr><tr><td><a href="#renderappoutput">renderAppOutput(appId, output, callback)</a></td><td>-</td><td>Loads a composition defined by its app ID.</td></tr><tr><td><a href="#rendercomposition">renderComposition(compositionObject, callback)</a></td><td>-</td><td>Loads the composition defined in a composition object.</td></tr><tr><td><a href="#setadaptationglobals">setAdaptationGlobals(data)</a></td><td>-</td><td>Sets adaptation global parameters.</td></tr><tr><td><a href="#setconfig">setConfig(data)</a></td><td>-</td><td>Sets configuration properties.</td></tr><tr><td><a href="#setframenumber">setFrameNumber(frame)</a></td><td>-</td><td>Depreciated.</td></tr><tr><td><a href="#videocommand">videoCommand(cmd, time)</a></td><td>-</td><td>Depreciated.</td></tr></tbody></table>

### addListener <a href="#addlistener" id="addlistener"></a>

Adds a listener for events coming from the overlay.

```javascript
overlay.addListener(type, callback);
```

<table><thead><tr><th>Attributes</th><th width="315">Description</th><th width="108">Type</th><th>Required</th><th></th></tr></thead><tbody><tr><td>type</td><td>A string containing one of the values described below</td><td>String</td><td>Yes</td><td></td></tr><tr><td>callback</td><td>The callback function for the <code>type</code> of event</td><td>Function</td><td>Yes</td><td></td></tr></tbody></table>

#### **Values for listener `type`**

<table><thead><tr><th width="314">Setting</th><th>Description</th></tr></thead><tbody><tr><td>payload_changed</td><td>Listens to messages from control node updates.</td></tr><tr><td>datanode_payload_changed</td><td>Listens to messages from data node updates.</td></tr><tr><td>state_changed</td><td>Listens to messages for animation state changes.</td></tr><tr><td>message</td><td>Listens to messages from the interactive events and widgets.</td></tr><tr><td>error</td><td>Listens to errors reported by overlay instances.</td></tr></tbody></table>

**See also:**

[removeAllListener](#removealllistener) [removeListener](#removelistener)

#### payload\_changed <a href="#payload_changed" id="payload_changed"></a>

Listens to `payload_changed` from control node updates.

```javascript
overlay.addListener('payload_changed', (event, msg) => {
  console.log("Composition payload changed:", event, msg);
});
```

**Callback attributes**

<table><thead><tr><th width="142.33333333333331">Attributes</th><th>Type</th><th width="309">Description</th></tr></thead><tbody><tr><td>event</td><td>String</td><td>The event name.</td></tr><tr><td>msg</td><td>Object</td><td>JSON data containing event details.</td></tr></tbody></table>

```json
{
  "compId": "your-composition-id",
  "compName": "your-composition-name",
  "compositionId": "your-composition-id",
  "compositionName": "your-composition-name",
  "payload": {
    "your-control-node-ids": "your-control-node-values"
  }
}
```

#### datanode\_payload\_changed <a href="#datanode_payload_changed" id="datanode_payload_changed"></a>

Listens to `datanode_payload_changed` from data node updates

```javascript
overlay.addListener('datanode_payload_changed', (event, msg) => {
  console.log("Composition datanode payload:", event, msg);
});
```

**Callback attributes**

<table><thead><tr><th>Attributes</th><th>Type</th><th width="283.3333333333333">Description</th></tr></thead><tbody><tr><td>event</td><td>String</td><td>The event name.</td></tr><tr><td>msg</td><td>Object</td><td>JSON containing event details.</td></tr></tbody></table>

```json
{
  "id": "your-datanode-id",
  "name": "your-datanode-name",
  "payload": {
    "your-datanode-ids": "your-datanode-values"
  }
}
```

#### state\_changed <a href="#state_changed" id="state_changed"></a>

Listens to `state_changed` for animation state changes.

```javascript
overlay.addListener('state_changed', (event, msg) => {
  console.log("Composition state changed:", event, msg);
});
```

**Callback attributes**

<table><thead><tr><th>Attributes</th><th width="154.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>event</td><td>String</td><td>The event name.</td></tr><tr><td>msg</td><td>Object</td><td>JSON containing event details.</td></tr></tbody></table>

```json
{
  "compositionId": "your-composition-id",
  "compositionName": "your-composition-name",
  "state": "In" | "Out" | "Out1" | "Out2"
}
```

#### message <a href="#message" id="message"></a>

Listens to `message` from interactive events and widgets

```javascript
overlay.addListener('message', (event, msg) => {jaconsole.log("Composition message:", event, msg);
});
```

**Callback attributes**

| Attributes | Type   | Description                    |
| ---------- | ------ | ------------------------------ |
| event      | String | The event name.                |
| msg        | Object | JSON containing event details. |

```json
{
  "event": "custom",
  "params": {
    "compId": "your-composition-id",
    "id": "your-widget-id",
    "name": "your-widget-name",
    "widgetId": "widget-id",
    "type": "type-of-message-sender",
    "data": {
      "JSON-object-with-custom-data"
    }
  }
}
```

#### error <a href="#error" id="error"></a>

Listens to `error` reported by an overlay instance.

```javascript
overlay.addListener("error", (params) => {
  console.log("Error:", params);
});
```

**Callback attributes**

<table><thead><tr><th>Attributes</th><th width="149.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>params</td><td>Object</td><td>JSON containing error details.</td></tr></tbody></table>

### getAdaptationGlobals <a href="#getadaptationglobals" id="getadaptationglobals"></a>

Makes a request to get adaptation global settings.

```javascript
const adaptationGlobals = overlay.getAdaptationGlobals();
console.log("adaptationGlobals =", adaptationGlobals);
```

#### **Return**

`object`: adaptation globals as a JSON object

```json
{
  "custom1": "",
  "custom2": "",
  "custom3": "",
  "interactive": "interactive",
  "language": "",
  "layout": "landscape",
  "screenSize": "small",
  "textDirection": "ltr"
}
```

#### **See also**

[setAdaptationGlobals](#setadaptationglobals)

### getCompositionById <a href="#getcompositionbyid" id="getcompositionbyid"></a>

Returns the composition object requested by its ID.

#### **Parameters**

| Name          | Type   | Description              |
| ------------- | ------ | ------------------------ |
| compositionId | String | The ID of a composition. |

```javascript
const compositionId = "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf";
const compositionObject = overlay.getComposition(compositionId);
console.log("compositionObject =", compositionObject);
```

#### **Return**

`object`: Composition object

{% code title="compositionObject:" %}

```log
> find: ƒ ()
> getCompositionById: ƒ (subCompId)
> getControlNode: ƒ ()
> getDataNodes: ƒ ()
> getLogicLayer: ƒ ()
> getModel: ƒ ()
> getPayload: ƒ ()
> getPayload2: ƒ ()
> getState: ƒ ()
> getSubcompositionById: ƒ (subCompId)
> id: "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf"
> jumpTo: ƒ (to)
> listSubcompositions: ƒ ()
> playTo: ƒ (to)
> seek: ƒ (to)
> sendMessage: ƒ (message)
> setPayload: ƒ (_payload)
```

{% endcode %}

#### **See also**

[Composition object](/software-development-kits/graphics-sdk/reference/composition-object)

### getCompositionInfo <a href="#getcompositioninfo" id="getcompositioninfo"></a>

Returns the JSON definition of the loaded composition.

```javascript
const compositionInfo = overlay.getCompositionInfo();
console.log("compositionInfo =", compositionInfo);
```

#### **Return**

`object`: Composition object

```javascript
{
  "adaptationGlobals": {"screenSize": "small", "layout": "landscape", "textDirection": "ltr", "interactive": "interactive", "language": "", …}
  "compositionDuration": {"-N8o2dhQeHQXZ6Emr2I7": {…}, "04234444-4383-4772-9619-ff971119eb5a": {…}, …}
  "compositionId": "-N8o2dhQeHQXZ6Emr2I7"
  "compositionProps": {"logicLayers": {…}, "timeline2Active": {…}, "durations": {…}}
  "compositionStates": {"-Kn25RyfewL29LigHvV1": "Out1", "-KnXXbHZpD6sKVP4iHCD": "Out1", "-Knh0082CSZTy0CXJ_B_": "Out1", …}
  "dataSources": {"-N8o2dhQeHQXZ6Emr2I7": {…}, "04234444-4383-4772-9619-ff971119eb5a": {…}, …}
  "effect": {"-N8o2dhQeHQXZ6Emr2I7": {…}, "04234444-4383-4772-9619-ff971119eb5a": {…}, …}
  "idToName": {"04234444-4383-4772-9619-ff971119eb5a": "Panel Left - Image", "1c895c6d-f17d-400a-8591-0c38298bbb4f": "Fullscreen - Title", …}
  "nameToId": {"Panel Left - Image": "04234444-4383-4772-9619-ff971119eb5a", "Fullscreen - Title": "1c895c6d-f17d-400a-8591-0c38298bbb4f", …}
  "subcompositionNames": {"-N8o2dhQeHQXZ6Emr2I7": {…}, "04234444-4383-4772-9619-ff971119eb5a": {…}, …}
}
```

### getConfig <a href="#getconfig" id="getconfig"></a>

Returns the configuration of the overlay.

```javascript
const config = overlay.getConfig();
console.log("config =", config);
```

#### **Return**

`object`: JSON object

### getMainComposition <a href="#getmaincomposition" id="getmaincomposition"></a>

Returns the composition object of the main or root composition.

```javascript
const compMain = overlay.getMainComposition();
console.log("compMain =", compMain);
```

#### **Return**

`object`: Composition object

{% code title="compMain:" %}

```log
> find: ƒ ()
> getCompositionById: ƒ (subCompId)
> getControlNode: ƒ ()
> getDataNodes: ƒ ()
> getLogicLayer: ƒ ()
> getModel: ƒ ()
> getPayload: ƒ ()
> getPayload2: ƒ ()
> getState: ƒ ()
> getSubcompositionById: ƒ (subCompId)
> id: "-N8o2dhQeHQXZ6Emr2I7"
> jumpTo: ƒ (to)
> listSubcompositions: ƒ ()
> playTo: ƒ (to)
> seek: ƒ (to)
> sendMessage: ƒ (message)
> setPayload: ƒ (_payload)
```

{% endcode %}

#### **See also**

[Composition object](/software-development-kits/graphics-sdk/reference/composition-object)

### getSequencer <a href="#getsequencer" id="getsequencer"></a>

Returns the sequencer object of the overlay.

```javascript
const sequencer = overlay.getSequencer();
console.log("sequencer =", sequencer);
```

#### **Return**

`object`: Sequencer object

{% code title="sequencer:" %}

```log
> play: ƒ (time)
> seek: ƒ (time)
> seqId: "-N8o2dhQeHQXZ6Emr2I7"
> setDuration: ƒ (duration)
> setPayload: ƒ (payload)
> start: ƒ ()
> stop: ƒ ()
```

{% endcode %}

**See also**

[Sequencer object](/software-development-kits/graphics-sdk/reference/sequencer-object)

### loadComposition <a href="#loadcomposition" id="loadcomposition"></a>

Loads a composition into an overlay instance.

```javascript
overlay.loadComposition(composition, outputName, callback);
```

<table><thead><tr><th width="157">Attributes</th><th width="102">Type</th><th width="111">Required</th><th>Description</th></tr></thead><tbody><tr><td>composition</td><td>String</td><td>Yes</td><td>A composition referenced by token or URL.</td></tr><tr><td>outputName</td><td>String</td><td>Yes</td><td>Depreciated. Set output name to <code>null</code> for compatibility reasons.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>Callback function called when the content has been loaded.</td></tr></tbody></table>

**Sample**

{% tabs %}
{% tab title="Load by composition URL" %}

```javascript
const compositionUrl = "https://assets.singular.live/f12f184c9a0eb763beb40478e02a1250/jsons/164gBjjETnHFKEU3LjPCKe.json";

overlay.loadComposition(compositionUrl, (success) => {
  // called when content finished loading
  if (success) {
    console.log("Composition loaded");
  } else {
    console.warn("Couldn't load composition");
  }
});
```

{% endtab %}

{% tab title="Load by composition token" %}

```javascript
const compositionToken = "08EBsfeF4yoYLejOXM51Rk";

overlay.loadComposition(compositionToken , null, (success) => {
  // called when content finished loading
  if (success) {
    console.log("Composition loaded");
  } else {
    console.warn("Couldn't load composition");
  }
});
```

{% endtab %}
{% endtabs %}

### noTrack <a href="#notrack" id="notrack"></a>

Deactivates analytic tracking.

### onmessage <a href="#onmessage" id="onmessage"></a>

This call has been depreciated.

### removeAllListener <a href="#removealllistener" id="removealllistener"></a>

Removes all registered listeners.

```javascript
overlay.removeAllListener();
```

#### **See also**

[addListener](#addlistener)

[removeListener](#removelistener)

### removeListener <a href="#removelistener" id="removelistener"></a>

Removes a listener specified by its type.

```javascript
overlay.removeListener(type, callback);
```

<table><thead><tr><th>Attributes</th><th width="98">Type</th><th width="104">Required</th><th>Description</th></tr></thead><tbody><tr><td>type</td><td>String</td><td>Yes</td><td>A string containing one of the values described below.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>The callback function for the <code>type</code> of event.</td></tr></tbody></table>

#### **Values for listener `type`**

| Setting                    | Description                                                  |
| -------------------------- | ------------------------------------------------------------ |
| payload\_changed           | Listens to messages from control node updates.               |
| datanode\_payload\_changed | Listens to messages from data node updates.                  |
| state\_changed             | Listens to messages for animation state changes.             |
| message                    | Listens to messages from the interactive events and widgets. |
| error                      | Listens to errors reported by an overlay instance.           |

#### **See also:**

[removeAllListener](#removealllistener)\
[removeListener](#removelistener)

### renderAppOutput <a href="#renderappoutput" id="renderappoutput"></a>

Load san app output into the overlay instance.

```javascript
overlay.renderAppOutput(appInstanceId, outputName, callback);
```

<table><thead><tr><th width="159">Attributes</th><th width="116">Type</th><th width="102">Required</th><th>Description</th></tr></thead><tbody><tr><td>appInstanceId</td><td>String</td><td>Yes</td><td>The ID of an app instance.</td></tr><tr><td>outputName</td><td>String</td><td>Yes</td><td>Depreciated. Sets output name to <code>null</code> for compatibility reasons.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>The callback function called when the content has been loaded.</td></tr></tbody></table>

#### **Sample**

```javascript
const appInstanceId = 12345;

overlay.renderAppOutput(appInstanceId, null, (success) => {
  // called when content finished loading
  if (success) {
    console.log("App output loaded");
  } else {
    console.warn("Couldn't load app output");
  }
});
```

### renderComposition <a href="#rendercomposition" id="rendercomposition"></a>

Loads a composition into an overlay instance.

```javascript
overlay.renderComposition(composition, callback);
```

<table><thead><tr><th width="156">Attributes</th><th width="103">Type</th><th width="115">Required</th><th>Description</th></tr></thead><tbody><tr><td>composition</td><td>Object</td><td>Yes</td><td>A composition's JSON object.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>The callback function called when the content has been loaded.</td></tr></tbody></table>

#### **Sample**

```javascript
const composition = {...};

overlay.renderComposition(composition, null, (success) => {
  // called when content finished loading
  if (success) {
    console.log("Composition loaded");
  } else {
    console.warn("Couldn't load composition");
  }
});
```

### setAdaptationGlobals <a href="#setadaptationglobals" id="setadaptationglobals"></a>

Sets adaptation global settings.

```javascript
const adaptationGlobals = {
  "custom1": "stationId=abc123",a
  "custom2": "",
  "custom3": "",
  "interactive": "",
  "language": "",
  "layout": "portrait",
  "screenSize": "small",
  "textDirection": "ltr"
};

overlay.setAdaptationGlobals(adaptationGlobals);
```

#### **See also**

[getAdaptationGlobals](#getadaptationglobals)

### setConfig <a href="#setconfig" id="setconfig"></a>

Sets the configuration of an overlay.

```javascript
const config = {...};
overlay.setConfig(config);
```

### setFrameNumber <a href="#setframenumber" id="setframenumber"></a>

This call has been depreciated.

### videoCommand <a href="#videocommand" id="videocommand"></a>

This call has been depreciated.


# Composition object

### Properties <a href="#properties" id="properties"></a>

<table><thead><tr><th width="107">Name</th><th width="91">Type</th><th>Example</th><th width="196">Description</th></tr></thead><tbody><tr><td>id</td><td>String</td><td>"303798-latest-1589207585405"</td><td>The ID of the composition.</td></tr></tbody></table>

### Methods <a href="#methods" id="methods"></a>

The composition object has following methods:

<table><thead><tr><th width="254">Method</th><th width="135.33333333333331" align="center">Return type</th><th>Brief description</th></tr></thead><tbody><tr><td><a href="#findname">find(name)</a></td><td align="center">Object</td><td>Returns the composition object of a sub-composition with a specified <code>name</code>.</td></tr><tr><td><a href="#getcompositionbyidid">getCompositionById(id)</a></td><td align="center">Object</td><td>Returns the composition object of a sub-composition with a specified <code>id</code>.</td></tr><tr><td><a href="#getcontrolnode">getControlNode()</a></td><td align="center">Object</td><td>Returns a composition's control nodes.</td></tr><tr><td>getDataNode()</td><td align="center">Object</td><td>Returns a composition's data nodes.</td></tr><tr><td><a href="#getlogiclayer">getLogicLayer()</a></td><td align="center">Object</td><td>Returns a composition's logic layers.</td></tr><tr><td><a href="#getmodel">getModel()</a></td><td align="center">Object</td><td>Returns a composition's model description.</td></tr><tr><td><a href="#getpayload">getPayload()</a></td><td align="center">Object</td><td>Returns a composition's payload as an array.</td></tr><tr><td><a href="#getpayload2">getPayload2()</a></td><td align="center">Object</td><td>Returns a composition's payload as a JSON object.</td></tr><tr><td><a href="#getstate">getState()</a></td><td align="center">Object</td><td>Returns a composition's state.</td></tr><tr><td><a href="#getsubcompositionbyidid">getSubcompositionById(id)</a></td><td align="center">Object</td><td>Returns the composition object of a sub-composition with a specified <code>id</code>.</td></tr><tr><td><a href="#jumptoto">jumpTo(to)</a></td><td align="center">-</td><td>Jumps to the animation state specified by <code>to</code>.</td></tr><tr><td><a href="#listsubcompositions">listSubcompositions()</a></td><td align="center">Object</td><td>Returns an array of a composition's sub-compositions.</td></tr><tr><td><a href="#playtoto">playTo(to)</a></td><td align="center">-</td><td>Plays to the animation state specified by <code>to</code>.</td></tr><tr><td><a href="#sendmessagemessage">sendMessage(message)</a></td><td align="center">-</td><td>Sends a custom message.</td></tr><tr><td><a href="#setpayloadpayload">setPayload(payload)</a></td><td align="center">-</td><td>Sets a composition's payload.</td></tr></tbody></table>

### find(name) <a href="#findname" id="findname"></a>

Returns an array of composition objects with a specified `name`.

#### **Parameters**

<table><thead><tr><th>Name</th><th width="158.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>name</td><td>String</td><td>The name of the composition.</td></tr></tbody></table>

```javascript
const name = "LowerThird";
// we use the first element in the array
const compObject = composition.find(name)[0];
```

#### **Return**

`Array`: An array of composition objects

{% code title="compObject:" %}

```log
> find: ƒ ()
> getCompositionById: ƒ (subCompId)
> getControlNode: ƒ ()
> getDataNodes: ƒ ()
> getLogicLayer: ƒ ()
> getModel: ƒ ()
> getPayload: ƒ ()
> getPayload2: ƒ ()
> getState: ƒ ()
> getSubcompositionById: ƒ (subCompId)
> id: "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf"
> jumpTo: ƒ (to)
> listSubcompositions: ƒ ()
> playTo: ƒ (to)
> seek: ƒ (to)
> sendMessage: ƒ (message)
> setPayload: ƒ (payload)
```

{% endcode %}

#### **See also**

[Composition object](/software-development-kits/graphics-sdk/reference/composition-object)

### getCompositionById(id) <a href="#getcompositionbyidid" id="getcompositionbyidid"></a>

Returns the composition object of a sub-composition with a specified `id`.

#### **Parameters**

| Name | Type   | Description             |
| ---- | ------ | ----------------------- |
| id   | String | The sub-composition ID. |

```javascript
const id = "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf";
const compObject = composition.find(id);
```

#### **Return**

`object`: Composition object

{% code title="compObject:" %}

```log
> find: ƒ ()
> getCompositionById: ƒ (subCompId)
> getControlNode: ƒ ()
> getDataNodes: ƒ ()
> getLogicLayer: ƒ ()
> getModel: ƒ ()
> getPayload: ƒ ()
> getPayload2: ƒ ()
> getState: ƒ ()
> getSubcompositionById: ƒ (subCompId)
> id: "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf"
> jumpTo: ƒ (to)
> listSubcompositions: ƒ ()
> playTo: ƒ (to)
> seek: ƒ (to)
> sendMessage: ƒ (message)
> setPayload: ƒ (payload)
```

{% endcode %}

#### **See also**

[Composition object](/software-development-kits/graphics-sdk/reference/composition-object)

### getControlNode() <a href="#getcontrolnode" id="getcontrolnode"></a>

Returns a composition's control node model and payload.

```javascript
const controlnode = compositionObject.getControlNode();
```

#### **Return**

`object`: A JSON description, if the composition has control nodes\
`undefined`: if the composition doesn't have any control nodes

```json
{
  "id": "controlNode",
  "model": {
    "fields": {
      "3c686562-1cf3-7493-7fae-803009afa22e": {
        "defaultValue": "Default Text",
        "id": "Top Title",
        "index": 2,
        "title": "Top Title",
        "type": "text"
      },
      "502a7d0e-e0f8-a895-1b80-43cdb81a1ca9": {
        "defaultValue": "Default Text",
        "id": "Bottom Title",
        "index": 3,
        "title": "Bottom Title",
        "type": "text"
      }
    }
  },
  "name": "Control Node",
  "payload": {
    "Bottom Title": "Extra Information",
    "Top Title": "SINGULAR.LIVE"
  },
  "thumbnail": "/images/noimage.png"
}
```

### getLogicLayer() <a href="#getlogiclayer" id="getlogiclayer"></a>

Returns a composition's logic layer details.

```javascript
const logicLayer = compositionObject.getLogicLayer();
```

#### **Return**

`object`: a JSON description, if a logic layer has been defined for the composition

`undefined`: if no logic layer has been defined for the composition

```json
{
  "name": "Lower",
  "tag": "#B0C8CD"
}
```

### getModel() <a href="#getmodel" id="getmodel"></a>

Returns the model description of a composition's control node fields.

```
const model = compositionObject.getModel();
```

#### **Return**

`object`: An array of JSON objects, if the composition has control nodes

`undefined`: if the composition doesn't have any control nodes

```json
[
  {
    "0": {
      "defaultValue": "Default Text",
      "id": "Top Title",
      "index": 2,
      "title": "Top Title",
      "type": "text"
    }
  },
  {
    "1": {
      "defaultValue": "Default Text",
      "id": "Bottom Title",
      "index": 3,
      "title": "Bottom Title",
      "type": "text"
    }
  }
]
```

### getPayload() <a href="#getpayload" id="getpayload"></a>

Returns the payload of a composition's control node.

```javascript
const payload = compositionObject.getPayload();
```

#### **Return**

`object`: An array of JSON objects, if the composition has control nodes

`undefined`: if the composition doesn't have any control nodes

```json
[
  {
    "0": {
      "key": "Bottom Title",
      "value": "Extra Information"
    }
  },
  {
    "1": {
      "key": "Top Title",
      "value": "SINGULAR.LIVE"
    }
  }
]
```

### getPayload2()

Returns the control node content from the composition as a JSON object.

```javascript
const payload = comp.getPayload2();
console.log(payload);
```

### getState() <a href="#getstate" id="getstate"></a>

Returns the composition's animation state.

```javascript
const animState = compositionObject.getState();
```

#### **Return**

`String`: animation state

```javascript
getState = Out
```

### getSubcompositionById(id) <a href="#getsubcompositionbyidid" id="getsubcompositionbyidid"></a>

Returns the composition object of a sub-composition with a specified `id`.

#### **Parameters**

| Name | Type   | Description               |
| ---- | ------ | ------------------------- |
| id   | String | ID of the sub-composition |

```
const id = "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf";
const compObject = composition.find(id);
```

#### **Return**

`object`: Composition object

{% code title="compObject:" %}

```log
> find: ƒ ()
> getCompositionById: ƒ (subCompId)
> getControlNode: ƒ ()
> getDataNodes: ƒ ()
> getLogicLayer: ƒ ()
> getModel: ƒ ()
> getPayload: ƒ ()
> getPayload2: ƒ ()
> getState: ƒ ()
> getSubcompositionById: ƒ (subCompId)
> id: "dbc46c1a-1375-4c2c-9f0d-1df5a86d0baf"
> jumpTo: ƒ (to)
> listSubcompositions: ƒ ()
> playTo: ƒ (to)
> seek: ƒ (to)
> sendMessage: ƒ (message)
> setPayload: ƒ (payload)
```

{% endcode %}

#### **See also**

[Composition object](/software-development-kits/graphics-sdk/reference/composition-object)

### jumpTo(to) <a href="#jumptoto" id="jumptoto"></a>

Jumps to the animation state specified by `to`.

```javascript
compositionObject.jumpTo("In");
```

#### **Return**

`none`

### listSubcompositions() <a href="#listsubcompositions" id="listsubcompositions"></a>

Returns an array of a composition's sub-compositions.

```javascript
const subcompositionList = compositionObject.listSubcompositions();
```

#### **Return**

`object`: An array of JSON objects

```json
[ { "id": "a4db02c8-df43-4737-a43d-ba9a4430cd86", "name": "Lower 1 Line - Large" } ]
```

### playTo(to) <a href="#playtoto" id="playtoto"></a>

Plays to the animation state specified by `to`.

```javascript
compositionObject.playTo("In");
```

#### **Return**

`none`

### sendMessage(message) <a href="#sendmessagemessage" id="sendmessagemessage"></a>

Send a message to the composition. Use the [message listener](https://developer.singular.live/composition-scripting/cheat-sheets/fundamentals#message) to receive the message in the [Composition Script](/composition-scripting/introduction).

<table><thead><tr><th width="140">Attributes</th><th width="103">Type</th><th width="127">Required</th><th>Description</th></tr></thead><tbody><tr><td>object</td><td>object</td><td>Yes</td><td>Custom data as JSON object</td></tr></tbody></table>

### setPayload(payload) <a href="#setpayloadpayload" id="setpayloadpayload"></a>

Sets the payload of a composition's control nodes.

```javascript
const payload = {
  "Bottom Title": "Extra Information",
  "Top Title": "SINGULAR.LIVE",
};
compositionObject.setPayload(payload);
```

#### **Return**

`none`


# Sequencer object

## Properties <a href="#properties" id="properties"></a>

<table><thead><tr><th width="103">Name</th><th width="113">Type</th><th>Example</th><th>Description</th></tr></thead><tbody><tr><td>seqId</td><td>String</td><td>"-N8o2dhQeHQXZ6Emr2I7"</td><td>The ID of a sequencer.</td></tr></tbody></table>

## Methods <a href="#methods" id="methods"></a>

The sequencer object has the following methods:

<table><thead><tr><th width="233">Method</th><th width="146.33333333333331" align="center">Return type</th><th>Description</th></tr></thead><tbody><tr><td><a href="#playtime">play(time)</a></td><td align="center">-</td><td>Plays a sequence to <code>time</code>.</td></tr><tr><td><a href="#seektime">seek(time)</a></td><td align="center">-</td><td>Seeks a sequence to <code>time</code>.</td></tr><tr><td><a href="#setdurationduration">setDuration(duration)</a></td><td align="center">-</td><td>Sets the duration of a sequence.</td></tr><tr><td><a href="#setpayloadpayload">setPayload(payload)</a></td><td align="center">-</td><td>Sets a sequencer payload.</td></tr><tr><td><a href="#start">start()</a></td><td align="center">-</td><td>Starts playing a sequence.</td></tr><tr><td><a href="#stop">stop()</a></td><td align="center">-</td><td>Stops playing a sequence.</td></tr></tbody></table>

### play(time) <a href="#playtime" id="playtime"></a>

Plays a sequence to a specified `time`.

**Parameters**

| Name | Type   | Description      |
| ---- | ------ | ---------------- |
| time | number | Time in seconds. |

```javascript
sequencer.play(10.5);
```

**See also**

[getSequencer](/software-development-kits/graphics-sdk/reference/sdk-functions#getsequencer)\
[seek](#seektime)\
[start](#start)\
[stop](#stop)

### seek(time) <a href="#seektime" id="seektime"></a>

Seeks a sequence to a specified `time`.

**Parameters**

| Name | Type   | Description      |
| ---- | ------ | ---------------- |
| time | number | Time in seconds. |

```javascript
sequencer.seek(10.5);
```

**See also**

[getSequencer](/software-development-kits/graphics-sdk/reference/sdk-functions#getsequencer)\
[play](#playtime)\
[start](#start)\
[stop](#stop)

### setDuration(duration) <a href="#setdurationduration" id="setdurationduration"></a>

Sets the duration of a sequence.

**Parameters**

| Name     | Type   | Description          |
| -------- | ------ | -------------------- |
| duration | number | Duration in seconds. |

```javascript
sequencer.setDuration(120.0);
```

**See also**

[getSequencer](/software-development-kits/graphics-sdk/reference/sdk-functions#getsequencer)\
[setPayload](#setpayloadpayload)

### setPayload(payload) <a href="#setpayloadpayload" id="setpayloadpayload"></a>

Sets the payload of a sequence.

**Parameters**

<table><thead><tr><th width="137.33333333333331">Name</th><th width="185">Type</th><th>Description</th></tr></thead><tbody><tr><td>payload</td><td>JSON</td><td>A payload defining a sequence's data and timing.</td></tr></tbody></table>

```javascript
const payload = {
  "2ed116aa-bb56-49a5-adc4-37f7654312a8": [{
    "beginTime": 2,
    "endTime": 5,
    "controlNode": {
      "payload": {
        "Text": "This is headline 1"
      }
    }
  }, {
    "beginTime": 5,
    "endTime": 9,
    "controlNode": {
      "payload": {
        "Text": "This is headline 2"
      }
    }
  }],
  "dce1e90c-1d8d-4854-87e0-da8eed153105": [{
    "beginTime": 7,
    "endTime": 10,
    "controlNode": {
      "payload": {
        "Image": "https://assets.singular.live/7072b13f9e20b98034f48d6202400ff9/svgs/57VdHBnRTPNEU4yirlLfKm_w880h146.svg",
        "Image Size": "100",
        "Title": "A panel on the left size"
      }
    }
  }]
};

sequencer.setPayload(payload);
```

**See also**

[getSequencer](/software-development-kits/graphics-sdk/reference/sdk-functions#getsequencer)\
[setDuration](#setdurationduration)

### start() <a href="#start" id="start"></a>

Starts playing a sequence.

### stop() <a href="#stop" id="stop"></a>

Stops playing a sequence.


# Guides and examples


# Load a composition with its token

Use this code to load a composition by its token for on-device use cases during the design process only.&#x20;

For best performance and quickest loading times, we recommend using the [composition URL when publishing an integration](/software-development-kits/graphics-sdk/guides-and-examples/load-a-composition-with-its-url).

### **Supported data integrations**

<table><thead><tr><th width="215">Type</th><th width="138.33333333333331" align="center">Support</th><th>Description</th></tr></thead><tbody><tr><td>Control node</td><td align="center">No</td><td>No support for <a href="/pages/E1K0dQQTcGopHY56Irkh">REST API </a>calls.</td></tr><tr><td>Data node</td><td align="center">Yes</td><td>Update data nodes via the <a href="/pages/E1K0dQQTcGopHY56Irkh">REST API</a>.</td></tr><tr><td>Composition scripting</td><td align="center">Yes</td><td>Manage <a href="/pages/mPWqVwivCXdnax9p1F8i">user interactions</a> and read data from third-party APIs.</td></tr><tr><td>Data stream</td><td align="center">Yes</td><td>Connect <a href="/pages/6sJFjaLZGAPP2LCJIcRN">data streams</a> via <a href="/pages/WdFEDGdRL5T118LzGkao">composition scripting</a>.</td></tr></tbody></table>

```html
<!DOCTYPE html>
<html>

<head>
  <!-- Include the Singular Graphic JavaSrcipt library -->
  <script src="https://app.singular.live/libs/singulargraphics/1.0.0/singulargraphics.js"></script>
</head>

<body>
  <!-- define iFrame for a non-interactive Singular Player Instance -->
  <iframe id="SingularOverlay" style="width:720px; height: 400px;"
    src="https://app.singular.live/singularplayer/client"></iframe>

  <script>
    // Composition token
    const COMPOSITION_TOKEN = "08EBsfeF4yoYLejOXM51Rk";

    // create graphic object from iframe 'SingularOverlay'
    const overlay = SingularGraphics("SingularOverlay");

    // render composition instance specified by COMPOSITION_TOKEN,
    // the second param is output name, passing undefined to select the first output
    overlay.loadComposition(COMPOSITION_TOKEN, function (isSuccess) {
      if (isSuccess) {
        console.log("Composition loaded");
      } else {
        console.warn("Couldn't load composition");
      }
    });
  </script>
</body>
</html>
```

{% embed url="<https://codepen.io/Singular/pen/abYaNme>" %}

View on [codepen.io](https://codepen.io/Singular/pen/abYaNme).


# Load a composition with its URL

For best performance and quickest loading time, we recommend using the composition URL when publishing an integration.

### **Supported data integrations**

<table><thead><tr><th width="215">Type</th><th width="138.33333333333331" align="center">Support</th><th>Description</th></tr></thead><tbody><tr><td>Control node</td><td align="center">No</td><td>No support for <a href="/pages/E1K0dQQTcGopHY56Irkh">REST API </a>calls.</td></tr><tr><td>Data node</td><td align="center">Yes</td><td>Update data nodes via the <a href="/pages/E1K0dQQTcGopHY56Irkh">REST API</a>.</td></tr><tr><td>Composition scripting</td><td align="center">Yes</td><td>Manage <a href="/pages/mPWqVwivCXdnax9p1F8i">user interactions</a> and read data from third-party APIs.</td></tr><tr><td>Data stream</td><td align="center">Yes</td><td>Connect <a href="/pages/6sJFjaLZGAPP2LCJIcRN">data streams</a> via <a href="/pages/WdFEDGdRL5T118LzGkao">composition scripting</a>.</td></tr></tbody></table>

```html
<!DOCTYPE html>
<html>

<head>
  <!-- Include the Singular Graphic JavaSrcipt library -->
  <script src="https://app.singular.live/libs/singulargraphics/1.0.0/singulargraphics.js"></script>
</head>

<body>
  <!-- define iFrame for a non-interactive Singular Player Instance -->
  <iframe id="SingularOverlay" style="width:720px; height: 400px;"
    src="https://app.singular.live/singularplayer/client"></iframe>

  <script>
    // Composition token
    const COMPOSITION_URL = "https://assets.singular.live/f12f184c9a0eb763beb40478e02a1250/jsons/164gBjjETnHFKEU3LjPCKe.json";

    // create graphic object from iframe 'SingularOverlay'
    const overlay = SingularGraphics("SingularOverlay");

    // render composition instance specified by COMPOSITION_URL,
    // the second param is output name, passing undefined to select the first output
    overlay.loadComposition(COMPOSITION_URL, function (isSuccess) {
      if (isSuccess) {
        console.log("Composition loaded");
      } else {
        console.warn("Couldn't load composition");
      }
    });
  </script>
</body>
</html>
```

{% embed url="<https://codepen.io/Singular/pen/KKoxzqG>" %}

View on [codepen.io](https://codepen.io/Singular/pen/KKoxzqG).


# Get the composition URL of an app instance

You can use an app instance to manage layout modifications in a composition and publish them to the graphics SDK.

**Get URL of the composition loaded into an app instance**

```javascript
const SHARED_APP_TOKEN = "2B50lCTxWNYfoYadq1I2K9";
const apiURL = "https://app.overlays.uno/apiv2/controlapps/" + SHARED_APP_TOKEN;

const requestOptions = {
  method: "GET",
  redirect: "follow"
};
fetch(apiURL, requestOptions)
  .then((response) => response.json())
  .then((result) => {
    console.log(result);
    loadCompositionUrl(result.compositionJson);
  })
  .catch((error) => console.log("error", error));

const loadCompositionUrl = function(compUrl) {
  overlay.loadComposition(compUrl, function (isSuccess) {
    if (isSuccess) {
      console.log("Composition loaded");
    } else {
      console.warn("Couldn't load composition");
    }
  }); 
}
```

**See also**

[Load a composition with its URL](/software-development-kits/graphics-sdk/guides-and-examples/load-a-composition-with-its-url)


# Sequencer VOD example

This example covers the following topics:

* Loading the Singular Graphic SDK
* Loading a composition by its composition URL definition
* Defining a sequence of graphic events
* Mapping video player callbacks (playing, pausing, waiting, ending, seeking) to Singular sequencer commands

{% embed url="<https://codepen.io/Singular/pen/qBoMaaJ>" %}

View on [codepen.io](https://codepen.io/Singular/pen/qBoMaaJ).


# Control local preview of app

This example covers following topics:

* Loading the Singular graphics SDK
* Loading a composition using the composition URL from an app instance
* Sending text to a local instance of an overlay
* Triggering animations in a local instance of an overlay

{% embed url="<https://codepen.io/Singular/pen/mdWxMRB>" %}

View on [codepen.io](https://codepen.io/Singular/pen/mdWxMRB)


# Load app instance output

## Load app instance output <a href="#load-app-instance-output" id="load-app-instance-output"></a>

Load the app instance output to update content in an overlay and trigger animations using an app or the REST API.

### **Supported data integrations**

<table><thead><tr><th width="215">Type</th><th width="138.33333333333331" align="center">Support</th><th>Description</th></tr></thead><tbody><tr><td>Control node</td><td align="center">Yes</td><td>Update control nodes and trigger animations via apps and the <a href="/pages/E1K0dQQTcGopHY56Irkh">REST API</a>.</td></tr><tr><td>Data node</td><td align="center">Yes</td><td>Update data nodes via the <a href="/pages/E1K0dQQTcGopHY56Irkh">REST API</a>.</td></tr><tr><td>Composition scripting</td><td align="center">Yes</td><td>Manage <a href="/pages/mPWqVwivCXdnax9p1F8i">user interactions</a> and read data from third-party APIs.</td></tr><tr><td>Data stream</td><td align="center">Yes</td><td>Connect <a href="/pages/6sJFjaLZGAPP2LCJIcRN">data streams</a> via <a href="/pages/WdFEDGdRL5T118LzGkao">composition scripting</a>.</td></tr></tbody></table>

```html
<!DOCTYPE html>
<html>

<head>
  <!-- Include the Singular Graphic JavaSrcipt library -->
  <script src="https://app.singular.live/libs/singulargraphics/1.0.0/singulargraphics.js"></script>
</head>

<body>
  <!-- define iFrame for a non-interactive Singular Player Instance -->
  <iframe id="SingularOverlay" style="width:720px; height: 400px;"
    src="https://app.singular.live/singularplayer/client"></iframe>

  <script>
    // Singular App Instance 
    const SHARED_APP_TOKEN = "2B50lCTxWNYfoYadq1I2K9";

    // create graphic object from iframe 'SingularOverlay'
    const overlay = SingularGraphics("SingularOverlay");

    // render app instance specified by SHARED_APP_TOKEN,
    // the second param is output name, passing undefined to select the first output
    overlay.renderAppOutput(SHARED_APP_TOKEN, function (isSuccess) {
      if (isSuccess) {
        console.log("App output loaded");
      } else {
        console.warn("Couldn't load app output");
      }
    });
  </script>
</body>
</html>
```

{% embed url="<https://codepen.io/Singular/pen/QWmBzgv>" %}

View on [codepen.io](https://codepen.io/Singular/pen/QWmBzgv).

[Open the control app](https://app.singular.live/control/2B50lCTxWNYfoYadq1I2K9?noHeader=true).


# Overlay SDK

The Overlay SDK is a JavaScript library for integrating Singular overlays into video players.

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/VyHPMtMHzqKhOi5jY8EC">Getting started</a></td></tr><tr><td><a href="/pages/mgJuMd4ni2hMkhalDRgp">SDK functions</a></td></tr><tr><td><a href="/pages/V52pLdbakip8fYeO05NM">Use case examples</a></td></tr></tbody></table>


# Getting started

Include the Singular overlay SDK JavaScript library in your HTML code:

{% code title="Load Overlay SDK" %}

```html
<head>
  <!-- Include the Singular Overlay JavaSrcipt library -->
  <script src="https://app.singular.live/libs/singularoverlay/1.0.0/singularoverlay.js"></script>
</head>
```

{% endcode %}

Add a `div` to your HTML code for the Singular overlay instance:

{% code title="Define Singular Overlay" %}

```html
<body>
  <!-- inside the div the iFrame of the overlay will be created -->
  <div id="SingularOverlay" style="width:720px; height: 400px;"></div>
</body>
```

{% endcode %}


# SDK functions

## SingularOverlay(domElement, options, callback) <a href="#singularoverlaydomelement-options-callback" id="singularoverlaydomelement-options-callback"></a>

Create an instance of a Singular overlay with the following code:

```javascript
var options = {
  class: "iFrameClass",
  endpoint: "http://app.singular.live",
  interactive: true,
  syncGraphics: false,
  showPreloader: false,
  aspect: "",
};

var overlay = SingularOverlay("#DomElement", options, (params) => {
  // callback code goes here
});
```

<table><thead><tr><th width="136">Attributes</th><th width="113">Type</th><th width="115">Required</th><th>Description</th></tr></thead><tbody><tr><td>domId</td><td>String</td><td>Yes</td><td>The target <code>div</code> that the SingularOverlay will replace.</td></tr><tr><td>options</td><td>JSON</td><td>Yes</td><td>Configuration options to define how to render an overlay. See the <a href="#config-option-description">config option description </a>below.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>Callback function called when the overlay object has been created.</td></tr></tbody></table>

## **Config option description**

<table><thead><tr><th width="164">Setting</th><th width="109">Type</th><th width="159">Default</th><th>Description</th></tr></thead><tbody><tr><td>class</td><td>class</td><td>iFrameClass</td><td>Can be used to style the iframe that the overlay SDK creates.</td></tr><tr><td>endpoint</td><td>String</td><td>app.singular.live</td><td>Optional. Can be used to work with different Singular servers.</td></tr><tr><td>delay</td><td>String</td><td>0</td><td>Specified in milliseconds. It tells the overlay SDK to execute commands coming through Firebase later.</td></tr><tr><td>interactive</td><td>Boolean</td><td>true</td><td>Tells the overlay if it should handle mouse events. This will simply set <code>pointer-events: none</code> on the iframe that is created.</td></tr><tr><td>syncGraphics</td><td>Boolean</td><td>false</td><td>Tells the render engine to synchronize graphics to UTC time received by <code>videoSegment</code> calls.</td></tr><tr><td>showPreloader</td><td>Boolean</td><td>false</td><td>Shows or hides the animation indicating that graphics are loading.</td></tr><tr><td>aspect</td><td>String</td><td>empty</td><td>Defines the aspect ratio of the graphics window. When unspecified, the graphics will cover 100% of the iframe.</td></tr></tbody></table>

## **Adaptive config option description**

You can also use adaptive parameters to define a specific adaptive layout define in the composition.

<table><thead><tr><th width="148">Setting</th><th width="100">Type</th><th width="155">Default</th><th>Description</th></tr></thead><tbody><tr><td>screenSize</td><td>Class</td><td>iFrameClass</td><td>Select</td></tr><tr><td>layout</td><td>String</td><td>app.singular.live</td><td></td></tr><tr><td>textDirection</td><td>String</td><td>0</td><td>0 ... left-to-right<br>1 ... right-to-left</td></tr><tr><td>interactive</td><td>Boolean</td><td>false</td><td>true ... propagate interactions to overlay<br>false ... ignore interactions</td></tr><tr><td>language</td><td>String</td><td>Empty</td><td>Language code (<code>en, fr, es, de</code>, ...)</td></tr><tr><td>custom1</td><td>String</td><td>Empty</td><td>Custom Parameter</td></tr><tr><td>custom2</td><td>String</td><td>Empty</td><td>Custom Parameter</td></tr><tr><td>custom3</td><td>String</td><td>Empty</td><td>Custom Parameter</td></tr></tbody></table>

## setContent(content, callback) <a href="#setcontentcontent-callback" id="setcontentcontent-callback"></a>

Loads a Singular graphic into the overlay instance.

<table><thead><tr><th width="136">Attributes</th><th width="164">Type</th><th width="115">Required</th><th>Description</th></tr></thead><tbody><tr><td>content</td><td>String or JSON</td><td>Yes</td><td>Content can be a string or an object and contain one of the values described below.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>The callback function is called when  content has been loaded.</td></tr></tbody></table>

Options to define the content:

<table><thead><tr><th>Setting</th><th>Type</th><th width="110">Default</th><th>Description</th></tr></thead><tbody><tr><td>string</td><td>String</td><td>-</td><td>The composition revision URL.</td></tr><tr><td>compToken</td><td>String</td><td>-</td><td>The compositions token.</td></tr><tr><td>appOutputToken</td><td>String</td><td>-</td><td>The app output token.</td></tr><tr><td>webOutputToken</td><td>String</td><td>-</td><td>The web output token.</td></tr></tbody></table>

**Sample**

```javascript
overlay.setContent(content, (params) => {
  // called when content finished loading
});
```

**Content options**

```javascript
// defining composition revision URL as string
var content = "https://assets.singular.live/f12f184c9a0eb763beb40478e02a1250/jsons/5E2QFRRjiRjN7n4AZUJPzq.json";
// defining composition token as string
var content = "6IX8LkY32V3KACSNvs0MnT";
// defining composition token in content object
var content = { compToken: "6IX8LkY32V3KACSNvs0MnT" };
// defining app output token in content object
var content = { appOutputToken: "6IX8LkY32V3KACSNvs0MnT" };
// defining web output token in content object
var content = { webOutputToken: "49bb0fyieZMUWAmQdbeajI" };
```

Locate the Revision URL in  Singular Composer

![Composition revision URL](http://singular.molden.de/developer-singular-live/assets/screenshots/overlay-sdk-revision-url.jpg)

## addListener(type, callback) <a href="#addlistenertype-callback" id="addlistenertype-callback"></a>

Adds a listener for events coming from the overlay.

<table><thead><tr><th width="182">Attributes</th><th width="93">Type</th><th width="119">Required</th><th>Description</th></tr></thead><tbody><tr><td>type</td><td>String</td><td>Yes</td><td>An object containing one of the values described below.</td></tr><tr><td>callback</td><td>Function</td><td>Yes</td><td>The callback function for the <code>type</code> of event.</td></tr></tbody></table>

**Values for listener `type`**

| Setting | Description                                                                              |
| ------- | ---------------------------------------------------------------------------------------- |
| message | Listens to messages from interactive events and sends messages back to the video player. |
| error   | Listen to errors reported by the overlay instance.                                       |

**Sample: Listen to `message` from interactive events**

```javascript
overlay.addListener("message", (params) => {});
```

**Sample: Listen to `error` reported by the&#x20;*****Overlay Instance***

```javascript
overlay.addListener("error", (params) => {});
```

## setDelay(delay) <a href="#setdelaydelay" id="setdelaydelay"></a>

Defines the delay for the execution of incoming graphics control commands.

<table><thead><tr><th>Attributes</th><th width="102">Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>delay</td><td>Number</td><td>Yes</td><td>Delay in milliseconds.</td></tr></tbody></table>

**Sample**

```javascript
overlay.setDelay(500);
```

## show() <a href="#show" id="show"></a>

Shows the graphics overlay by setting the iFrame visibility to `visible`.

## hide() <a href="#hide" id="hide"></a>

Hides the graphics overlay by setting the iFrame visibility to `hidden`.

## destroy() <a href="#destroy" id="destroy"></a>

Deletes the graphics overlay.

## videoPlaying() <a href="#videoplaying" id="videoplaying"></a>

Notifies the overlay SDK that the video is playing.

## videoPaused() <a href="#videopaused" id="videopaused"></a>

Notifies the overlay SDK that the video is paused.

## videoBuffering() <a href="#videobuffering" id="videobuffering"></a>

Notifies the overlay SDK that the video is buffering.

## videoStopped() <a href="#videostopped" id="videostopped"></a>

Notifies the overlay SDK that the video is stopped.

## videoFinished() <a href="#videofinished" id="videofinished"></a>

Notifies the overlay SDK that the video is finished.

## videoSeeking(time) <a href="#videoseekingtime" id="videoseekingtime"></a>

Notifies the overlay SDK that the video is seeking at a specific time. Time is in seconds.

<table><thead><tr><th>Attributes</th><th width="116">Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>time</td><td>Number</td><td>Yes</td><td>Time in seconds.</td></tr></tbody></table>

**Sample**

```javascript
overlay.videoSeeking(500);
```

## videoSeeked() <a href="#videoseeked" id="videoseeked"></a>

Notifies the overlay SDK that the seeking operation is finished.

## videoTime(time) <a href="#videotimetime" id="videotimetime"></a>

Sends the current time of the video to the overlay SDK.

<table><thead><tr><th>Attributes</th><th width="127">Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>time</td><td>Number</td><td>Yes</td><td>Time in seconds.</td></tr></tbody></table>

**Sample**

```javascript
overlay.videoTime(60.0);
```

## videoMetadata(object) <a href="#videometadataobject" id="videometadataobject"></a>

Sends information about the video. This info can be received in the interactive layer code.

<table><thead><tr><th>Attributes</th><th width="90">Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>object</td><td>object</td><td>Yes</td><td>add description</td></tr></tbody></table>

## videoMuted() <a href="#videomuted" id="videomuted"></a>

The user clicked the mute button in the video player.

## videoUnmuted(value) <a href="#videounmutedvalue" id="videounmutedvalue"></a>

The user clicked the mute button in the video player again.

<table><thead><tr><th>Attributes</th><th width="111">Type</th><th width="103">Required</th><th>Description</th></tr></thead><tbody><tr><td>value</td><td>Number</td><td>Yes</td><td>The volume of the video.</td></tr></tbody></table>

## videoVolume(value) <a href="#videovolumevalue" id="videovolumevalue"></a>

The user changed the volume of the video.

<table><thead><tr><th>Attributes</th><th width="126">Type</th><th width="103">Required</th><th>Description</th></tr></thead><tbody><tr><td>value</td><td>Number</td><td>Yes</td><td>The volume of the video.</td></tr></tbody></table>

**Sample**

```javascript
overlay.videoVolume(value);
```

## videoAdStart(), videoAdFinished(), videoAdSkip() <a href="#videoadstart-videoadfinished-videoadskip" id="videoadstart-videoadfinished-videoadskip"></a>

The video player is playing an advertisement. The interactive layer can then hide certain graphics.

## videoSegment(time) <a href="#videosegmenttime" id="videosegmenttime"></a>

UTC time received from the video that the overlay SDK can use to synchronize graphics to video. The format of time is Unix timestamp.

<table><thead><tr><th>Attributes</th><th width="286">Description</th><th>Type</th><th>Required</th></tr></thead><tbody><tr><td>time</td><td>Time in the format of the UNIX timestamp</td><td>String</td><td>Yes</td></tr></tbody></table>

**Sample**

```javascript
overlay.videoSegment(500);
```

## message(object) <a href="#messageobject" id="messageobject"></a>

Send a message to the interactive layer.

<table><thead><tr><th width="140">Attributes</th><th width="103">Type</th><th width="127">Required</th><th>Description</th></tr></thead><tbody><tr><td>object</td><td>object</td><td>Yes</td><td>Custom data as JSON object</td></tr></tbody></table>

**Sample**

```javascript
overlay.message(object);
```


# Use case examples

### Send notifications from the overlay SDK to a composition script <a href="#send-notifications-from-the-overlay-sdk-to-the-composition-script" id="send-notifications-from-the-overlay-sdk-to-the-composition-script"></a>

This example uses video.js callbacks to listen to specific player events. The Overlay SDK sends a notification to the composition script when an event occurs. The composition script then updates control nodes in the composition to trigger the indicators.

[Contact the Singular support team](/support/support-resources#developer-support) for a copy of the composition.

Use the player controls and watch the status indicators changing.

{% embed url="<https://codepen.io/Singular/pen/dyoXjZW?editors=1010>" %}

### Send messages from composition scripts to the overlay SDK <a href="#send-messages-from-composition-script-to-the-overlay-sdk" id="send-messages-from-composition-script-to-the-overlay-sdk"></a>

This example shows how to receive messages from a composition script to the overlay SDK.

[Contact the Singular support team](/support/support-resources#developer-support) for a copy of the composition.

Click on the interactive icons build within Singular to play the according clip in the video.js player.

{% embed url="<https://codepen.io/Singular/pen/NWqrBgO?editors=1010>" %}


# Widget SDK

The Singular Widget SDK is a JavaScript library that allows you to develop your own widgets and extend Composer's functionality.

A widget is like a small web page hosted in an iframe that can connect to external data sources and manage and update its HTML elements. Using this SDK, you can write code in JavaScript, HTML, and CSS and leverage your preferred libraries to build powerful and intuitive widgets.

The Singular Widget SDK provides classes and methods to manage initialization, data updates, and animation states.

### What can you do with Singular widgets? <a href="#what-can-singular-apps-do" id="what-can-singular-apps-do"></a>

Singular widgets are versatile. Among other things, you can use them to:

* Visualize content
* Access external data sources and data service&#x20;
* Manage instances of widget compositions
* Define internal animation logic
* Send and receive messages to a composition script

{% hint style="info" %}
If you're new to JavaScript and web development, check out these [resources](/support/support-resources#javascript) to learn basic concepts and techniques.
{% endhint %}


# Preparing your environment

### Prerequisites <a href="#prerequisite" id="prerequisite"></a>

The Singular Widget SDK requires a local installation of Node.js. Download it from <https://nodejs.org/en/>.

{% hint style="info" %}
This document describes how to install singular-cli. For details about Singular's other CLIs, singularwidget-cli and singularapp-cli, see <https://github.com/singularlive/> and <https://www.npmjs.com/~singularlive>.
{% endhint %}

### Installing the Singular CLI <a href="#install-singular-cli" id="install-singular-cli"></a>

After installing Node.js, open a console and install `singular-cli` by running this command:

```bash
npm install singular-cli -g
```

After the installing the CLI, console commands to create and deploy Singular widgets and apps will become available.

The `create` commands serve to clone the Singular boilerplates into a specified folder.

The `deploy` commands check, package, and upload your widget or app code to Singular.live. Every widget and app is identified by a unique deploy key. Generating a deploy key is covered [here](/software-development-kits/widget-sdk/getting-started#deploying-an-app).

**Create Singular widget boilerplate**

```bash
singular createwidget <folder name>
```

**Deploy a Singular widget**

```bash
singular deploywidget <folder name>
```

For more information about the Singlar.live CLI see <https://github.com/singularlive/singular-cli>.


# Getting started

Follow these steps to create a Singular widget:

1. [Download the widget boilerplate](#download-the-app-boilerplate)
2. [Create a widget in your Singular account](#creating-an-app-template-in-your-account)
3. [Deploy your widget code](#deploying-an-app)
4. [Test and debug your widget](#testing-and-debugging-an-app)
5. [Publish your widget](#publishing-an-app)
6. Share your widget

### 1. Download the widget boilerplate <a href="#download-the-app-boilerplate" id="download-the-app-boilerplate"></a>

Open a console and enter following commands:

```bash
C:\Users\tm\Singular Widgets>singular createwidget myWidget
Singular Widget – myWidget has been created
C:\Users\tm\Singular Widgets>dir /S /B
C:\Users\tm\Singular Widgets\myWidget
C:\Users\tm\Singular Widgets\myWidget\deploykey.json
C:\Users\tm\Singular Widgets\myWidget\source
C:\Users\tm\Singular Widgets\myWidget\source\icon.png
C:\Users\tm\Singular Widgets\myWidget\source\output.html
```

This set of commands creates a folder called **myWidget** on your local disk and clones the widget boilerplate into it. We recommend adding this folder to your source code management tool.

**Description of default files**

<table><thead><tr><th width="284">File</th><th>Description</th></tr></thead><tbody><tr><td><code>deploykey.json</code></td><td>A file containing the widget deploy key.</td></tr><tr><td><code>source\output.html</code></td><td>The widget's source code.</td></tr><tr><td><code>source\icon.png</code></td><td>The widget icon in the .png format.</td></tr></tbody></table>

#### output.html boiler plate <a href="#apphtml-example-for-deployed-use" id="apphtml-example-for-deployed-use"></a>

```html
<!DOCTYPE html>
<html>

<head>
    <style>
        /* The default widget style to make output match the bounding box */
        html,
        body {
            background: none;
            margin: 0;
            padding: 0;
            height: 100%;
        }

        /* Add your custom styles here */
    </style>
</head>

<body style="margin: 0;	background:none; overflow: hidden;">
    <div id='container' style="position: absolute; left:0%; top:0%; width: 100%; height: 100%"></div>

    <!-- Your custom html goes here -->

    <!-- Singular widget library -->
    <script src="https://app.singular.live/libs/singularwidget/1.0.4/singularwidget.js"></script>
    <script>
        let windowWidth = 0;
        let windowHeight = 0;
        let compositionInstance = null;

        /**
         * Initialize a Singular widget object and define its callback functions
         */
        SingularWidget.init({
            onInit: onSingularInit,
            onValue: onSingularValue,
            onButtonClicked: onSingularButtonClicked,
            onEditComp: onSingularEditComp,
            onAnimation: onSingularAnimation
        });

        /**
         * addEventListener for window resizing
         * Relevant when your widget displays HTML content or uses widget compositions
         */
        window.addEventListener("resize", function() {
            if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
                windowWidth = window.innerWidth;
                windowHeight = window.innerHeight;
                if (compositionInstance) {
                    compositionInstance.resize(windowWidth, windowHeight);
                }
            }
        });

        /**
         * onSingularInit()
         * Called when the widget instance is created
         */
        function onSingularInit(params) {
            console.log("onSingularInit() - params =", params);
            windowWidth = window.innerWidth;
            windowHeight = window.innerHeight;
        }

        /**
         * onSingularValue()
         * Called when the widget instance is created or the instance data has changed
         */
        function onSingularValue(json) {
            console.log("onSingularValue() - json =", json);
        }

        /**
         * onSingularButtonClicked()
         * Called when a push button of the widget is clicked
         */
        function onSingularButtonClicked(button) {
            console.log("onSingularButtonClicked() - button =", button);
        }

        /**
         * onSingularAnimation()
         * Called when the animation state of the widget changes
         */
        function onSingularAnimation(anim) {
            console.log("onSingularAnimation() - anim =", anim);
            if (!compositionInstance) return;
            switch (anim.event) {
                case "start":
                    break;
                case "stop":
                    break;
                case "jump":
                    break;
                case "seek":
                    break;
                case "init":
                    break;
            }
        }

        /**
         * onSingularEditComp()
         * Called when the subcomposition of a widget is edited
         */
        function onSingularEditComp(composition) {
            console.log("onSingularEditComp() - composition =", composition);
        }
    </script>
</body>

</html>
```

### 2. Create a widget template in your Singular account <a href="#creating-an-app-template-in-your-account" id="creating-an-app-template-in-your-account"></a>

#### 1. Log into Singular.live and check your permissions <a href="#log-into-singularlive" id="log-into-singularlive"></a>

To create a Singular.live widget, you need an account that has developer permissions. Check your permissions by navigating to the Singular Dashboard, selecting the **User Menu** > **Settings,** and checking your **User Settings**.

**User Administration** and **Developer Tools** should be switched on.

<figure><img src="/files/2kaY4H5lXeIJKtlDTQy1" alt=""><figcaption><p>Checking your account's developer permissions</p></figcaption></figure>

#### 2. Create a new widget <a href="#create-app" id="create-app"></a>

Also on the Singular Dashboard:

1\. Select the **User Menu** > **Widget Manager,** and then **New Widget** to create a new widget.

<figure><img src="/files/7lg1oGOwQakx26OfwXo5" alt=""><figcaption><p>Creating a new widget</p></figcaption></figure>

2\. Enter a name and category for your widget.&#x20;

The widget name doesn't need to match the name of the folder used in the `singular createwidget` command in [step one](#download-the-app-boilerplate). Nevertheless, we recommend using the same name.

### 3. Deploy your widget code <a href="#deploying-an-app" id="deploying-an-app"></a>

Continuing in the Widget Manager:

1\. Copy the **Deploy Key** from the Widget Details and paste it into the `deploykey.json` file from [step one](#download-the-app-boilerplate).&#x20;

<figure><img src="/files/h741oX2Z4VV5SDKZwYzE" alt=""><figcaption><p>Copying the deploy key</p></figcaption></figure>

The file should look like this:

{% code title="deploykey.json" %}

```javascript
{
  "deploykey": "uS3bDUabcDnYlAZMO87OL2nBckvSIdmE"
}
```

{% endcode %}

2\. Upload the widget code to Singular using following command in the Node.js console:

```log
C:\Users\tm\Singular Widgets> singular deploywidget myWidget
-----------------------------------------------
Singular.Live widget deploy
Validating files in directory "source"
Creating zip file
Deploying widget to Singular.Live
Widget ID: 4215 successfully deployed

C:\Users\tm\Singular Widgets>
```

The deploy script will read the deploy.json file, extract the deploy key, create a zip-file of the widget sources, and upload it to the Singular.live Cloud. Your widget then will be visible in the **Widget Browser** when you create a new widget instance.

{% hint style="info" %}
Activate the `Show Dev Versions` checkbox in the **Widget Browser** to access the development version of your widget.
{% endhint %}

The status of the `Development` version of your widget gets updated every time you deploy new sources for your widget. Refresh the **Widget Manager** and select your widget to see to see its updated status.

### 4. Define your widget UI

Define the widget UI in the **UI Definition** field.

<figure><img src="/files/eEh9uYqPUmTZVhqrjHbg" alt=""><figcaption><p>Copy your widget UI definition</p></figcaption></figure>

### 5. Test and debug your widget <a href="#testing-and-debugging-an-app" id="testing-and-debugging-an-app"></a>

Before you publish your widget, use Chrome's developer tools to debug and optimize it.

### 6. Publish your widget <a href="#publishing-an-app" id="publishing-an-app"></a>

When you're done testing your widget, use the `publish` function to make it available for other users.

The `publish` function creates an identical copy of the development version of your widget and tags it as `published`. When you do this, the development version gets increased by one.

Widgets never get deleted from the Singular.live platform. They are always in one of the following states:

* **development**: Every new widget defaults to the `development` status. Widgets under development and can only be accessed by users with development permissions. Once your widget has been fully developed and tested, you can `publish` it to make it available to other users. Widgets with `development` status can be `published`.
* **published**: A published widget is available to all members of your account. Only one `published` version of a widget can exist at the same time. By default, the latest published version will be used when creating an instance of a widget. Widgets with `published` status can be `un-published`.
* **archived**: Widgets automatically get archived when a new version is published. Existing widget instances that use an older widget version will stay on the older version. A standard user can manually update the widget in the widget instance by selecting the **Update** button. Users with developer permissions can also use any archived version of an widget. Widgets with `archived` status can be `published` and `depreciated`.
* **depreciated**: Set a widget to `depreciated` status when you want or need to force a widget instance to upgrade to the latest published version of an widget. A widget with `depreciated` status can be `archived`.

#### Overview of widget statuses <a href="#overview-of-app-statuses" id="overview-of-app-statuses"></a>

|    Status   |   Action   |  New status |
| :---------: | :--------: | :---------: |
| development |   Publish  |  Published  |
|  published  | Un-publish |   Archived  |
|   archived  |   Publish  |  Published  |
|             | Depreciate | Depreciated |
| depreciated |   Archive  |   Archived  |

### 7. Share your widget (optional) <a href="#publishing-an-app" id="publishing-an-app"></a>

If you'd like to share your widget with another Singular user, enter their Singular account name in the **Share with** field in the Widget Manager.

<figure><img src="/files/fDa8Ie1TveLTmhZBGsVs" alt=""><figcaption><p>Sharing a widget</p></figcaption></figure>


# Reference

The reference documentation provided in the following sections describes the Widget SDK structure and methods, and the widget UI definition.

The Widget SDK returns an object with the following properties and methods:

{% code title="Debug Console" overflow="wrap" %}

```bash
SingularWidget {init: ƒ, getStyle: ƒ, instantiate: ƒ, sendCustomMessage: ƒ}
> compositionId: "-NIqFVJ1rRnNsiUQJing"
> createTimeControl: ƒ ()
> init: ƒ (hash)
> instanceId: undefined
> instantiate: ƒ (compName, domTarget, instantiateCallback)
> sendCustomMessage: ƒ (obj)
> widgetId: 4215
> widgetVersion: 1
```

{% endcode %}

## Properties

The widget object has the following properties:

<table><thead><tr><th width="157">Name</th><th width="96.33333333333331">Type</th><th width="224">Example</th><th width="236">Description</th></tr></thead><tbody><tr><td>compositionId</td><td>String</td><td>"-NIqFVJ1rRnNsiUQJing"</td><td>The ID of a (sub)composition.</td></tr><tr><td>instanceId</td><td>String</td><td></td><td>The ID of a widget composition instance.</td></tr><tr><td>widgetId</td><td>Number</td><td>4215</td><td>The ID of a widget template.</td></tr><tr><td>widgetVersion</td><td>Number</td><td>1</td><td>The version of a widget.</td></tr></tbody></table>

## Methods

The widget object has the following methods:

<table><thead><tr><th width="249.33333333333331">Method</th><th width="179">Return type</th><th>Description</th></tr></thead><tbody><tr><td><a href="#createtimecontrol">createTimeControl()</a></td><td>Object</td><td>Creates a Singular timer object.</td></tr><tr><td><a href="#init">init()</a></td><td>-</td><td>Registers callback functions for <code>onInit</code>, <code>onValue</code>, <code>onButtonClicked</code>, <code>onEditComp</code>, and <code>onAnimation</code> events.</td></tr><tr><td><a href="#instantiate">instantiate()</a></td><td>-</td><td>Creates an instantiate of a widget composition.</td></tr><tr><td><a href="#sendcustommessage">sendCustomMessage()</a></td><td>-</td><td>Sends a custom message to a composition script.</td></tr></tbody></table>

### createTimeControl()

Returns a Singular timer control object.&#x20;

Use `createTimeControl()` to manage timers and synchronize them globally in all composition and output instances.&#x20;

#### Parameter

`none`

#### Return

`object`: Time control object

{% code overflow="wrap" %}

```bash
timeControl: {tc: {…}, allowBackwardsJump: false, roundToInterval: true, intervalID: null, timeoutID: null, …}
> allowBackwardsJump: false
> getCurrentTime: ƒ ()
> intervalID: null
> intervalTime: 1000
> offsetToServerTime: 0
> oldRunningTime: 0
> roundToInterval: true
> setAllowBackwardsJump: ƒ (v)
> setIntervalTime: ƒ (time)
> setOffsetToServerTime: ƒ (time)
> setRoundToInterval: ƒ (v)
> setTimeControl: ƒ (tc)
> setUpdateCallback: ƒ (callback)
> tc: {UTC: 0, isrunning: false, value: 0}
> timeoutID: null
> updateCallback: null
```

{% endcode %}

#### See also

[Time control object](/software-development-kits/widget-sdk/reference/time-control-object)

### init()

Initializes and registers callback functions.

```javascript
/**
 * initialize the Singular widget object and define callback functions
 */
SingularWidget.init({
    onInit: onSingularInit,
    onValue: onSingularValue,
    onButtonClicked: onSingularButtonClicked,
    onEditComp: onSingularEditComp,
    onAnimation: onSingularAnimation
});
```

#### Parameter

The `init()` function defines callback functions for events in a JSON object.

<table><thead><tr><th width="207.33333333333331">Event</th><th>Description</th></tr></thead><tbody><tr><td><code>onInit</code></td><td>Called when loading a composition, adding a widget, or closing a widget composition.</td></tr><tr><td><code>onValue</code></td><td>Called when changing a value in a widget UI field.</td></tr><tr><td><code>onButtonClicked</code></td><td>Called when clicking a button in a widget UI.</td></tr><tr><td><code>onEditComp</code></td><td>Called when starting to edit a widget composition. Creates and initializes widget nodes in this callback.</td></tr><tr><td><code>onAnimation</code></td><td>Called when a widget supports custom widget animations and a user selected using the widget animation in the animation timeline.</td></tr></tbody></table>

#### Also see

[Boilerplate example](/software-development-kits/widget-sdk/guides-and-examples/widget-example-css-patterns)

#### Return

`none`

### instantiate()

Creates an instance of a composition and initializes it. This function is only required when a widget supports widget compositions.&#x20;

```javascript
// The code below creates a new composition instance
// compositionId returned by the onValue() callback
const domElement = document.getElementById("container");

// check if compositionInstance exists
if (compInstance != null) {
  compInstance.destroy();
  compInstance = null;
}

SingularWidget.instantiate(compositionId, domElement, function cb(comp) {
  // returns a composition instance
  compInstance = comp;
});
```

#### Parameter

<table><thead><tr><th width="178.33333333333331">Name</th><th width="124">Type</th><th>Description</th></tr></thead><tbody><tr><td>compositionID</td><td>string</td><td>A Composition ID returned by the onValue() function.</td></tr><tr><td>domElement</td><td>html</td><td>An HTML dom element.</td></tr><tr><td>cb</td><td>function</td><td>The callback function.</td></tr></tbody></table>

#### Return

`object`: Composition Instance

{% code overflow="wrap" %}

```bash
compInstance: {onAirRefId: '56344bcd-9b83-43e2-863b-a5e5fde60c4f', getDuration: ƒ,  is2Timeline: ƒ, …}
> animate: ƒ (t)
> destroy: ƒ ()
> getDuration: ƒ ()
> is2Timeline: ƒ ()
> jumpTo: ƒ (t)
> onAirRefId: "56344bcd-9b83-43e2-863b-a5e5fde60c4f"
> playTo: ƒ (t,i)
> resize: ƒ (t,e)
> seek: ƒ (t,i)
> setControlNode: ƒ (t)
> setWidgetNode: ƒ (t)
> stop: ƒ () -> ask Khing
```

{% endcode %}

#### See also

[Composition instance](/software-development-kits/widget-sdk/reference/composition-instance)

### sendCustomMessage()

Sends a custom message to the composition script. Registers a message listener in composition script to receive and process the message object.

```javascript
// define custom message object
const message = {
    "type": "json",
    "timestamp": Date.now(),
    "tableJson": jsonData
};
// send custom message object
SingularWidget.sendCustomMessage(message);
```

#### Parameter

<table><thead><tr><th width="126.33333333333331">Name</th><th width="249">Type</th><th>Description</th></tr></thead><tbody><tr><td>message</td><td>string, number, object</td><td>A message object.</td></tr></tbody></table>

#### Return

`none`


# Widget UI definition

The widget UI definition contains **field** and **group definitions** in a JSON structure.

## Defining UI fields

The minimal definition of a fields includes the `id`, `type`, `title`, and `defaultValue`.

<table><thead><tr><th width="227">Field model property</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>string</td><td><strong>(required)</strong> - field id returned in the JSON data by onSingularValue()</td></tr><tr><td>type</td><td>string</td><td><strong>(required)</strong> - type of data (<code>infotext</code>, <code>text</code>, <code>textarea</code>, <code>number</code>, <code>checkbox</code>, <code>composition</code>, <code>button</code>, <code>selection</code>, <code>JSON</code>, <code>color</code>, <code>gradient</code>, <code>image</code>, <code>timecontrol</code>)</td></tr><tr><td>title</td><td>string</td><td>Field title displayed in the widget UI</td></tr><tr><td>defaultValue</td><td>string</td><td>Default value</td></tr></tbody></table>

{% code title="UI field definition" %}

```json
{
  "model": {
    "fields": [
      {
        "id": "textId",
        "type": "text",
        "title": "Single line text",
        "defaultValue": "enter text here ..."
      },
      {
        "id": "numberId",
        "type": "number",
        "title": "Number",
        "defaultValue": "50",
        "min": "0",
        "max": "100",
        "step": "0.1",
        "unit": "%",
        "format": "0.1"
      }
    ],
    "groups": [...]
  }
}
```

{% endcode %}

## Grouping UI fields

Use **groups** to arrange UI fields in cards.&#x20;

**Group properties:**

<table><thead><tr><th width="142">Field</th><th width="164">Type</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>string</td><td><strong>(required)</strong> - group id</td></tr><tr><td>title</td><td>string</td><td>Group title</td></tr><tr><td>width</td><td>string</td><td>Width of card <code>single</code> (default) or <code>double</code></td></tr><tr><td>toolTip</td><td>string</td><td>Tool tip trext</td></tr><tr><td>childIds</td><td>array of strings</td><td>Array of UI field ids</td></tr></tbody></table>

{% code title="Group definition" overflow="wrap" %}

```json
{
  "model": {
    "fields": [...],
    "groups": [
      {
        "id": "groupId",
        "title": "Input Options",
        "width": "double",
        "toolTip": "Enter your values",
        "childIds": [
          "textId",
          "numberId"
        ]
      }
    ]
  }
}
```

{% endcode %}

## Widget UI definition - full reference&#x20;

```json
{  
  "model": {
    "fields": [{
        "id": "infotextId",
        "type": "infotext",
        "title": "Info",
        "defaultValue": "this is a readonly text..."
      },
      {
        "id": "textId",
        "type": "text",
        "title": "Single line text",
        "defaultValue": "enter text here ..."
      },
      {
        "id": "textAreaId",
        "type": "textarea",
        "title": "Text Area",
        "defaultValue": "Enter text into this area...",
        "rows": "5",
        "cols": "15"
      },
      {
        "id": "numberId",
        "type": "number",
        "title": "Number",
        "defaultValue": "50",
        "min": "0",
        "max": "100",
        "step": "0.1",
        "unit": "%",
        "format": "0.1"
      },
      {
        "id": "checkboxId",
        "type": "checkbox",
        "title": "Enable",
        "defaultValue": false
      },
      {
        "id": "compositionId",
        "type": "composition",
        "title": "Composition",
        "defaultValue": ""
      },
      {
        "id": "buttonId",
        "type": "button",
        "title": "Click me",
        "defaultValue": ""
      },
      {
        "id": "selectionId",
        "type": "selection",
        "title": "Selection",
        "defaultValue": "0",
        "selections": [{
            "id": "0",
            "title": "Option 1"
          },
          {
            "id": "1",
            "title": "Option 2"
          },
          {
            "id": "2",
            "title": "Option 3"
          }
        ]
      },
      {
        "id": "jsonId",
        "type": "json",
        "title": "Json data",
        "defaultValue": "{}",
        "width": "350",
        "height": "200"
      },
      {
        "id": "useCustomParameterId",
        "type": "checkbox",
        "title": "Enable",
        "defaultValue": false
      },
      {
        "id": "colorId",
        "type": "color",
        "title": "Color",
        "defaultValue": "#9B9B9B"
      },
      {
        "id": "gradientId",
        "type": "gradient",
        "title": "Gradient",
        "defaultValue": "#9B9B9B"
      },
      {
        "id": "imageId",
        "type": "image",
        "title": "Image",
        "defaultValue": "https://app.singular.live/images/logo.png"
      }, {
        "id": "countdownControlId",
        "title": "TimeControl",
        "type": "timecontrol",
        "defaultValue": {
          "UTC": 0,
          "isRunning": false,
          "value": 0
        }
      }
    ],
    "groups": [{
        "id": "group1Id",
        "title": "Input Options",
        "width": "double",
        "toolTip": "Enter your values and define a custom widget composition",
        "childIds": ["infotextId", "textId", "textAreaId", "numberId", "checkboxId", "selectionId", "compositionId"]
      },
      {
        "id": "group2Id",
        "title": "Customize and Control Options",
        "width": "double",
        "toolTip": "Customize and control options",
        "activeId": "useCustomParameterId",
        "childIds": ["jsonId", "colorId", "gradientId", "imageId", "countdownControlId", "buttonId"]
      }
    ]
  }
}
```


# Widget callback functions

## onSingularInit()

{% code title="Debug Console" overflow="wrap" %}

```log
onSingularInit(): {OffsetToServerTime: -589.5, adaptationGlobals: {…}}
> OffsetToServerTime: -589.5
> adaptationGlobals: {}
```

{% endcode %}

## onSingularValue()

{% code title="Debug Console" overflow="wrap" %}

```log
onSingularValue(): {idLabel: ' ', idText: 'enter text here ...', idTextArea: 'Enter text into this area...', idNumber: '50.0', idCheckbox: false, …}
> idCheckbox: false
> idColor: {r: 155, g: 155, b: 155, a: 1}
> idComposition: "325724b5-03b8-46cf-93cb-16a2587dfb21"
> idGradient: "#9B9B9B"
> idImage: "https://app.singular.live/images/logo.png"
> idJson: "{}"
> idLabel: " "
> idNumber: "50.0"
> idSelection: "1"
> idText: "enter text here ..."
> idTextArea: "Enter text into this area..."
> idUseCustomParameter: false
```

{% endcode %}

## onSingularButtonClicked()

{% code title="Debug Console" overflow="wrap" %}

```log
onSingularButtonClicked(): idButton
```

{% endcode %}

## onSingularEditComp()

{% code title="Debug Console" overflow="wrap" %}

```log
onSingularEditComp(): idComposition
```

{% endcode %}

## onSingularAnimation()

{% code title="Debug Console" overflow="wrap" %}

```log
onSingularAnimation(): {event: 'init', timeline: 'In', duration: 0.5, animationEvent: 'play', direction: 'forward', …}
> animationEvent: "play"
> direction: "forward"
> duration: 0.5
> easing: {easing: 'linear', inOut: 'out'}
> event: "init"
> newState: "In"
> oldState: "Out1"
> state: "In"
> timeline: "In"
```

{% endcode %}


# Time control object

The `createTimeControl()` function returns a time control object to manage clocks, timers, and countdowns.

{% code title="Create time control object" %}

```javascript
// This code prints the time control object to the console
const timeControl = SingularWidget.createTimeControl();
console.log(timeControl);
```

{% endcode %}

#### Return

`object`: Time control object

{% code overflow="wrap" %}

```bash
timeControl: {tc: {…}, allowBackwardsJump: false, roundToInterval: true, intervalID: null, timeoutID: null, …}
> allowBackwardsJump: false
> getCurrentTime: ƒ ()
> intervalID: null
> intervalTime: 1000
> offsetToServerTime: 0
> oldRunningTime: 0
> roundToInterval: true
> setAllowBackwardsJump: ƒ (v)
> setIntervalTime: ƒ (time)
> setOffsetToServerTime: ƒ (time)
> setRoundToInterval: ƒ (v)
> setTimeControl: ƒ (tc)
> setUpdateCallback: ƒ (callback)
> tc: {UTC: 0, isrunning: false, value: 0}
> timeoutID: null
> updateCallback: null
```

{% endcode %}

## Properties

<table><thead><tr><th width="212">Name</th><th width="108">Type</th><th>Example</th><th width="257">Description</th></tr></thead><tbody><tr><td>allowBackwardsJump</td><td>Boolean</td><td><code>false</code> (default)</td><td>Allows the timer to jump backward due to network latency when restarting or continue playing a paused timer.</td></tr><tr><td>intervalID</td><td>Integer</td><td></td><td>The interval ID (internal).</td></tr><tr><td>intervalTime</td><td>Integer</td><td>10, 100, 1000</td><td>Interval time in [ms].</td></tr><tr><td>offsetToServerTime</td><td>Integer</td><td></td><td>The time offset of a local browser to Singular server time.</td></tr><tr><td>roundToInterval</td><td>Boolean</td><td><code>true</code> (default)</td><td>Rounds time to the next interval.</td></tr><tr><td>tc</td><td>Object</td><td><code>{</code><br>  <code>"UTC": 0,</code><br>  <code>"isRunning": false,</code><br>  <code>"value": 0</code><br><code>}</code></td><td>The time control object.<br>  UTC: UTC start time [ms];<br>  isRunning: boolean;<br>  value: offset in [ms]</td></tr><tr><td>timeoutID</td><td>Integer</td><td></td><td>The timeout ID (internal).</td></tr><tr><td>updateCallback</td><td>Function</td><td></td><td>A callback function triggered  at every interval.</td></tr></tbody></table>

## Methods

### getCurrentTime()

Returns the current running time of the timer in \[ms].

```javascript
// This code prints the current running time to the console
const currTime = timeControl.getCurrentTime();
console.log("currTime =", currTime);
```

#### Return

&#x20;`integer`: time in \[ms]

```bash
currTime = 1234
```

### setAllowBackwardsJump()

Due to a slow internet connection and/or network latency, clocks can jump backwards when starting, pausing, or restarting. Use the `setAllowBackwardsJump()` call to set this behavior.

#### Parameter

<table><thead><tr><th width="217">Name</th><th width="116.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>allowBackwardsJump</td><td>Boolean</td><td><code>false</code>: (default) don't allow backwards jump<br><code>true</code>: allow backwards jump</td></tr></tbody></table>

```javascript
// This code avoids backwards jumps
timeControl.allowBackwardsJump(false);
```

#### &#x20;Return

`none`

### setIntervalTime()

Sets the interval frequency for the timer.

#### Parameter

<table><thead><tr><th width="113.33333333333331">Name</th><th width="127">Type</th><th>Description</th></tr></thead><tbody><tr><td>tInterval</td><td>Number</td><td>The interval duration in [ms].</td></tr></tbody></table>

```javascript
// This code sets the interval frequency to 1000 [ms]
const tInterval = 1000;
timeControl.setIntervalTime(tInterval );
```

#### Return

`none`

### setOffsetToServerTime()

Sets the offset from the local browser to the Singular server time.

#### Parameter

<table><thead><tr><th width="108.33333333333331">Name</th><th width="102">Type</th><th>Description</th></tr></thead><tbody><tr><td>tOffset</td><td>Number</td><td>Offset from the local browser time to the Singular server time.</td></tr></tbody></table>

```javascript
// This code sets the offset to the Singular server time in [ms]
const tOffset = 876;
timeControl.setOffsetToServerTime(tOffset);
```

#### Return

`none`

### setRoundToInterval()

Rounds the time to the interval frequency.

#### Parameter

<table><thead><tr><th width="153.33333333333331">Name</th><th width="125">Type</th><th>Description</th></tr></thead><tbody><tr><td>doRounding</td><td>Boolean</td><td><code>true</code>: (default) round time to interval frequency<br><code>false</code>: do not round time</td></tr></tbody></table>

```javascript
// This code sets the round-to-interval flag
timeControl.setRoundToInterval(true);
```

#### Return

`none`

### setTimeControl()

The time control object defines the current timer state.

```javascript
// This code shows examples of the time control object
const tStart = Date.now();
const tcStart = {
  "UTC": tStart,     // set start time to current UTC time in [ms]
  "isRunning": true, // start timer
  "value": 0         // offset is 0
};

// .... later
const tPause = Date.now();
const tcPause = {
  "UTC": tPause,             // set start time to current UTC time in [ms]
  "isRunning": false,        // start false
  "value": tcPause - tcStart // offset is 0
};

// .... even later
const tcContinue = {
  "UTC": Date.now(),         // set start time to current UTC time in [ms]
  "isRunning": true,         // start false
  "value": tcPause - tcStart // offset is 0
};
```

#### Parameter

<table><thead><tr><th width="153.33333333333331">Name</th><th width="162">Type</th><th>Description</th></tr></thead><tbody><tr><td>tc</td><td>object</td><td>The time control object.</td></tr></tbody></table>

```javascript
// This code starts the timer
timeControl.setTimeControl(tcStart)(;
```

#### Return

`none`

### setUpdateCallback()

Sets the time control callback function. This function is called at every interval.

#### Parameter

<table><thead><tr><th width="155.33333333333331">Name</th><th width="128">Type</th><th>Description</th></tr></thead><tbody><tr><td>cb</td><td>function</td><td>The callback function.</td></tr></tbody></table>

```javascript
// The code below updates the timer callback function
timeControl.setUpdateCallback(() => {
  console.log("elapsed time = %d[ms]", timeControl.getCurrentTime());
  // add your code here ...
});
```

#### Return

`none`

```javascript
let tControl = {
    UTC: 0,
    isRunning: false,
    value: value
};

// The code below creates a timecontrol object
const timeControl = SingularWidget.createTimeControl();
// set update callback function
timeControl.setUpdateCallback(updateCallback);
// this will stop the clock from jumping backwards when it is stopped. A jump
// backwards can happen due to network latency
timeControl.setAllowBackwardsJump(false);
// round the time to the interval frequency
timeControl.setRoundToInterval(true);
// tell the timer control object how often to update the callback
timeControl.setIntervalTime(100); //milliseconds

timeControl.setTimeControl(tControl); // todo...

const updateCallback = () => {
  // get current timer value
  let currentTime = timeControl.getCurrentTime();
  console.log("currentTime = %d[ms]", currentTime);
}

```


# Composition instance

{% code overflow="wrap" %}

```log
compInstance: {onAirRefId: '56344bcd-9b83-43e2-863b-a5e5fde60c4f', getDuration: ƒ,  is2Timeline: ƒ, …}
> animate: ƒ (t)
> destroy: ƒ ()
> getDuration: ƒ ()
> is2Timeline: ƒ ()
> jumpTo: ƒ (t)
> onAirRefId: "56344bcd-9b83-43e2-863b-a5e5fde60c4f"
> playTo: ƒ (t,i)
> resize: ƒ (t,e)
> seek: ƒ (t,i)
> setControlNode: ƒ (t)
> setWidgetNode: ƒ (t)
> stop: ƒ ()
```

{% endcode %}

## Properties

<table><thead><tr><th width="145.33333333333331">Name</th><th width="95">Type</th><th width="180">Example</th><th width="294">Description</th></tr></thead><tbody><tr><td>onAirRefId</td><td>String</td><td>"56344bcd-9b83-43e2-863b-a5e5fde60c4f"</td><td>The ID of the sub-composition that contains the widget.</td></tr></tbody></table>

## Methods

### animate()

Animates a composition instance using the event object(t) produced by an [onAnimation ](broken://pages/lbVM6qfRVExav9sbmKGj#onsingularanimation)callback.

### destroy()

Destroys a composition instance.

```javascript
// The code below destroys a composition instance

// check if composition instance exists
if (compInstance!= null) {
  compInstance.destroy();
  compInstance= null;
}
```

#### Return

`none`

### getDuration()

Returns the duration of the **In** and **Out** animation timelines.

```javascript
// The code below prints the animation durations to the console
const animDuration = compInstance.getDuration();
console.log("animDuration =", animDuration);
```

#### Return

`object`: JSON object&#x20;

```bash
animationDuration = {
  In: 1.3,
  Out: 0.5
}
```

### is2Timeline()

Returns if a composition instance has one or two timelines.

```javascript
// The code below prints the return value
const has2Timelines = compInstance.is2Timeline();
console.log("has2Timelines =", has2Timelines);
```

#### Return

`boolean` : `false` , has single timeline; `true`, has two timelines

```bash
has2Timelines = true
```

### jumpTo()

Jumps to a specified animation state.&#x20;

#### Parameter

<table><thead><tr><th width="123.33333333333331">Name</th><th width="102">Type</th><th>Description</th></tr></thead><tbody><tr><td>toState</td><td>String</td><td><code>In</code>: jump to "In" state<br><code>Out</code>: jump to "Out" state</td></tr></tbody></table>

```javascript
const toState = "In";
compInstance.jumpTo(toState);
```

### playTo()

Plays the animation to the specified state.

#### Parameter

<table><thead><tr><th width="127.33333333333331">Name</th><th width="113">Type</th><th>Description</th></tr></thead><tbody><tr><td>toState</td><td>String</td><td><code>In</code>: jump to "In" state<br><code>Out</code>: jump to "Out" state</td></tr></tbody></table>

```javascript
const toState = "In";
compInstance.playTo(toState);
```

### resize()

Resizes a composition instance when a window size changes.

```javascript
/**
 * addEventListener for window resizing
 */
window.addEventListener("resize", function() {
    if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
        windowWidth = window.innerWidth;
        windowHeight = window.innerHeight;
        if (compInstance) {
            compInstance.resize(windowWidth, windowHeight);
        }
    }
});
```

#### Return

`none`

### seek()

Seeks the timeline to the specified time in \[s].

#### Parameter

<table><thead><tr><th width="147.33333333333331">Name</th><th width="136">Type</th><th>Description</th></tr></thead><tbody><tr><td>t</td><td>Number</td><td>Time in [s].</td></tr></tbody></table>

```javascript
const t = 1.5; // 1.5 [s]
compInstance.seek(t);
```

#### Return

`none`

### setControlNode()

Sets the content of control nodes defined in a composition instance.&#x20;

{% hint style="info" %}
Users can manually define control nodes in widget compositions.
{% endhint %}

#### Parameter

<table><thead><tr><th width="155.33333333333331">Name</th><th width="134">Type</th><th>Description</th></tr></thead><tbody><tr><td>payload</td><td>JSON</td><td>A payload that contains control node content.</td></tr></tbody></table>

<pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript"><strong>// The code below sets content of the control nodes "Pos", "Name", and "Age" in the composition instance.
</strong><strong>const payload = {
</strong>  "Pos": 1,
  "Name": "John Doe",
  "Age": "32"
};

compInstance.setControlNode(payload);
</code></pre>

#### Return

`none`

### setWidgetNode()

Sets the content of widget nodes defined in a composition instance.&#x20;

{% hint style="info" %}
Widget nodes are created programmatically and managed by the widget dynamically.
{% endhint %}

#### Parameter

<table><thead><tr><th width="127.33333333333331">Name</th><th width="152">Type</th><th>Description</th></tr></thead><tbody><tr><td>payload</td><td>JSON</td><td>A payload containing widget node content.</td></tr></tbody></table>

{% code overflow="wrap" %}

```javascript

// The code below sets content of the widget nodes "id", "longitude", "latitude", and "temperature" in the composition instance.
const payload = {
  "id": 1001,
  "longitude": 45.67,
  "latitude": 18.97,
  "temperature": "25°C"
};

compInstance.setWidgetNode(payload);
```

{% endcode %}

#### Return

`none`

### stop()

Stops a widget animation.


# Guides and examples


# Widget example: CSS patterns

This example widget lets users choose a pattern from a selection and define colors and spacing.

## **Widget UI**

The widget UI exposes the following properties and groups them in a single card.

<table><thead><tr><th width="164">Name</th><th width="128">Id</th><th width="127">Type</th><th>Description</th></tr></thead><tbody><tr><td>Select Pattern</td><td>patternId</td><td>selection</td><td>Selection of pattern names</td></tr><tr><td>Back Color</td><td>backColorId</td><td>color</td><td>Background color</td></tr><tr><td>Front Color</td><td>frontColorId</td><td>color</td><td>Foreground color</td></tr><tr><td>Spacing</td><td>spacingId</td><td>number</td><td>Spacing between pattern repetitions</td></tr></tbody></table>

<figure><img src="/files/uQHJPoCmQd8sOx8gK9s2" alt=""><figcaption><p>Widget UI</p></figcaption></figure>

<details>

<summary>UI definition</summary>

```json
{
  "model": {
    "fields": [
      {
        "id": "patternId",
        "type": "selection",
        "title": "Select Pattern",
        "defaultValue": "Rectangles",
        "selections": [
          {
            "id": "Circles",
            "title": "Circles"
          },
          {
            "id": "Rectangles",
            "title": "Rectangles"
          },
          {
            "id": "Triangles",
            "title": "Triangles"
          }
        ]
      },
      {
        "id": "backColorId",
        "type": "color",
        "title": "Back Color",
        "defaultValue": "#E5E5F7"
      },
      {
        "id": "frontColorId",
        "type": "color",
        "title": "Front Color",
        "defaultValue": "#444CF7"
      },
      {
        "id": "spacingId",
        "type": "number",
        "title": "Spacing",
        "defaultValue": "25",
        "min": "1",
        "max": "100",
        "step": "0.1",
        "unit": "unit",
        "format": "0.1"
      }
    ],
    "groups": [
      {
        "id": "group1Id",
        "title": "Pattern Options",
        "width": "single",
        "toolTip": "Select a Pattern and modify options",
        "childIds": ["patternId", "backColorId", "frontColorId", "spacingId"]
      }
    ]
  }
}
```

</details>

## Widget code

{% hint style="info" %}
The widget loads and uses the tinycolor.js library to convert various color values (RGB, HEX, HSL, RGBA, or HSLA) to HEX color definition.
{% endhint %}

<details>

<summary>output.html</summary>

```html
<!DOCTYPE html>
<html>
  <head>
    <style>
      /* Default widget style to make output match the bouding box */
      html,
      body {
        background: none;
        margin: 0;
        padding: 0;
        height: 100%;
        overflow: hidden;
      }
      /* Add your custom styles here */
    </style>
  </head>
  <body>
    <!-- Your custom html goes here -->
    <div id="pattern" style="position: absolute; left:0%; top:0%; width: 100%; height: 100%"></div>
    
    <!-- Singular Widget Library -->
    <script src="https://app.singular.live/libs/singularwidget/1.0.4/singularwidget.js"></script>
    <!-- Load tinycolor library from cloudflare cdn -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.5.2/tinycolor.min.js" integrity="sha512-gV2/CUt/1tn0eFseTyMjNqZU3kvhXacxA5eFoTwTd9c4b/Ems0BF00LOG/KcWnGZRdo62tqYNS8IiKtU4PGVoA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script>
      let windowWidth = 0;
      let windowHeight = 0;
      let elePattern = undefined;
      let patternChanged = false;
      let pattern = "Rectangles";
      let spacing = 50;
      let bgColor = "#E5E5F7";
      let fgColor = "#444CF7";
      // we define pattern styles in a map
      const patternStyles = {
        "Circles": {
          draw: (params) => {
            const cssBgImage = "radial-gradient(circle at center center, {{fgColor}}, {{bgColor}}), repeating-radial-gradient(circle at center center, {{fgColor}}, {{fgColor}}, {{spacing}}%, transparent {{spacing2x}}%, transparent {{spacing}}%)";
            elePattern.style.backgroundImage = cssBgImage.replaceAll("{{fgColor}}", params.fgColor).replaceAll("{{bgColor}}", params.bgColor).replaceAll("{{spacing2x}}", params.spacing * 2).replaceAll("{{spacing}}", params.spacing);
            elePattern.style.backgroundBlendMode = "multiply";
          }
        },
        "Rectangles": {
          draw: (params) => {
            const cssBackground = "repeating-conic-gradient({{bgColor}} 0% 25%, {{fgColor}} 0% 50%) 50%/{{spacing}}vw {{spacing}}vh";
            elePattern.style.background = cssBackground.replaceAll("{{fgColor}}", params.fgColor).replaceAll("{{bgColor}}", params.bgColor).replaceAll("{{spacing}}", params.spacing);
          }
        },
        "Triangles": {
          draw: (params) => {
            const cssBackground = "conic-gradient(from 26.56505deg, {{bgColor}} 0% 63.43495deg, {{fgColor}} 0% 126.8699deg, {{bgColor}} 0% 50%, {{fgColor}} 0% 243.43495deg, {{bgColor}} 0% 306.8699deg, {{fgColor}} 0%) 0 0/ calc(9/16*{{spacing}}vh) {{spacing}}vh";
            elePattern.style.background = cssBackground.replaceAll("{{fgColor}}", params.fgColor).replaceAll("{{bgColor}}", params.bgColor).replaceAll("{{spacing}}", params.spacing);
          }
        }
      };
      /**
       * initialize Singular widget object and define callback functions
       */
      SingularWidget.init({
        onInit: onSingularInit,
        onValue: onSingularValue
      });
      /**
       * addEventListener for window resizing
       * relevant when your widget displays HTML content or uses widget compositions
       */
      window.addEventListener("resize", function() {
        if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
          windowWidth = window.innerWidth;
          windowHeight = window.innerHeight;
        }
      });
      /**
       * onSingularInit()
       * called when the widget instance is created
       */
      function onSingularInit(params) {
        console.log("onSingularInit() - SingularWidget =", SingularWidget);
        console.log("onSingularInit() - params =", params);
        windowWidth = window.innerWidth;
        windowHeight = window.innerHeight;
        elePattern = document.getElementById('pattern');
      }
      /**
       * onSingularValue()
       * called when the widget instance is created or the instance data is changed
       */
      function onSingularValue(json) {
        console.log("onSingularValue() - json =", json);
        patternChanged = false;
        if (json.patternId !== undefined && json.patternId != pattern) {
          pattern = json.patternId;
          patternChanged = true;
        }
        if (json.spacingId !== undefined && Number(json.spacingId) != spacing) {
          spacing = Number(json.spacingId);
          patternChanged = true;
        }
        const newBgColor = tinycolor(json.backColorId).toHex8String();
        if (json.backColorId !== undefined && newBgColor != bgColor) {
          bgColor = newBgColor;
          patternChanged = true;
        }
        const newFgColor = tinycolor(json.frontColorId).toHex8String();
        if (json.frontColorId !== undefined && newFgColor != fgColor) {
          fgColor = newFgColor;
          patternChanged = true;
        }
        if (patternChanged === true) {
          redrawPattern({
            spacing: spacing,
            bgColor: bgColor,
            fgColor: fgColor
          });
        }
      }
      /**
       * redrawPattern()
       */
      const redrawPattern = function(params) {
        // we reset all style properties
        elePattern.style.background = "";
        elePattern.style.backgroundImage = "";
        elePattern.style.backgroundPosition = "";
        elePattern.style.backgroundSize = "";
        elePattern.style.backgroundBlendMode = "";
        // draw pattern
        patternStyles[pattern].draw(params);
      }
    </script>
  </body>
</html>
```

</details>

<figure><img src="/files/oI42TenvRY9xDevcJKvc" alt=""><figcaption><p>Pattern Widget UI in Composer</p></figcaption></figure>


# App SDK

Are you interested in developing a Singular app? Contact **thomas(at)singular.live** to receive access to our App SDK documentation.


# Overview of Singular

Learn the basics of Singular

This page walks you through the essential concepts, workflow, and Singular platforms you need to know about to get started with Singular.

Start by creating or logging into your account at [Singular.live](http://www.singular.live).

### Compositions and apps

In Singular, a file that contains the *make-up* of an overlay, or a suite of overlays, is called a composition.&#x20;

A file that contains the *content* of an overlay, or a suite of overlays, including their text, images, animations, and playlists, is called an app.

#### Compositions

A composition typically contains a group of overlays called sub-compositions and all the information about their structure and attributes, including their shape, size, color, and placement, plus the fonts used in any text blocks, how they animate in and out, and more.

Sub-compositions are built and updated in Composer using a library of customizable [widgets](/support/singular-terminology#widget) for each element. By setting up a [logic layer](/support/singular-terminology#logic-layer), sub-compositions of the same type or in the same position on the screen automatically replace each other instead of overlapping as they are sent on-air. By creating [control nodes](/support/singular-terminology#control-node) and/or data nodes, you can make widgets in a sub-composition available to be controlled externally. For example, by setting up a control node for a text widget in a composition, you can update that overlay's text using the Singular REST API.

**Examples of compositions**

* A simple countdown timer
* A suite of overlays for a sports match, complete with a fullscreen match-up, a score bug, panels, a static baseline, social media icons, and more
* A news-themed pack of overlays with a live bug, lower third, talking points panel, and ticker

<figure><img src="/files/YlEUAO9Q0G9dCas7yNRB" alt=""><figcaption><p>A composition in Composer</p></figcaption></figure>

#### Apps

Apps, or control apps, are used to control compositions. You can create apps in the Dashboard from compositions that you have created or download app templates from the Singular template library.

Apps allow you to update the content of a composition, including its text, images, animations, and more. They can be controlled with Singular Studio or UNO, the Singular APIs, or third-party apps like OBS and TVU Networks.&#x20;

For every show, you need an [app instance](/support/singular-terminology#app-instance). For example, say you have a tennis composition and want to use it for a few different matches. You would create an app instance on the Dashboard for each match. Then you can update each app instance with details about the players, their stats, the location, and more, for each match. This allows you to run multiple shows, including simultaneously, based on the same composition without affecting the original composition.

<figure><img src="/files/q60ITdCfwP4TK4EQbf7W" alt=""><figcaption><p>An app in Studio with a playlist on the left, a preview on the top right, and text entry fields on the bottom right.</p></figcaption></figure>

### **The Singular workflow**

1. The Singular workflow begins on the Dashboard, where you can create a blank composition or download an app template from the template library.
2. From there, you move to Composer to build a composition from scratch or customize the template you downloaded.
3. After building the composition, you create an app for it in the Dashboard.
4. Finally, you add content to the composition and control it in Singular’s Studio or UNO, a third-party app, or by using the Singular APIs.

<figure><img src="/files/iCgLqFuURsKO8CQcYsMp" alt=""><figcaption><p>The Singular workflow</p></figcaption></figure>

### Singular platforms: Dashboard, Composer, Studio, and UNO

Singular has a platform for each part of the workflow, so you can create, control, and share overlays with the optimal toolset for each task. Overlays are managed in Dashboard, built in Composer, and controlled in Studio or UNO, or by using the Singular APIs.

{% hint style="info" %}
As a developer, you’ll use the Singular APIs to update and control overlays. However, Studio and UNO are still helpful for seeing how overlays perform before you have integrated the APIs into your system.
{% endhint %}

#### Dashboard

Dashboard is Singular's file manager, where you can create, duplicate, organize, and delete compositions and apps. It also contains information about your account and details about your compositions and apps.

<figure><img src="/files/EYtpnCQbhQIOCxCEllMm" alt=""><figcaption><p>Dashboard</p></figcaption></figure>

Dashboard also includes an extensive **template library**, with overlay themes for entertainment, news, wellness, sports, and more, plus resources like composition scripts and examples from webinars and tutorials.

<figure><img src="/files/1RRCRoQnH7wXhJNOV6oA" alt=""><figcaption><p>Template library</p></figcaption></figure>

#### Composer

In Composer, you can build and edit compositions; use control nodes to make widgets within sub-compositions available to be controlled by control apps and the Singular APIs; and add logic layers to coordinate the animation of overlays of the same time or that share the same screen real estate. You can also use Composer to add composition scripts (JavaScript code snippets) to add additional logic to a composition so it automatically updates according to the rules set up in the composition script.

<figure><img src="/files/WaxFaDU3mfL0dJqJfaGy" alt=""><figcaption><p>Composer</p></figcaption></figure>

#### Studio

Studio is Singular's standard control application. It allows you to create playlists, add content to  compositions, and take overlays on air.

<figure><img src="/files/DOnxy2GA3zLe02M1J08m" alt=""><figcaption><p>Studio controlling a countdown overlay</p></figcaption></figure>

#### UNO

The UNO control app controls Singular's UNO family of templates. UNO templates are designed to do one thing and one thing only, so the interface displays only the options applicable to a particular template.&#x20;

<figure><img src="/files/GK1hS1DH6YfXkzvjPoBt" alt=""><figcaption><p>A football template in UNO</p></figcaption></figure>

Now that you understand the basics of Singular, go deeper into [managing](/singular-basics/managing-overlays-in-the-dashboard), [building](/singular-basics/building-overlays-in-composer), and [controlling overlays](/singular-basics/controlling-overlays-in-studio-and-uno). Or jump right in and [build your first composition](/singular-basics/building-overlays-in-composer/how-to-build-a-composition).


# Managing overlays in the Dashboard

Stay organized with the Singular Dashboard.

The Dashboard is your go-to place for creating and organizing Singular compositions and control apps, as well as managing your account and settings. It also contains the Singular template library.

Access your Dashboard [here](https://app.singular.live/dashboard).

For an overview of the Dashboard setup, see the [Dashboard layout reference](/singular-basics/managing-overlays-in-the-dashboard/dashboard-reference).

For how-to guides to common Dashboard tasks, continue to the next sections.


# How to create a new composition

To create a new composition from scratch, follow these steps:

1. Navigate to the Dashboard.
2. Select the folder where you want your composition to live.
3. Open the **New** menu in the upper left of the Dashboard and select **Composition**.
4. Name your composition.

   <figure><img src="/files/ERik94llGdYvHJHg33os" alt=""><figcaption><p>Creating a new composition on the Dashboard</p></figcaption></figure>
5. To open your composition, double-click it. Your composition will open in Composer.

<figure><img src="/files/ZzUfs63lnFYTO0EWUQS4" alt=""><figcaption><p>A new composition in Composer</p></figcaption></figure>

{% hint style="info" %}
Learn how to build a basic composition [here](/singular-basics/building-overlays-in-composer/how-to-build-a-composition).
{% endhint %}


# How to open a new app template

Using a template is an easy way to create beautiful, dynamic overlays with Singular. Here's how to create a new control app from a template:

1. Open **Templates** in the upper left of Dashboard.
2. Browse or search for a template.
3. Select a template to see a preview.
4. Click **Use this template**. A new [app instance](/support/singular-terminology#app-instance) based on the template can now be found under Apps in Dashboard.

   <figure><img src="/files/fyk24UZesYQ0yfVejGHQ" alt=""><figcaption><p>Opening a new template in Dashboard</p></figcaption></figure>


# How to create an app for a composition

To add control a composition, you'll first need to create an app for it.

To create a control app from a composition

1. Right-click on the composition you want to control and select **New** > **Control app**.
2. A pop-up window will prompt you to choose which control app you'd like to use to control the app. Select **Studio** or **UNO**.
3. Name the new control application and click **Create**. The app will appear appear in the Apps section of the Dashboard.&#x20;

<figure><img src="/files/tK7S3wCLJxFxyzVSIqe5" alt=""><figcaption><p>Creating a control app for a composition</p></figcaption></figure>


# How to extract a composition from an app

If you want to customize an app template, you'll first need to extract a composition from it. The Dashboard is where you do this. Here's how:

1. Right-click on the app that you'd like to extract a composition from.
2. Select **Extract Composition**. The new composition will appear in the Compositions section of the same fold that contains the control app.

<figure><img src="/files/mzL9fRuYotGXcy171nB0" alt=""><figcaption><p>Extracting a composition from an app template</p></figcaption></figure>


# How to find an app's shared app token and shared API URL

To update and control an app with the Singular APIs, you'll need an app's shared app token or shared API URL.

To get these

1. Select an app in Dashboard.
2. Press the **i** button at the top right to open the [Dashboard inspector](/support/singular-terminology#dashboard-inspector). The Shared API URL and Shared App Token are listed under URLs and Token.

<figure><img src="/files/uamXQsBs5Kz79dROu2CE" alt=""><figcaption><p>Getting a control app's shared API URL and shared app token</p></figcaption></figure>

###


# Dashboard reference

<figure><img src="/files/TuUyDO1V151mbRIqUqjC" alt=""><figcaption><p>The Singular Dashboard</p></figcaption></figure>

### Introduction to the Dashboard

Below, you'll find descriptions of the six main areas of the Dashboard, including:

1. The [platform toolbar](#1.-platform-toolbar)
2. The [main panel](#2.-main-panel)
3. The [main panel filters](#3.-main-panel-filters)
4. The [dashboard inspector](#the-dashboard-inspector)
5. The [navigation panel](#4.-navigation-panel)
6. The [new menu and templates](#5.-new-menu-and-templates) buttons

#### 1. Platform toolbar

The platform toolbar provides access to your subscription status, Singular Support, and the user menu, which contains settings, the output manager, and the data stream manager.

<figure><img src="/files/PdeZPn1a8Lkyv5UnBsh2" alt=""><figcaption><p>Platform toolbar</p></figcaption></figure>

#### 2. Main panel

This is where you can view all of your assets. You can organize them into folders for easy access.

<figure><img src="/files/6DRgyw7knnNQqYNitCEp" alt=""><figcaption><p>Main panel</p></figcaption></figure>

#### 3. Main panel filters

These filters allow you to search for and sort your assets within the main panel.

<figure><img src="/files/2mo3HFaQXGShWFBYa9u3" alt=""><figcaption><p>Main panel filters</p></figcaption></figure>

#### 4. Dashboard inspector

The Dashboard inspector displays info about the assets in the main panel. It is useful for getting IDs and asset URLs, changing asset thumbnails, and viewing asset details like creation time, updated time, and more.

<figure><img src="/files/UlC745Pyy3YbukcsJA24" alt=""><figcaption><p>Dashboard inspector</p></figcaption></figure>

#### 5. Navigation panel

This is where you can navigate through your Singular assets.

<figure><img src="/files/PfSQnEg9L1c2FzZiZeOi" alt=""><figcaption><p>Navigation panel</p></figcaption></figure>

#### 6. New menu and templates

The new menu is where you can create and upload a variety of assets. Templates contains overlay templates for live shows, including sports, news, education, and more. It also includes resources like composition scripts, compositions for learning advanced widgets, and more.

<figure><img src="/files/tubkS15hRhjvYAvt87yW" alt=""><figcaption><p>New menu and templates</p></figcaption></figure>


# Building overlays in Composer

Take a deep dive into overlays and how to build them in Composer.

The [Overview of Singular section](/singular-basics/overview-of-singular) describes the basics of compositions, or files that contain the makeup of overlays. Here we'll take a closer look at the elements that make up a composition.

### Sub-compositions

Every composition is made up of one or more sub-compositions, which in turn consist of widgets, groups of widgets, and even other sub-compositions. When nested within a parent sub-composition, these create an overlay.&#x20;

For example, this composition has three sub-compositions, each of which makes up an overlay.

<figure><img src="/files/g6KarjrUErIXP5t80B0b" alt=""><figcaption><p>Sub-compositions in a composition</p></figcaption></figure>

When you click into a sub-composition, you can see the groups and widgets that make up the sub-composition. In the next screenshot, you can see the name of the sub-composition in the [composition breadcrumb](https://developer.singular.live/singular-basics/pages/AySCeUxs0Zng6fmn2RN6#1.1-composition-breadcrumb) (1) and the sub-composition's groups and widgets in the [composition tree](https://developer.singular.live/singular-basics/pages/AySCeUxs0Zng6fmn2RN6#1.-composition-tree) (2).

<figure><img src="/files/mKMWXnzslPtdisb6J0bk" alt=""><figcaption><p>A sub-composition and its groups and widgets</p></figcaption></figure>

Your first step when building a new composition should be to create separate sub-compositions for each overlay within your root group folder. Then, you'll nest the widgets required to build each overlay within them.

By grouping elements together within a sub-composition, you can add controls, effects, and animations to all widgets in that sub-composition. For example, instead of having the same animation for three separate sub-compositions, you can tie one animation to a single sub-composition containing your three elements.

### Logic layers

A logic layer is an overlay category that can be assigned to sub-compositions in the composition navigator in Composer. Sub-compositions assigned to the same logic layer can't be in the In state at the same time. That means that if you send a sub-composition on air, any other sub-compositions assigned to the same logic layer will automatically be sent off air. This prevents sub-compositions from interfering with each other on air.

<figure><img src="/files/cjkb9wBBXGevnbWUoRGJ" alt=""><figcaption><p>Logic layers defined for a composition in Composer</p></figcaption></figure>

### The architecture of Singular compositions

A composition has four parts:&#x20;

* Widgets
* Control nodes
* Data nodes
* Composition scripts

<figure><img src="/files/xdKIs6F6EvRBnImYR5mm" alt=""><figcaption><p>Composition architecture</p></figcaption></figure>

To understand what each part does and how they work together in a composition, let’s look at each one.

### **Widgets**

Widgets are the basic building blocks of compositions. You might start building a sub-composition by creating a rectangle widget and modifying its color, size, scale, and position. Then you might put a text widget in front of it and modify its font, size, and color. From there, you can create a whole collection of sub-compositions to make up one composition that contains all the overlays you need for a show.

<figure><img src="/files/a07oNp4RmzVKYnj9x8Oc" alt=""><figcaption><p>Widget library</p></figcaption></figure>

Widgets come in two categories: basic and advanced.

#### **Basic widgets**

Basic widgets come with a simple set of properties and straightforward options.

Examples of basic widgets:

* Text
* Circle
* Rectangle
* Image
* Video

<figure><img src="/files/jehqJNp5jaCPtMa6itTX" alt=""><figcaption><p>An overlay built with basic widgets</p></figcaption></figure>

**Advanced widgets**

Advanced widgets are usually dependent on additional compositions in order to function. They also require data or code to work.

Examples of advanced widgets:

* Grid
* Table
* Video playlist
* Google line chart
* Google pie chart

<figure><img src="/files/Fj3hVJNCqhT9rnZFg9D6" alt=""><figcaption><p>An overlay made with an advanced table widget</p></figcaption></figure>

{% hint style="info" %}
The widget library has a large collection of widgets, but you can also create new widgets with the Singular Widget SDK.
{% endhint %}

### **Control nodes**

Control nodes make certain widget properties, like text, images, colors, and more, available to be controlled by applications like Studio and UNO, or the Singular APIs. This allows you to dynamically modify widgets without having to go into the composition itself in Composer. The scope of a control node is limited to one app instance of the linked composition.

<figure><img src="/files/nB1PD3awzWjwwh9JcM8R" alt=""><figcaption><p>Control nodes set up so that cryptocurrencies can be updated using Singular's REST API</p></figcaption></figure>

### **Data nodes**

Like control nodes, data nodes link specific widget properties to control applications like Studio and UNO, or the Singular APIs. The difference between control nodes and data nodes is that while control nodes are limited to one app instance of the linked composition, the scope of a data node is global within a Singular account. This means that you can use a single data node to update multiple compositions simultaneously.

### **Composition scripts**

Singular overlays are HTML webpages that are placed over videos. With composition scripting, you can add JavaScript layers to HTML overlays. This allows you to extend the functionality of your compositions and add logic to them so that they automatically update according to the rules set up in a composition script.&#x20;

For example, you might add composition scripting to an auto racing overlay so that as it receives real-time data about the positioning of the cars, the drivers’ positions move up and down a leader board. Or in a football overlay, you can set up an animation to play whenever a team scores a point.

Composer includes a built-in composition script editor, allowing you to test the functionality of your composition scripts directly in your composition's output rather than deploying any code to your web environment.

<figure><img src="/files/DziOxu8NuMSgtCTjKsDp" alt=""><figcaption><p>Composition scripts linking data to an overlay</p></figcaption></figure>


# How to build a composition

Singular's Composer gives you the tools to create stunning overlays with impressive animations, powerful controls, and live data integration. But how do you go from a blank page to a full set of overlays? Follow this step-by-step guide to get started making basic overlays.

### 1. Open a blank composition

1. If you haven't already done so, [create a new composition in the Dashboard](/singular-basics/managing-overlays-in-the-dashboard/how-to-create-a-new-composition).
2. Double-click the name of the composition to open it in Composer.

### 2. Rename the default sub-composition

When you open a composition for the first time, you'll see an empty default overlay called Overlay 1 in the composition tree.&#x20;

* Double-click **Overlay 1** to rename it.

<figure><img src="/files/eAiEklmMwG413e9wGqOf" alt=""><figcaption><p>Renaming the default sub-composition</p></figcaption></figure>

### 3. Assign the sub-composition to a logic layer

{% hint style="info" %}
Assigning a logic layer to a sub-composition allows overlays of the same type, or ones that sit in the same portion of the screen, to be smoothly replaced instead of displayed on top of each other.
{% endhint %}

To add an overlay to a logic layer

1. Open the composition navigator.
2. Click ***No Layer*** and name the new layer.

{% hint style="info" %}
When you add another sub-composition, you can assign it to the same layer or create a new layer, accordingly.
{% endhint %}

<figure><img src="/files/8LOQ531XQwRvTzMNnBfO" alt=""><figcaption><p>Selecting a logic layer for the overlay</p></figcaption></figure>

### 4. Add widgets to the sub-composition&#x20;

There are two ways to add widgets to a sub-composition:

* By selecting a widget icon
* By choosing a widget from the widget library

For both

* Navigate to the composition tree and click the **small arrow** to the right of the sub-composition name.

{% hint style="info" %}
The name of the sub-composition will disappear from the composition tree and appear in the composition breadcrumb at the top of the screen. The composition breadcrumb should say the name of your composition followed by the name of the sub-composition.
{% endhint %}

In the composition toolbar

* Select the text, circle, rectangle, image, or video icon, **or**&#x20;
* Select **Add** > **Widget** to choose widgets from the widget library.

From there, you just need to rename and configure the new widget.

#### To rename a widget

* Double-click its default name and enter a new one.

#### To configure a widget

* With the widget selected in the composition tree, navigate to the property panel and adjust the widget properties as needed.

{% hint style="info" %}
Repeat step four for as many widgets as needed.
{% endhint %}

<figure><img src="/files/Td84dynU2A2HCj33zLI9" alt=""><figcaption><p>Adding widgets to the sub-composition</p></figcaption></figure>

### 5. Reorder the sub-composition's widgets&#x20;

If you haven't already done so, you might need to reorder the widgets in the sub-composition you just created in the composition tree so they display correctly.&#x20;

The widget at the top of the list will be at the front, the second widget will be behind it, and so on.&#x20;

### 6. Add control nodes

Now that you've built the structure of an overlay, you need to make certain elements available to be edited and controlled with a control app or by using the Singular APIs. To do this, you'll add control nodes for each element you want to control.&#x20;

To add a control node

1. Navigate to the data interface and control panel, select **Text** from the drop-down menu, and name the control node in **Field name**.&#x20;

   <figure><img src="/files/uZX59L3M7kprB1LtpCCS" alt=""><figcaption><p>Where to create a new control node</p></figcaption></figure>
2. In the composition tree, select a widget to connect to the control node. (You might need to click into a group or sub-composition to find the widget.)

   <figure><img src="/files/3TKWJbX3PFckckN67vGa" alt=""><figcaption><p>Selecting a widget to connect to the control node</p></figcaption></figure>
3. In the property panel, select the text tab and click **Text** just under it. The link browser will appear.
4. In the link browser, select the name of the control node you just created.

   <figure><img src="/files/7756yB8KAfMQskZwj3yR" alt=""><figcaption><p>Linking a widget to a control node</p></figcaption></figure>
5. Back in the data interface and control panel, you can change the text for the control node.

   <figure><img src="/files/Xto01cerksLXHRfK44QB" alt=""><figcaption></figcaption></figure>

### 7. Add animations

1. Select **Animation** in the properties panel.
2. Click the widget or sub-composition in the composition tree that you want to animate.
3. Adjust the settings in the property panel and click and drag on the timeline to adjust the duration of the animation.
4. See how it looks by toggling the in/out buttons in the output window.

<figure><img src="/files/BzGW4PYUwf9tD9iB3BS1" alt=""><figcaption><p>Adding animation to the overlay</p></figcaption></figure>

### 8. Repeat steps 3-7 for each overlay

For each overlay you add to the composition, add a new sub-composition and repeat steps three through seven.

To add a new sub-composition

* Navigate to the composition tree toolbar in the top left of the screen and select **Add** > **New Sub-composition**.
* Double-click **New Sub-comp** to rename it.

<figure><img src="/files/jceruHOQbrSBWRp0LbLz" alt=""><figcaption><p>Adding a new sub-composition</p></figcaption></figure>

### 9. Create an app for your composition

Once your composition is finished, you'll need to [create an app for it in Dashboard](/singular-basics/managing-overlays-in-the-dashboard/how-to-create-an-app-for-a-composition).&#x20;

From there, you can add content to it and control it with the Singular APIs or with Studio.

<figure><img src="/files/2BmJqUsQ3zbickeDJrO5" alt=""><figcaption><p>The app for this composition being controlled in Studio</p></figcaption></figure>


# How to set up layer logic to automate overlay transitions

Layer logic allows for smooth on-air transitions between overlays that have been assigned to the same layer type.&#x20;

To show how this works, let's look at an example. Say you're creating overlays for a talk show where the hosts will talk about a variety of subjects. You create a playlist of overlays for the subjects and assign them to the same logic layer. Then every time you send one of these overlays on air, the previous one animates out before the new one animates in.&#x20;

In this guide, you'll learn how to set up layer logic.

{% hint style="info" %}
To follow along, you'll need a composition. If you don't have one, first learn [how to build a composition](/singular-basics/building-overlays-in-composer/how-to-build-a-composition) or [extract a composition from a Singular template](/singular-basics/managing-overlays-in-the-dashboard/how-to-extract-a-composition-from-an-app).&#x20;
{% endhint %}

## How to set up layer logic

1. Open a composition in Composer and open the composition navigator.&#x20;

   <figure><img src="/files/EWV3nrydrjHdLGkVMRPD" alt=""><figcaption><p>Opening the composition navigator</p></figcaption></figure>
2. In the composition navigator's Logic Layer column, open the drop-down for an overlay.
3. Select the layer of your choice or create a new logic layer by entering a new name in the **new layer** field.

   <figure><img src="/files/Vw6IdOCeFMC7KRYOy3Xq" alt=""><figcaption><p>Adding layer logic</p></figcaption></figure>
4. Repeat for any additional layers.

{% hint style="danger" %}
If you build a composition where one layer spills into an area where another layer sits, they will overlap if they're onscreen at the same time. Thus, be mindful of the space that your layers occupy when you're creating and controlling overlays.
{% endhint %}


# How to set up control nodes to make widget properties available to a control app

Control nodes expose widgets in a composition to a control application or the Singular APIs so that you can dynamically update them without having to go into the composition itself.

In this guide, you'll learn how to create control nodes in the [data interface and control panel](#how-to-create-a-control-node-with-the-data-interface-and-control-panel) and in the [node link browser](#how-to-create-a-control-node-with-the-node-link-browser). The examples show a control node being made for a text widget, but you can also create control nodes for various other types of widgets.

{% hint style="info" %}
To follow this guide, you'll need a composition. If you don't have one, first learn [how to build a composition](/singular-basics/building-overlays-in-composer/how-to-build-a-composition) or [extract a composition from a Singular template](/singular-basics/managing-overlays-in-the-dashboard/how-to-extract-a-composition-from-an-app).
{% endhint %}

## How to create a control node in the data interface and control panel

1. Open a composition in Composer.
2. Navigate to the data interface and control panel and select **Text** from the drop-down menu.
3. Name the control node in **Field name**.&#x20;

   <figure><img src="/files/exLYJu3Zt4ZrtkXeyK5j" alt=""><figcaption><p>Creating and naming a control node</p></figcaption></figure>
4. In the composition tree, select a widget to connect to the control node. (You might need to click into a group or sub-composition to find the widget.)

   <figure><img src="/files/XXax93JzkNXU8TH0gJZZ" alt=""><figcaption><p>Selecting a widget to connect to the control node</p></figcaption></figure>
5. In the property panel, select the text tab and click **Text** just under it. The link browser will appear.
6. In the link browser, select the name of the control node you just created.

   <figure><img src="/files/7756yB8KAfMQskZwj3yR" alt=""><figcaption><p>Linking a widget to a control node</p></figcaption></figure>
7. Back in the data interface and control panel, you can change the text for the control node.

   <figure><img src="/files/IalUvyMWZaPvYQdG1HDd" alt=""><figcaption><p>Finding a new control node in the data interface and control panel</p></figcaption></figure>

## How to create a control node with the node link browser

1. Open a composition in Composer.
2. Navigate to the composition tree and select a text widget. (You might need to click into a sub-composition or group to see the widgets.)

   <figure><img src="/files/XXax93JzkNXU8TH0gJZZ" alt=""><figcaption><p>Selecting a text widget from the composition tree</p></figcaption></figure>
3. In the property panel, select the text tab and click **Text** just under it. The link browser will appear.
4. In the link browser, name the control node in the text field and select **Add**.

   <figure><img src="/files/BLqievXMrQvjzJ1CvZlV" alt=""><figcaption><p>Naming a control node</p></figcaption></figure>
5. Navigate to the data interface and control panel and find the control node that you just created.
6. In the text field to the right of the control node name, you can change the text.

   <figure><img src="/files/IalUvyMWZaPvYQdG1HDd" alt=""><figcaption><p>Finding a new control node in the data interface and control panel</p></figcaption></figure>


# Animating overlays

Animation is important for creating dynamic and compelling overlays. It defines how an overlay behaves when taken on-air or off-air, or sitting on screen.

The three methods of animating in Singular's Composer are:

* [Timeline animations](#timeline-animations)
* [Behavior animations](#behavior-animations)
* [Update animations](#update-animations)

### Timeline animations

The most important and necessary of the three types of animations is the timeline animation. Timeline animation defines what an overlay does when taken on and off air. Does an overlay fade, slide, or twirl when you take it on or off air?

[Click here for a tutorial on how to create timeline animations](/singular-basics/building-overlays-in-composer/animating-overlays/how-to-create-timeline-animations).

### Behavior animations

Behavior animations allow you to add looping animations to Composer elements. This is useful for adding things like a flare or other living holds to an overlay. The purpose of behavior animations is to make overlays more alive while they are on-air. How do your overlays behave on air?

[Click here for a tutorial on how to create behavior animations](/singular-basics/building-overlays-in-composer/animating-overlays/how-to-create-behavior-animations).

### Update animations

Update animations are triggered when new data is pushed to overlays. If an update animation is enabled, any new information sent to your overlay will update with an animation of your choosing. Think of a fade out and then a fade in when the score of your favorite sports team goes from 0 to 1 on the scoreboard.&#x20;

[Click here for a tutorial on how to create update animations](/singular-basics/building-overlays-in-composer/animating-overlays/how-to-create-update-animations).


# How to create timeline animations

Timeline animations control the speed, duration, and style of how sub-compositions, groups, and widgets animate In and Out.

This guide shows you how to:

* [Change the timing of animations](#how-to-change-the-timing-of-animations)
* [Create independent In and Out animations](#how-to-create-independent-in-and-out-animations)
* [Change animation settings](#how-to-change-animation-settings)
* [How to use the Link Timelines feature to run timeline animations in nested sub-compositions](#how-to-link-timelines-to-run-the-timeline-animations-of-nested-sub-compositions)

## How to change the timing of animations

Timeline animations are represented by colored bars in the animation menu tab in the property panel. To change them

1. Navigate to the animation tab in the property panel.
2. Click, hold, and drag the bar left and right to change when the animation starts. Or click hold and drag either end of the bar to shorten or lengthen the duration of an animation.

<figure><img src="/files/LgEJ9mK8TS6c8HWzF7EA" alt=""><figcaption><p>Changing animation timing</p></figcaption></figure>

## How to create independent In and Out animations

To set up animations so they animate In differently than they animate Out

1. Navigate to the animation tab in the property panel.
2. Activate **2 timelines** in the top of the animation tab.
3. Select **Out1 to In** to configure the In animation and **In to Out2** to configure the Out animation.

<figure><img src="/files/qkD39P7WfXRhW3bIg5pm" alt=""><figcaption><p> Enabling two animation timelines</p></figcaption></figure>

{% hint style="info" %}
If you want an overlay to animate In and Out in the same way, don't use this feature.
{% endhint %}

## How to change animation settings

To fine-tune animation settings

1. Navigate to the animation tab in the property panel.
2. Click **the three sliders icon** on the timeline animation bar. You can change the type, direction, duration, and curve of animations.
3. Test your changes by clicking **In** and **Out** below the output window.

<figure><img src="/files/ruew9JSWbuU3S2YeOoJB" alt=""><figcaption><p>Adjusting timeline animation settings</p></figcaption></figure>

{% hint style="info" %}
The Singular REST API and control applications can only control timeline animations that have been set to the root level of your composition tree structure. If you need timeline animations to play out in nested sub-compositions, use the link timelines feature.
{% endhint %}

## How to use the link timelines feature to run timeline animations in nested sub-compositions

You can force the Singular REST API and control apps to run the timeline animations of nested sub-compositions by linking timelines.

To play the animation timelines of a sub-composition nested beneath another sub-composition, right click the nested sub-composition in the composition tree and select **Link Timeline**.&#x20;

<figure><img src="/files/0OoOreCsea5SCrbvXzCm" alt=""><figcaption><p>Choosing Link Timeline from the composition tree menu</p></figcaption></figure>

{% hint style="success" %}
When a timeline animation has been linked, it will turn green in the composition tree.&#x20;
{% endhint %}

You can also show or hide sub-compositions with linked timelines by toggling the linked timelines button (chainlink icon) in the top left of the composition navigator.

<figure><img src="/files/JUzk6mdPjo1bloxU2EQG" alt=""><figcaption><p>The linked timelines button in the composition navigator</p></figcaption></figure>


# How to create behavior animations

Behavior animations allow you to add looping animations to elements in your compositions. This can be particularly useful for adding a flare or other living hold to an overlay.

To add a behavior animation to a composition, follow the steps below. &#x20;

### 1. Add a behavior animation

1. Navigate to the in the composition tree and select a widget to add a behavior animation to.
2. Select the Behavior tab in the property panel to access the behavior animation controls.
3. Select the type of behavior animation you wish to add. (The type used in this example, Position X, makes the flare move horizontally).

<figure><img src="/files/uNGzjZaIEGdTCGVtiiE3" alt=""><figcaption><p>Adding a behavior animation</p></figcaption></figure>

### 2. Adjust the properties and values of the animation

After adding a behavior, you can adjust several of its properties to customize the animation to your needs.

* **Effect**: how you want the behavior to animate
* **Easing**: the speed curve of the animation
* **Value Min and Max**: the minimum and maximum values that the animation moves between, the duration of the animation, and the delay between each loop of the animation

In this example, the effect, easing, and min and max values are being set to make the flare pingpong horizontally at a certain speed and distance.

<figure><img src="/files/3JUDgsHp4ZCWuRRDaVtI" alt=""><figcaption><p>Refining behavior animation</p></figcaption></figure>

### 3. Toggle a behavior On and Off

* To toggle an animation On and Off, click the checkbox on the left of each behavior.
* To delete a behavior animation, select the **X** on the right.

<figure><img src="/files/o8KdEv9JexWCfdzCx4Wk" alt=""><figcaption><p>Toggling and deleting a behavior animation</p></figcaption></figure>


# How to create update animations

Update animations are automatically triggered when new data is pushed to a widget via a control node or data node. If an update animation is enabled, any new information entered in the control field (or pushed via a data node) updates with an animation of your choosing. This requires your widget to first be connected to a control node or data node.

### Requirements&#x20;

* A composition with a text widget connected to a control node.

Learn know how to connect a widget to a control node [here](/singular-basics/building-overlays-in-composer/how-to-set-up-control-nodes-to-make-widget-properties-available-to-a-control-app).

### 1. Enable update animation

1. Open a composition and select the widget you want to have an update animation.
2. Click the update tab in the property panel and select **active**.

<figure><img src="/files/XZDn4fWBehd5OWVMUjGi" alt=""><figcaption><p>Enabling update animation</p></figcaption></figure>

### 2. Choose Out update animation settings

1. In the drop-down menu on the left, set the animation type.
2. In the drop-down menu in the center, set the animation direction.
3. In the drop-down menu on the right, set the animation effect.

<figure><img src="/files/2ibShMNTKZDiaHQDd7h2" alt=""><figcaption><p>Choosing out update animation settings</p></figcaption></figure>

### 3. Choose in update animation settings

* Repeat the process from step two for the In animation settings.

<figure><img src="/files/5A2SY9WtGATfrxon5RNt" alt=""><figcaption><p>Choosing in update animation settings</p></figcaption></figure>

### 4. Test

#### To test your settings

* Navigate to the data interface and control panel and update the widget's control node contents.

{% hint style="info" %}
Every time you update the text, the In and Out update animations will be triggered.
{% endhint %}

#### To ensure the text is located where you want it in its bounding box

* Click on the **Toggle Bounding Box** icon above the output screen.

#### To change the justification of the text

1. Select the text tab in the property panel
2. Change the justification in the justification drop-down menu.

<figure><img src="/files/qntC4AumbYGJCzWRQ8K8" alt=""><figcaption><p>Testing update animation</p></figcaption></figure>


# How to make overlays interactive

Making overlays is one thing. Making *interactive* overlays is another.

This guide shows how to make interactive overlays with buttons that toggle their corresponding graphics on and off.

{% hint style="warning" %}
Interactive overlays only work if you prepare and set up embed codes on your website.&#x20;
{% endhint %}

### 1. Download and open the template used in this guide

* Navigate to the **Dashboard** > **Templates** > **Resources** and download and open the **Interactive Example** template.

For help, see [How to open a new app template](/singular-basics/managing-overlays-in-the-dashboard/how-to-open-a-new-app-template).

<figure><img src="/files/WsAR6wvWS0S08ocXgiUM" alt=""><figcaption><p>Downloading the Interactive Example template</p></figcaption></figure>

{% hint style="info" %}
This template has four pre-built graphics: A, B, C, D, and four pre-built buttons: A Button, B Button, C Button, D Button.&#x20;
{% endhint %}

### 2. Choose an interactive object

1. In the composition tree, double-click the **A Button** sub-composition.
2. Select the **mainBase** widget to add the interactivity.

<figure><img src="/files/VZVxPMW6YLUAZAraCMoX" alt=""><figcaption><p>Selecting the mainBase widget</p></figcaption></figure>

### 3. Add an event

The event tool allows you to assign interactivity to a widget.

1. Navigate to the event tab in the property panel and select **Mouse Down** from the drop-down men&#x75;**.**&#x20;
2. Click **Add**.

<figure><img src="/files/gswLFOHXLsBylA6Xc2EI" alt=""><figcaption><p>Adding a "mouse down" event</p></figcaption></figure>

{% hint style="info" %}
You can perform eight different actions in the output window to control a sub-composition (you can test the swipe options on a touchscreen):

* Mouse down
* Mouse up
* Mouse enter
* Mouse leave
* Swipe left
* Swipe right
* Swipe up
* Swipe down
  {% endhint %}

### 4. Deactivate the Send Message to Composition tool

* Deselect **Active** in the Send Message to Composition Script box.

{% hint style="info" %}
This tool is for advanced users.
{% endhint %}

<figure><img src="/files/y70xlmeuaftErt7f7vZ8" alt=""><figcaption><p>Deactivating the composition script tool</p></figcaption></figure>

### 5. Activate the event

* Select **Active** in the control sub-composition box to activate the interactivity.

<figure><img src="/files/6oTJuWxz3fBWZg37rM3Y" alt=""><figcaption><p>Activating the event</p></figcaption></figure>

### 6. Select the controlled graphic

* In the Composition dropdown menu, select **A**, which is the sub-composition that will be controlled by the A Button.

<figure><img src="/files/FxqqQrOmQM33NYWRwoI8" alt=""><figcaption><p>Selecting the composition A graphic</p></figcaption></figure>

### 7. Select the mode

* Select **Toggle** so the A Button toggles the A Graphic on and off.

<figure><img src="/files/cgrwhyZukqrMZ1VxWYY8" alt=""><figcaption><p>Selecting Toggle mode</p></figcaption></figure>

{% hint style="info" %}
You can assign three modes to a sub-composition:

* In
* Out
* Toggle
  {% endhint %}

### 8. Test it

You've just added your first event.&#x20;

To test whether it worked

* Click **A Button** in the output window.

The green graphic should toggle off.

<figure><img src="/files/mBUUYH6H1o1EPwcAPIup" alt=""><figcaption><p>Testing interactivity in the output window</p></figcaption></figure>

### 9. Set up the other three buttons

Repeat steps 3-8 to set up the other three buttons and their corresponding graphics.

If you want to specify the cursor type your viewers see while interacting with the output, see the Mouse Pointer event property setting.

{% hint style="info" %}
When you add events to a composition, make sure the bounding box is tight around the area you want as a button. When an event is added to a widget, the corresponding bounding box will control the overlay.
{% endhint %}

### 10. Output the overlays using an embed code

When outputting interactive overlays, you must use Singular embed codes on your website. If you output via the standard process of combining overlays with your video production software, the overlays won't be interactive.


# How to adapt overlays to various screen sizes

This guide shows how to use the Singular adaptive overlays' layout feature. It uses a composition with overlays adapted to three separate aspect ratios:

* **16:9** (landscape mode on phones and typical laptop/desktop screens)
* **4:3** (square aspect ratio on most tablets)
* **9:16** (portrait mode on most phones)

### 1. Download and open the composition used in this guide

* Navigate to the **Dashboard** > **Templates** > **Resources** and download the **Adaptive Overlays for Screen Size** template.&#x20;

<figure><img src="/files/iLRvthDK3ADkmJkl2WJQ" alt=""><figcaption><p>The Adaptive Overlays for Screen Size resource</p></figcaption></figure>

### 2. Open and inspect the composition

1. Open the downloaded composition in Composer.
2. Double-click the **Lower** sub-composition in the composition tree and notice the "16:9 (Landscape Range)," "4:3 (Square Range)," and "9:16 (Portrait Range)" overlays.

{% hint style="info" %}
These three overlays all present the same information but do so in layouts designed for the specific aspect ratios of various devices.&#x20;
{% endhint %}

<figure><img src="/files/PAZrq9L8uJLVg1gsY2qR" alt=""><figcaption><p>Opening and inspecting the composition</p></figcaption></figure>

### 3. Assign aspect ratios to an overlay

Since the three overlays have already been already built, all that is left to do is activate the Adaptive feature and assign them a layout (portrait, square, or landscape) so Singular knows which layout to render on various devices.

1. Select the 16:9 (Landscape Range) folder.
2. In the property panel, select the **Adaptive** tab.
3. In the tab's drop-down menu, choose **Layout** and click **Add**.

   <figure><img src="/files/HgWkVMTYMieaGgAMyzD5" alt=""><figcaption><p>Adding a layout in the Adaptive tab </p></figcaption></figure>
4. Select **Landscape** to make this specific overlay only visible when a landscape range device views this output.

   <figure><img src="/files/q3PrdEWAxrWn7LwkrJOn" alt=""><figcaption><p>Choosing the layout</p></figcaption></figure>

{% hint style="info" %}
If you roll over the selection box, a popup shows the available aspect ratios for each layout. Each layout option covers a range.
{% endhint %}

### 4. Repeat step three

Repeat [step three](#3.-assign-aspect-ratios-to-an-overlay) for the other two overlays in the composition.&#x20;

{% hint style="info" %}
Make sure to choose **square** for the "4:3 (Square Range)" overlays and **portrait** for the "9:16 (Portrait Range)" overlays.
{% endhint %}

### 5. Test in an output window

After you activate the adaptive overlay feature for all three sub-compositions, it's time to check your work in Composer. In this guide, you'll do this for the 9×16 (Portrait Range)" overlay.&#x20;

To do this, you'll change the output window's Globals - Layout setting.&#x20;

#### A. Set globals

Navigate to the data interface and control panel and select **Globals** > **Layout** > **Portrait**. The 9×16 (Portrait Range) overlay should be activated in the output window when you animate in the overlays.&#x20;

{% hint style="info" %}
This makes only overlays assigned to Portrait Layout visible in the composition tree, which is handy when you're authoring multiple aspect ratios in one composition.&#x20;
{% endhint %}

<figure><img src="/files/PqAFIIeZU31Z5HSWuwfH" alt=""><figcaption><p>Setting Layout globals</p></figcaption></figure>

#### B. Change output window aspect ratio

In the top right corner above the output window, change the window size to 1080×1920 (or the portrait resolution of your choice) to view your overlay at the correct aspect ratio.&#x20;

<figure><img src="/files/hoBbbvV8RtYGGCwQOl1n" alt=""><figcaption><p>Changing the output window aspect ratio</p></figcaption></figure>

{% hint style="info" %}
Continue to [step seven](#7.-adjust-the-output-settings) to test if your other overlays become visible when the output window is at their aspect ratios.
{% endhint %}

### 6. Take In an overlay in Studio

1. [Create a Studio app for the composition](/singular-basics/managing-overlays-in-the-dashboard/how-to-create-an-app-for-a-composition).
2. Click **Click here to Add Overlay** and select **Lower**.
3. Press the play button to take it In.

<figure><img src="/files/vTsJ15pq3GZZjtxGAGoP" alt=""><figcaption><p>Setting up and taking In an overlay in Studio</p></figcaption></figure>

{% hint style="info" %}
Your output window is in portrait mode because that was the last mode set in Composer.
{% endhint %}

### 7. Adjust the output settings

The last step is to adjust the output settings so that the overlays adapt to the layout settings defined in Composer.&#x20;

1. Select the three dot button in the top right corner of Studio and select **Output URL**.&#x20;
2. On the left panel of the pop-up window, select **Advanced**.
3. Choose **Responsive** in the Render Window Size drop-down menu.

   <figure><img src="/files/5COgijaeKLoEksXkR0Uk" alt=""><figcaption><p>Choosing Responsive to render window size</p></figcaption></figure>
4. Select the Adaptation Globals menu option in the left panel.&#x20;
5. In the Layout drop-down menu, choose **Automatic**.

   <figure><img src="/files/dWRwKnOoZSD2ffBHXB2X" alt=""><figcaption><p>Adding "Layout" adaptive globals</p></figcaption></figure>
6. Copy and paste the App Output URL to a new Chrome browser tab and scale the browser window around the screen. Depending on the aspect ratio, different sized pre-built overlays will be displayed.

   <figure><img src="/files/XNHq8cGE7RiQEKP2sUyd" alt=""><figcaption><p>Testing the Adaptive Globals feature in a browser </p></figcaption></figure>

{% hint style="success" %}
You can also paste your adaptive URL into [Responsinator](http://www.responsinator.com/) to test how responsive websites look on various devices.&#x20;
{% endhint %}

### Generate locked URL outputs from an instance of Singular Studio

To generate multiple locked URL outputs from one instance of Singular Studio

* Add the screen size and layout to the end of an app's output URL.

For example, to lock the output of the app you used in this tutorial to 1920×1080: 16:9, add /`Output?width=1920&height=1080&g_layout=landscape` to the end of its output URL.

See the output here:

<https://app.singular.live/output/2C6LUMbEY0u83YItRILsbL/Output?width=1920&height=1080&g_layout=landscape>

<figure><img src="/files/esxqUi31PsU69iJM8B0Q" alt=""><figcaption><p>Locked 1920×1080 output URL </p></figcaption></figure>

{% hint style="info" %}
In a locked output ratio, scaling the output window won't activate the other overlay layouts.
{% endhint %}


# Composer reference

Get to know Composer, where you create and edit custom overlays

Singular’s overlay builder, Composer, is where you create and edit custom HTML5 overlays. On this page, you'll find an overview of the layout of Composer and and a list of Composer shortcut hotkeys.

* [Composer layout](#composer-layout)
* [Composer shortcuts](#composer-shortcuts)

## Composer layout

### The main areas of Composer

Composer contains four main areas:

1. Composition tree
2. Property panel
3. Output window
4. Data interface and control panel

<figure><img src="/files/nm0FqjYeJcLjrpwZi2tX" alt=""><figcaption><p>Main areas of Composer</p></figcaption></figure>

#### 1. Composition tree

This panel shows the organizational structure of the elements in a composition or sub-composition.

#### 2. Property panel

Here, various editors are available to modify and change the parameters of a selected widget, group, or sub-composition.

#### 3. Output window

In this panel, you can preview overlays.

#### 4. Data interface and control panel

This panel allows you to define links to external data and specify widget properties you want to expose to external control applications or the Singular APIs.

### A closer look at each area of Composer

<figure><img src="/files/Qrlryi7o2XZ6NX4tBuEO" alt=""><figcaption><p>A closer look at Composer</p></figcaption></figure>

#### Areas of the composition tree

**1.1 Composition breadcrumb**

Here you can see the size and name of the composition you are working on.

**1.2 Composition tree toolbar**

This toolbar allows you to add, copy, and paste elements, such as widgets, groups, and sub-compositions, into a composition. It also contains a search bar, undo/redo buttons, open/close all groups buttons, and the composition navigator button.

**1.3 Composition navigator**

This floating user interface is for navigating between sub-compositions within a composition. Here you can trigger in and out timeline animations, assign a sub-composition to a logic layer, and adjust transition timers. Access it by selecting the composition navigator button in the composition tree toolbar or by using the shortcut command + F (Mac) or control + F (PC).

#### Areas of the property panel

**2.1 Composition script editor**

Above the property panel is a button to open the composition script editor and another to open the composition script editor in safe mode.

#### Areas of the output window

**3.1 Preview toolbar**

In this toolbar within the output window, you can turn on/off bounding boxes, choose the aspect ratio of the device or the composition you want to build for, take a thumbnail snap of the frame, or open a new tab to see the output of your work on a clean page.

**3.2 In and Out buttons**

These buttons trigger In and Out animations.

**3.3 Singular support button**

The support button opens the Singular Support widget.

## Composer shortcuts

### General composer shortcuts

The following general Composer shortcuts are shown next to the menu items or as tool tips (accessed by hovering over):

<figure><img src="/files/2gjf2ep9FepI08VBbJ5P" alt=""><figcaption><p>Adding a group, widget, or sub-composition</p></figcaption></figure>

<figure><img src="/files/41Sl5YH3wlY5cz7QLBEn" alt=""><figcaption><p>Undo shortcut</p></figcaption></figure>

* **CMD/CTRL + G**: Add a new group
* **CMD/CTRL + B**: Add a new widget
* **CMD/CTRL + S**: Add a new sub-composition
* **CMD/CTRL + F**: Open the composition navigator
* **CMD/CTRL + X**: Cut
* **CMD/CTRL + C**: Copy
* **CMD/CTRL + V**: Paste
* **CMD/CTRL + Z**: Undo
* **CMD/CTRL + Y**: Redo

### Composition tree navigation shortcuts

Composition tree navigation shortcuts are not shown in the UI. To activate them, click on the composition tree. They allow you to navigate through the composition tree quickly and easily:

* Right, left, up, and down cursors
* Enter
* Space bar
* H

#### Right, left, up and down cursors

These arrow keys allow you to select tree elements

**Right**

Moves from a group to the first child element. If the selected element is a sub-composition, the right key will open this sub-composition and select the first element.

<figure><img src="/files/ObGsRkhAnI9fTGHEgRIO" alt=""><figcaption><p>Right</p></figcaption></figure>

**Left**

Moves from an element to its group and then to its parent composition.

<figure><img src="/files/GSvHOin0wAURNIzcKLbJ" alt=""><figcaption><p>Left</p></figcaption></figure>

**Up**

Moves up, vertically, within that section of the tree.

<figure><img src="/files/iJxZV7vnIcgUQqO5z6u0" alt=""><figcaption></figcaption></figure>

**Down**

Moves down, vertically, within that section of the tree.

<figure><img src="/files/FIetO5GFSSrpLi7DZvYD" alt=""><figcaption><p>Down </p></figcaption></figure>

#### Addition composition tree shortcuts

**Enter**

Changes the selected element name.

<figure><img src="/files/pnOX35LW3DybrpKdpRTU" alt=""><figcaption><p>Enter</p></figcaption></figure>

**Space bar**

Opens and closes the selected group.

<figure><img src="/files/FhFXTlQAMBcSvZfwBSe2" alt=""><figcaption><p>Space bar</p></figcaption></figure>

**H key**

Toggles visibility of the selected element.

<figure><img src="/files/IUvAf567TgcXOsctpQwk" alt=""><figcaption><p>H key</p></figcaption></figure>


# Controlling overlays in Studio and UNO

Learn how to use Singular's control applications.

As a developer, you'll mostly be updating and controlling overlays with the Singular APIs. Still, being familiar with Studio and UNO—Singular's control apps—is useful, especially before you integrate the Singular APIs with your system.

Continue to the next articles to learn how to accomplish basic tasks in Studio and UNO.




---

[Next Page](/llms-full.txt/1)

