# Welcome to our dev guides

IoT development can be hard, but we want to make it as easy as possible.

We know this is not an easy undertaking, especially because there are many layers involved in an IoT project, from hardware to the cloud. We can only act on the last one: the application layer. That said, we put a lot of care into understanding the full IoT development process, and we hope these guides help you through it.

{% hint style="success" %}
**New to Ubidots?**

We're an Industrial IoT platform that helps thousands of **end users** and **system integrators** connect their industrial assets to the cloud, maximize uptime, improve efficiency, and drive digital transformation.

Learn more about [our key features](https://ubidots.com/platform), or [create an account](https://industrial.ubidots.com/accounts/signup_industrial/).
{% endhint %}

These guides are categorized as follows:

* **Dashboards**: Our drag-and-drop IoT dashboards are often called "codeless," but that doesn't mean they lack customization. Check these guides to learn how to extend the power of Ubidots dashboards.

{% content-ref url="/pages/MRMpDykMPA97ZOKvi9Hf" %}
[Dashboards & widgets](/dashboards-and-widgets/html-canvas)
{% endcontent-ref %}

* **UbiFunctions**: Our serverless environment has grown from a custom decoding tool into an integration and analytics powerhouse. It deserves a chapter of its own.

{% content-ref url="/pages/-MhzPm3IV6pmVP15f8cp" %}
[UbiFunctions](/ubifunctions/getting-started)
{% endcontent-ref %}

* **Plugins:** Plugins are purpose-built extensions that add specialized capabilities to your Ubidots account. Learn how to develop, deploy, and use both public and private plugins to tailor the platform to your project's needs.

{% content-ref url="/pages/9pzximvoWPbLp3jiqKQm" %}
[Plugins](/plugins/what-is-a-plugin)
{% endcontent-ref %}

* **Synthetic Variables:** Synthetic Variables let you define mathematical and statistical expressions over your time-series data to compute derived values without writing a single backend service. These guides walk you through the editor, the available expression types, and practical examples to help you extract greater meaning from your data.

{% content-ref url="/pages/vT6k2JWQC6ApRKWjFUD0" %}
[Synthetic variables](/synthetic-variables/getting-started)
{% endcontent-ref %}

* **Developer Tools:** Whether you prefer working in code or on the command line, Ubidots provides the tooling to build and automate programmatically. These guides cover the JavaScript SDK, the CLI, and the dashboard runtime library, giving you the building blocks to integrate Ubidots deeply into your own development workflows.

{% content-ref url="/pages/XZHm1IrrSKjOFVOzgPeL" %}
[Developer tools](/sdks/javascript)
{% endcontent-ref %}

* **Custom IoT Applications:** Ubidots isn't just a dashboard — it's a platform for building fully branded IoT applications. These guides show you how to compose multi-page apps, configure a custom sidebar menu, and apply CSS styling so that the end product feels like your own, not a third-party tool.

{% content-ref url="/pages/Mlfv1wOlFcMhIB0hSUM8" %}
[Custom IoT applications](/apps/pages)
{% endcontent-ref %}

{% hint style="success" %}
We'll continue to add more guides and categories. In the meantime, make sure you check out our [Help Center](https://help.ubidots.com)!
{% endhint %}

{% embed url="<https://help.ubidots.com>" %}


# What is Ubidots?

We're an Internet of Things development platform that helps thousands of **IoT entrepreneurs**, **system integrators**, and **businesses** launch and scale their IoT projects without hiring a software team.

{% embed url="<https://www.youtube.com/watch?v=YTQdTAXkq8Y>" %}

Ubidots provides three tiers to power your IoT development cycle:

* [**Ubidots STEM**](https://ubidots.com/stem): A free tier for makers, students, researchers, and hobbyists learning about the possibilities of IoT.
* **Ubidots Cloud**: Our main deployment, offered under the traditional SaaS model (Software as a Service). See [public pricing](https://ubidots.com/pricing).
* **Ubidots Enterprise**: For customers requiring dedicated support, service levels, and/or support for large volumes of devices or data.
  * Optional: Ubidots Private Cloud deployment


# Devices, variables, and Dots

The main entities in Ubidots are:

* Devices
* Variables
* Dots

Every time an **Ubidots Device** receives a sensor value in a variable, a data point, or "dot," is created. Ubidots stores the dots from your devices inside variables, and each stored dot has a corresponding timestamp:

![](/files/-MiSwJ_Ua-noDT_kJxTD)

{% hint style="info" %}
An **Ubidots Device** is a virtual entity. This means you can map multiple physical devices to a single **Ubidots Device**, or map many variables from a single physical device to several Ubidots Devices. It is up to you how you want to organize your data in your Ubidots account.

We recommend mapping **1 Physical Device to 1 Ubidots Device**.
{% endhint %}

### The Dot

Each dot contains these items:

| Item        | Description                                                                                                             | Mandatory | Size limit                           |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------ |
| `value`     | A numerical value.                                                                                                      | Yes       | Up to 16-bit floating-point numbers. |
| `timestamp` | Unix Epoch time, in **milliseconds**. If not specified, our servers assign one upon reception.                          | No        |                                      |
| `context`   | An arbitrary collection of key-value pairs. Mostly used to store the latitude and longitude coordinates of GPS devices. | No        | 1 KB                                 |

You can find further details below.

#### Values

A numerical **value**. Ubidots accepts up to 16-bit floating-point numbers.

`{"value" : 34.87654974}`

#### Timestamps

A **timestamp**, as described [here](https://www.unixtimestamp.com/), tracks time as a running total of seconds. This count starts at the Unix Epoch on January 1, 1970, at UTC. Therefore, a Unix timestamp is simply the number of seconds between a particular date and the Unix Epoch.

{% hint style="info" %}
Please keep in mind that Ubidots timestamps are in **milliseconds**.
{% endhint %}

**Why do we use timestamps in milliseconds?** This is common practice in APIs. It also lets us support many IoT applications where several data points are sent in the same second.

`"timestamp" : 1537453824000`

The above timestamp corresponds to Thursday, September 20, 2018 2:30:24 PM.

{% hint style="success" %}
**PRO-TIP:** A useful tool for converting between Unix timestamps and human-readable dates is [Epoch Converter](https://www.epochconverter.com/).
{% endhint %}

#### Context

Numerical values are not the only supported data type. You can also store string or character data inside what we call **context**. The context is a key-value object that lets you store both numerical and string values. For example:

`"context" : {"status" : "on", "weather" : "sunny"}`

Context is commonly used to store your device's latitude and longitude for GPS or tracking use cases. All Ubidots maps use the `lat` and `lng` keys from a dot's context to extract your device's coordinates. This means you only need to send a single dot with the coordinate values in the variable context to plot it on a map, instead of sending latitude and longitude separately in two different variables. Below is a typical context with coordinate values:

`"context" : {"lat":-6.2, "lng":75.4, "weather" : "sunny"}`

You can mix both string and numerical values in the context. If your application is for geolocation, make sure the coordinates are set in decimal degrees.


# Technical FAQs

### How to send data to Ubidots?

Ubidots accepts [HTTP](https://docs.ubidots.com/v1.6/reference/http), [MQTT](https://docs.ubidots.com/v1.6/reference/mqtt), [TCP](https://docs.ubidots.com/v1.6/reference/tcp-udp), and [UDP](https://docs.ubidots.com/v1.6/reference/tcp-udp) payloads. To learn how to send data using each of these protocols, refer to our [Data API documentation](https://docs.ubidots.com/v1.6/reference/welcome).

{% embed url="<https://docs.ubidots.com/v1.6/reference/welcome>" %}

### Can I control a device using Ubidots?

In theory, you can program a device to act based on changes to an Ubidots variable. You can do this either by polling the cloud at regular intervals or by [subscribing to a variable using MQTT](https://docs.ubidots.com/v1.6/reference/subscribe).

In practice, we only recommend using downlink commands to push configurations to remote devices, or, put differently, to perform "virtual controls." Physical controls are technically possible, but they pose a major risk when communication fails.

See some examples of virtual versus physical controls:

| Control Type         | Examples                                                                                                                                                                                                                                                                                        |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 👍 Virtual controls  | <ul><li>Change the update frequency of a device.</li><li>Reboot a device remotely.</li><li>Command a device to download new firmware from a specific URL.</li></ul>                                                                                                                             |
| ⚠️ Physical controls | <ul><li>Open or close a water valve. <strong>DANGER: If the "close" command is not received, a flood might occur.</strong></li><li>Start or stop a production line. <strong>DANGER: If the "start" command is received during a maintenance window, an accident might occur.</strong></li></ul> |


# Business FAQs

### Can you provide the hardware for my project?

We are a hardware-agnostic cloud platform and do not sell hardware.

We do work with hardware companies as partners to test how their hardware integrates with our cloud, but this does not imply a distribution partnership.

{% hint style="info" %}
Since our customers are mostly system integration companies or entrepreneurs who design, manufacture, and/or distribute hardware themselves, we respect the channel and do not compete with them by offering hardware or off-the-shelf IoT solutions.
{% endhint %}

### Do you provide Integration Services?

Ubidots does not provide System Integration services. In most cases, our licenses enable projects that are deployed by either a specialized SI (System Integrator), or a customer with internal integration capabilities.

However, if your project requires **Professional Services** other than hardware integration, the Ubidots team — together with an Ubidots Enterprise license — might be a good fit.

Here are some areas where our team can help:

* Integration of third-party APIs and databases with Ubidots.
* Integration with your security provider for single sign-on (SSO).
* Integration of Ubidots dashboards or widgets into your web or mobile application.
* Development of key features to enable your use case.

### I would like to become a Distributor

By default, our business model is to enable beautiful IoT dashboards, **but you can take all the credit!**

This means that, in most cases, there is no need to become an Ubidots Distributor in order to use our tools and serve clients in your region.

This diagram better explains how to use our platform under a white-label model:

![](/files/-MiTOlxLnyYHxsnku6Od)

This approach is powerful because it enables you to offer a platform as if it were your own. This offering, together with your value-added services, allows you to determine your own markup based on the particularities of your market and region. This markup is usually greater than a distributor discount.

**If the above model still doesn't work for you,** a distribution agreement makes sense when at least one of the following is true:

* The partner needs to act as an official Ubidots distributor in front of their clients. This is the case for some public tender opportunities.
* The partner wishes to transfer all the admin capabilities of an Ubidots account to the end customer.
* The partner provides value-added services that are not bundled into an Ubidots license (for example, consulting companies), so it is not in their interest to bundle the cost of the platform into an entire solution.


# HTML Canvas

Build your own custom widgets with the HTML Canvas

### Introduction:

Ubidots offers off-the-shelf widgets for most visualization needs. However, some projects require more specific or fully custom widgets. For those cases, use the HTML Canvas widget to build custom visualizations with your own code.

### Definition:

HTML Canvas is a widget for building custom widgets. It lets you write custom code with the following languages, along with Ubidots built-in methods and third-party libraries described later:

* HTML
* CSS
* JavaScript

This widget features a code editor composed of three tabs, one for each language.

### When to use the HTML Canvas?

Use HTML Canvas when other widgets do not meet your functionality or styling requirements.

### How to create an HTML Canvas?

Go to a dashboard, click the *Add new widget* button (+), scroll to the *Advanced* section, and select *HTML Canvas*.

<figure><img src="/files/2dChTFxn1cYdvFTxheJB" alt="" width="375"><figcaption></figcaption></figure>

### Widget settings:

The widget offers the following settings:

* Code editor
* Third-party libraries
* Enable lazy loading
* Preload dashboard data

### Widget appearance tab:

The appearance of the widget can be customized further with these options:

* Name
* Custom Style

### Custom Style:

See [**Custom Style**](/dashboards-and-widgets/custom-ui) to learn how to customize your HTML Canvas styles.


# Third-party packages

## Overview

The HTML Canvas widget supports importing third-party libraries from CDNs. By specifying a CDN URL, you can include third-party libraries written in vanilla JavaScript or React.

## Importing React libraries

To import React libraries into the HTML Canvas widget, the CDN must provide the library in UMD (Universal Module Definition) format. You can identify UMD CDN links by looking for `umd` in the URL path or filename. Sources like unpkg and jsDelivr usually label them as `umd`.

## Example

The following example shows how to import the jQuery library for use in the widget.

Third-party libraries are imported by clicking the *3rd party libraries* option and pasting the library URL there, as shown below:

<figure><img src="/files/qeihixSXova4Te7jgee7" alt="" width="563"><figcaption></figcaption></figure>


# Preload dashboard data

This feature makes the dashboard data available for the user at load time.

## Overview

This consists of a toggle button that, when enabled, changes the widget logic so dashboard data is preloaded and available as the dashboard loads.

<figure><img src="/files/hkdxp7iWqDYseotGcubn" alt=""><figcaption></figcaption></figure>

Before this feature was added, users could only retrieve data from the Ubidots class by listening for an event and reading the required variable inside the callback.

For example, if you wanted to get your Ubidots token and the ID of the currently selected device, you could do it as follows:

```javascript
var ubidots = new Ubidots();

ubidots.on('ready', async function () 
{
	token = ubidots.token;	
	deviceID = ubidots.selectedDevice;		
});
```

However, this requires advanced JavaScript knowledge and familiarity with custom Ubidots events.

With **Preload dashboard data** enabled, you can access the same values directly:

```javascript
token = ubidots.token;	
deviceID = ubidots.selectedDevice;
```

There is no need to instantiate the Ubidots class, since an instance already exists.

{% hint style="danger" %}
By enabling this feature, the `ready` event shown above is no longer available.
{% endhint %}

## Class properties when *Preload dashboard data* is disabled

When *Preload dashboard data* is disabled, some class properties may appear as `undefined`. This happens because the class instance has not yet received their values. Consider the following code snippet:

```javascript
const ubidots = new Ubidots();

console.log(ubidots.token);
ubidots.on('receivedToken', function (token) {
    console.log(token);
    console.log(ubidots.token);
})
```

Although `ubidots.token` is valid syntax and the `Ubidots` class includes that property, the first `console.log` prints `undefined`. The other two print the token correctly because the property is only available after the event sets its value.


# Built-in library

## Overview

The HTML Canvas built-in library is the core interface for interacting with dashboard data and the Ubidots API:

* Access the class [properties](/dashboards-and-widgets/html-canvas/built-in-library/properties) to read or update dashboard settings.
* Use the class [methods](/dashboards-and-widgets/html-canvas/built-in-library/methods) to perform actions in the dashboard or retrieve data from it.
* Register logic with the `on` event handler when dashboard events occur. See [Listening events](/dashboards-and-widgets/html-canvas/built-in-library/listening-events).
* Interact directly with the [API](https://docs.ubidots.com/reference/welcome) through the [Ubidots JavaScript SDK](/dashboards-and-widgets/html-canvas/built-in-library/api).


# Properties

This page describes the HTML Canvas class properties.

## HTML Canvas class properties <a href="#properties" id="properties"></a>

| Property                      | Type                 | Description                                                                                                                                       |
| ----------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dashboardDateRange`          | `Object`             | Dashboard time frame.                                                                                                                             |
| `dashboardObject`             | `Object`             | Dashboard information.                                                                                                                            |
| `deviceObject [DEPRECATED]`   | `Object`             | [Device object](https://docs.ubidots.com/reference/device-object) of the currently selected device in a dynamic dashboard.                        |
| `selectedDevice [DEPRECATED]` | `string`             | ID of the currently selected device in a dynamic dashboard.                                                                                       |
| `selectedDevices`             | `string array`       | Array of the selected device IDs in a dynamic dashboard.                                                                                          |
| `selectedDeviceObjects`       | `Object array`       | [Device objects](https://docs.ubidots.com/reference/device-object) array for the selected devices in a dynamic dashboard.                         |
| `selectedFilters`             | `Array array object` | Array containing as many arrays as filters configured in the dashboard. Each nested array contains the filter objects for that particular filter. |
| `token`                       | `string`             | Account default token.                                                                                                                            |
| `realTime`                    | `bool`               | Whether real time is enabled in the dashboard.                                                                                                    |

### `dashboardDateRange` <a href="#dashboarddaterange" id="dashboarddaterange"></a>

```json
{
    "start":1637006467592,
    "end":1637095008866
}
```

### `dashboardObject` <a href="#dashboardobject" id="dashboardobject"></a>

```json
{
    "id": "6624cb56820a574b5730a16",
    "name": "some-dashboard",
    "timeframe": {
        "endDate": "now",
        "startDate": "now-24h"
    },
    "organization": {}
}
```

### `selectedDevices` <a href="#selecteddevices" id="selecteddevices"></a>

```json
[
    "65fb47fad809281764f8a350",
    ... ,
    "65fb47fad809281764f8a358"
]
```

### `selectedDeviceObjects` <a href="#selecteddeviceobjects" id="selecteddeviceobjects"></a>

```json
[
   {
      "url":"https://industrial.ubidots.com/api/v2.0/devices/65fb47fad809281764f8a350",
      "id":"65fb47fad809281764f8a350",
      "label":"2cf7f1c042300137",
      "name":"El Poblado",
      "description":"",
      "lastActivity":1714518206109,
      "createdAt":"2024-03-20T20:32:58.793812Z"
   },
   {
      "url":"https://industrial.ubidots.com/api/v2.0/devices/65fb47cec4098b1afda06660",
      "id":"65fb47cec4098b1afda06660",
      "label":"2cf7f1c042300213",
      "name":"Envigado",
      "description":"",
      "lastActivity":1712755770263,
      "createdAt":"2024-03-20T20:32:14.215320Z"
   },
   {
      "url":"https://industrial.ubidots.com/api/v2.0/devices/65a6fd8a9c33d2000c0ba386",
      "id":"65a6fd8a9c33d2000c0ba386",
      "label":"902a3c5ff139b0d5e390c1682472c7fa",
      "name":"West Menlo Park",
      "description":"Current weather conditions in West Menlo Park (US)",
      "lastActivity":1709043377617,
      "createdAt":"2024-01-16T22:04:58.909444Z"
   }
]
```

### `selectedFilters` <a href="#selectedfilters" id="selectedfilters"></a>

```json
[
   [
      {
         "type":"value",
         "value":"60",
         "variables":"~temperature",
         "lookup":"lte",
         "negate":false
      },
      {
         "type":"value",
         "value":"30",
         "variables":"~temperature",
         "lookup":"gte",
         "negate":false
      }
   ],
   [
      {
         "type":"value",
         "value":"60",
         "variables":"~humidity",
         "lookup":"lte",
         "negate":false
      },
      {
         "type":"value",
         "value":"30",
         "variables":"~humidity",
         "lookup":"gte",
         "negate":false
      }
   ]
]
```


# Methods

This page describes the available methods within the HTML Canvas class

## HTML Canvas methods

| Method                  | Description                                                                                           |
| ----------------------- | ----------------------------------------------------------------------------------------------------- |
| `getHeaders`            | Returns the appropriate authentication headers for public or private dashboards.                      |
| `openDrawer`            | Embeds a URL in a drawer and displays it.                                                             |
| `setDashboardDevices`   | Changes the currently selected device or devices in single-device or multi-device dynamic dashboards. |
| `setDashboardDateRange` | Changes the selected time range in a dashboard.                                                       |
| `setRealTime`           | Turns the dashboard's real-time updates on or off.                                                    |
| `refreshDashboard`      | Refreshes dashboard data in all widgets.                                                              |
| `setDashboardLayer`     | Navigates to a different layer.                                                                       |

### `getHeaders` <a href="#getheaders" id="getheaders"></a>

**Arguments:** None

**Signature:** `ubidots.getHeaders()`

**Return:**

{% tabs %}
{% tab title="Public dashboard" %}

```json
{
  Authorization: "Bearer receivedJWTToken",
  Content-type: "application/json"
}
```

{% endtab %}

{% tab title="Private dashboard" %}

```json
{
  X-Auth-Token: "BBFF-xxyyzz",
  Content-type: "application/json"
}
```

{% endtab %}
{% endtabs %}

### `openDrawer` <a href="#opendrawer" id="opendrawer"></a>

**Arguments:** `options`: `Object` \[Mandatory] => An object containing the content's URL and the drawer's width in pixels:

```
{
    url: "https://url.com",
    width: 500 // Read as 500px
}
```

**Signature:**

```
ubidots.openDrawer(
    {
        url: "https://some-url",
        width: <width>
    }
);
```

**Return:** None

### `setDashboardDevices` <a href="#setdashboarddevice" id="setdashboarddevice"></a>

**Arguments:** `deviceIds`: `string` \[Mandatory] ⇒ Comma-separated string with the IDs of the devices you want to set.

```
"<device-1-id>,~<device-2-label>,<...>"
```

**Signature:** `ubidots.setDashboardDevices(<deviceIds>)`

**Return:** None

### `setDashboardDateRange` <a href="#setdashboarddaterange" id="setdashboarddaterange"></a>

**Arguments:** `timeframe`: `Object` \[Mandatory] => An object containing the start and end timestamps:

```
{
    "startTime": 1642282088000,
    "endTime": 1673818088000
}
```

**Signature:** `ubidots.setDashboardDateRange(<timeframe>)`

**Return:** None

### `setRealTime` <a href="#setrealtime" id="setrealtime"></a>

**Arguments:** `rt`: `Bool` \[Mandatory] => A boolean indicating whether real-time updates are enabled.

**Signature:** `ubidots.setRealTime(<rt>)`

**Return:** None

### `refreshDashboard` <a href="#refreshdashboard" id="refreshdashboard"></a>

**Arguments:** None

**Signature:** `ubidots.refreshDashboard()`

**Return:** None

### `setFullScreen` <a href="#setfullscreen" id="setfullscreen"></a>

**Arguments:** `screenSetting`: `string(toggle | enable | disabled)` \[Mandatory] => A string representing the full-screen setting.

**Signature:** `ubidots.setFullScreen(<screenSetting>)`

**Return:** None

### `setDashboardLayer` <a href="#setfullscreen" id="setfullscreen"></a>

**Arguments:**

* `layer`: `string` \[Mandatory] ⇒ layer ID or label.
  * Use ID as a plain string: `"6926000dcb198b4d10067b58"`
  * Use label prefixed with `~`: `"~Layer-2"`
* `queryParams`: `Object` \[Optional] ⇒ An object where each key/value pair will be added as a query param.
  * Allowed value types: `string | number | boolean`
  * Arrays are NOT allowed. If you need multiple values, pass a comma-separated string, e.g. `"id1,id2,id3"`


# Listening events

Access dashboard settings via event handlers

## Overview

An *event* fires at dashboard load time or when a specific dashboard action occurs. Register a handler with `ubidots.on()` — the callback receives the event argument when the event fires.

## Registering for an event

```javascript
ubidots.on('<event>', function(payload) {
    // your logic here
});
```

## Available events

| Event                             | Triggered when                                                                         | Argument                            |
| --------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------- |
| `v2:auth:token`                   | At dashboard load time, when the user's API token is retrieved.                        | `string`                            |
| `v2:auth:jwt`                     | At dashboard load time, when the user's JWT token is retrieved.                        | `string`                            |
| `v2:auth:*`                       | At dashboard load time, when either the API token or JWT is retrieved.                 | `string`                            |
| `v2:dashboard:settings:daterange` | The dashboard's date range is changed via the GUI or programmatically.                 | [Timeframe](#timeframe-object)      |
| `v2:dashboard:settings:rt`        | The dashboard's real-time setting is toggled via the GUI or programmatically.          | `boolean`                           |
| `v2:dashboard:settings:refreshed` | A user clicks the refresh button in the dashboard.                                     | —                                   |
| `v2:dashboard:settings:filters`   | The dashboard's filters configuration is changed.                                      | [Filters](#filters-array)           |
| `v2:dashboard:devices:self`       | At dashboard load time, with all devices associated with the dashboard.                | [Device\[\]](#device-object)        |
| `v2:dashboard:devices:selected`   | The selected device in a dynamic dashboard is changed via the GUI or programmatically. | [Device\[\]](#device-object)        |
| `v2:dashboard:self`               | At dashboard load time, when the dashboard metadata is retrieved.                      | [Dashboard](#dashboard-object)      |
| `v2:dashboard:*`                  | Any of the above dashboard events fire (catch-all).                                    | varies (same as the specific event) |
| `v2:widget:ready`                 | At dashboard load time, after all other events have been triggered.                    | —                                   |

{% hint style="warning" %}
`v2:auth:*` and `v2:dashboard:*` are the only supported wildcards. Patterns like `v2:dashboard:settings:*` are **not valid** — the callback is silently discarded and never fires.
{% endhint %}

## Payload reference

### Device object

Delivered by `v2:dashboard:devices:self` (all dashboard devices) and `v2:dashboard:devices:selected` (currently selected devices). Both events always deliver an **array**, even for a single selection.

```json
{
    "url": "https://industrial.ubidots.com/api/v2.0/devices/<device-id>",
    "id": "<device-id>",
    "label": "<device-label>",
    "name": "<device-name>",
    "description": "",
    "createdAt": "<ISO-timestamp>",
    "lastActivity": <unix-timestamp-ms>
}
```

### Timeframe object

Delivered by `v2:dashboard:settings:daterange`. Both values are Unix timestamps in **milliseconds**.

```json
{
    "start": 1710000000000,
    "end": 1710086400000
}
```

### Filters array

Delivered by `v2:dashboard:settings:filters`. Each outer array is a filter group (OR logic between groups); each inner array contains the conditions for that group (AND logic within a group).

```json
[
    [
        { "type": "value", "value": "60", "variables": "~temperature", "lookup": "lte", "negate": false },
        { "type": "value", "value": "30", "variables": "~temperature", "lookup": "gte", "negate": false }
    ],
    [
        { "type": "value", "value": "60", "variables": "~humidity", "lookup": "lte", "negate": false }
    ]
]
```

### Dashboard object

Delivered by `v2:dashboard:self`.

```json
{
    "id": "<dashboard-id>",
    "label": "<dashboard-label>",
    "name": "<dashboard-name>",
    "context": { "dashboardType": "<type>" },
    "widgetsNumber": 4,
    "isEditable": true,
    "timeframe": {}
}
```

## Class properties

Each event also populates a property on the `ubidots` instance. After `v2:widget:ready` fires, all properties are set and can be read synchronously — no handler needed.

| Property                     | Populated by                      |
| ---------------------------- | --------------------------------- |
| `ubidots.token`              | `v2:auth:token`                   |
| `ubidots.jwtToken`           | `v2:auth:jwt`                     |
| `ubidots.dashboardDateRange` | `v2:dashboard:settings:daterange` |
| `ubidots.realTime`           | `v2:dashboard:settings:rt`        |
| `ubidots.selectedFilters`    | `v2:dashboard:settings:filters`   |
| `ubidots.dashboardDevices`   | `v2:dashboard:devices:self`       |
| `ubidots.selectedDevices`    | `v2:dashboard:devices:selected`   |
| `ubidots.dashboardObject`    | `v2:dashboard:self`               |

## Examples

### Fetch device data whenever the selection changes

```javascript
ubidots.on('v2:dashboard:devices:selected', function(devices) {
    const device = devices[0];
    fetch('https://industrial.api.ubidots.com/api/v1.6/devices/' + device.label + '/', {
        headers: ubidots.getHeaders()
    })
    .then(res => res.json())
    .then(data => renderWidget(data));
});
```

### Sync a custom date picker with the dashboard

```javascript
ubidots.on('v2:dashboard:settings:daterange', function(timeframe) {
    myDatePicker.setRange(
        new Date(timeframe.start),
        new Date(timeframe.end)
    );
});
```

### Gate rendering on ready, then use token for API calls

```javascript
ubidots.on('v2:widget:ready', function() {
    // All load-time events have fired — safe to read any property
    const headers = ubidots.getHeaders();
    const device  = ubidots.selectedDevices[0];
    loadChartData(device, headers);
});
```


# API

Access the Ubidots JavaScript SDK from HTML Canvas

## JavaScript SDK

The HTML Canvas built-in library exposes the [Ubidots JavaScript SDK](/sdks/javascript) through `ubidots.api`. This simplifies interactions with the Ubidots API when you develop in HTML Canvas. It avoids manual requests and custom wrappers.

Use the following syntax to access SDK methods:

```
ubidots.api.<JS_SDK_METHODS>
```

{% hint style="info" %}
See the [JavaScript SDK docs](/sdks/javascript) for supported entities, methods, and filtering capabilities.
{% endhint %}

## Usage

**With** [**Preload dashboard data**](/dashboards-and-widgets/html-canvas/preload-dashboard-data) **enabled:**

```javascript
// Get the token from the Ubidots built-in library
const TOKEN = ubidots.token;

// Authenticate the SDK with the token
ubidots.api.authenticate(TOKEN);

// Get the first 100 devices in the account
ubidots.api.devices.get().then((devices) => {
    // Print an array of Device objects
    console.log(devices);
    
    // YOUR LOGIC FOR THESE 100 DEVICES
});
```

**With Preload dashboard data disabled:**

```javascript
var ubidots = new Ubidots();

let TOKEN;

ubidots.on("ready", async () => {
    TOKEN = ubidots.token;
    
    // Authenticate the SDK with the token
    ubidots.api.authenticate(TOKEN);
    
    // Get the first 100 devices in the account
    var devices = await ubidots.api.devices.get();
    
    // Print an array of Device objects
    console.log(devices);
    
    // YOUR LOGIC FOR THESE 100 DEVICES
});
```


# Examples


# Basics

### Example #1

This example creates a text box to set a variable's value.

**HTML**:

```html
<div class="notification--container" id="notification">
  <p>Value sent successfully</p>
</div>

<div class="send-value--container">
  <input type="number" step="0.0001" placeholder="Enter number" class="send-value--input" id="value" />
  <button type="button" class="send-value--button" id="send-value">Send value</button>
</div>
```

**CSS**:

```css
.send-value--container {
  	left: 50%;
	position: absolute;
  	top: 50%;
  	transform: translate(-50%, -50%);
}

.send-value--input {
	border: none;
  	border-bottom: 2px solid #28AECD;
  	display: block;
  	margin: 0 auto;
  	font-size: 14px;
  	outline: none;
}

.send-value--button {
  	background: #FFFFFF;
  	border: 2px solid #28AECD;
  	border-radius: 20px;
  	color: #28AECD;
  	font-size: 14px;
	margin-top: 10px;
	width: 100%;
}

.send-value--button:hover {
 	background: #28AECD;
  	cursor: pointer;
  	color: #FFFFFF;
}

.notification--container {
 	display: none;
}
```

**JavaScript**:

In the following code, set *VARIABLE\_ID* to your variable ID.

```javascript
var sendValue = document.querySelector('#send-value');
var value = document.querySelector('#value');
var notification = document.querySelector('#notification');
var TOKEN = ubidots.token;
var VARIABLE_ID = "variable-id";

function postValue(variable, token, callback) {
  var url = 'https://industrial.api.ubidots.com/api/v1.6/variables/' + variable + '/values';
  var headers = {
    'Content-Type': 'application/json',
    'X-Auth-Token': TOKEN 
  };
  
  notification.style.display = 'none';

  fetch(url, {
    method: 'POST',
    headers: headers,
    body: JSON.stringify({ 
      value: parseFloat(value.value, 10) 
    })
  })
  .then(function(response) {
    if (response.ok) {
      return response.json();
    } else {
      throw new Error('Error posting value');
    }
  })
  .then(function(data) {
    callback(data.value);
  })
  .catch(function(error) {
    console.log(error);
  });
}

sendValue.addEventListener('click', function () {
  postValue(VARIABLE_ID, TOKEN, function (value) {
    notification.style.display = 'block';
  });
});

```

<figure><img src="/files/3CFTjn65FXKNHzvlnAl9" alt=""><figcaption></figcaption></figure>

### Example #2

This example shows how to get data from Ubidots and display it in a Highcharts chart.

**HTML**:

```html
<div id="container" style="min-width: 310px; height: 310px; margin: 0 auto"></div>
```

**JavaScript**:

In the following code, set *VARIABLE\_ID* to your variable ID.

```javascript
var TOKEN = ubidots.token;
var VARIABLE = 'variable-id';

function makeHttpRequest(url, method, headers, body) {
  return new Promise(function(resolve, reject) {
    fetch(url, {
      method: method,
      headers: headers,
      body: body
    })
    .then(function(response) {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error('Error making HTTP request');
      }
    })
    .then(function(data) {
      resolve(data);
    })
    .catch(function(error) {
      reject(error);
    });
  });
}

async function getDataFromVariable(variable, token, callback) {
  var url = 'https://industrial.api.ubidots.com/api/v1.6/variables/' + variable + '/values';
  var headers = {
    'X-Auth-Token': token,
    'Content-Type': 'application/json'
  };
  
  makeHttpRequest(url, 'GET', headers)
    .then(function(res) {
      callback(res.results);
    })
    .catch(function(error) {
      console.error(error);
    });
}

var chart = Highcharts.chart('container', {
  chart: {
    type: 'line'
  },
  title: {
    text: 'Bring data from Ubidots'
  },
  xAxis: {
    type: 'datetime',
  },
  credits: {
    enabled: false
  },
  series: [{
    data: []
  }]
});

getDataFromVariable(VARIABLE, TOKEN, function(values) {
  var data = values.map(function(value) {
    return [value.timestamp, value.value];
  });
  
  chart.series[0].setData(data);
});

async function makeRequest(url, method, token, payload) {
  var headers = {
    'X-Auth-Token': token,
    'Content-Type': 'application/json'
  };
  var body = null;

  if (payload) {
    body = JSON.stringify(payload);
  }

  return makeHttpRequest(url, method, headers, body);
}

async function sendDataToDevice(sendDataDeviceLabel, token, payload) {
  var url = 'https://industrial.api.ubidots.com/api/v1.6/devices/' + sendDataDeviceLabel + '/';
  var method = 'POST';

  return makeRequest(url, method, token, payload);
}

async function getDeviceVariables(sendDataDeviceID, token) {
  var url = 'https://industrial.ubidots.com/api/v2.0/devices/' + sendDataDeviceID + '/variables';
  var method = 'GET';

  return makeRequest(url, method, token);
}

async function getDashboardDevices(sendDataDashboardID, ubidotsToken) {
  var url = 'https://industrial.ubidots.com/api/-/dashboards/' + sendDataDashboardID + '/devices?sort_by=name&page_size=500';
  var method = 'GET';

  return makeRequest(url, method, ubidotsToken);
}

async function getAllDashboards(ubidotsToken) {
  var url = 'https://industrial.api.ubidots.com/api/v2.0/dashboards/';
  var method = 'GET';

  return makeRequest(url, method, ubidotsToken);
}

async function getAllDevices(token) {
  var url = 'https://industrial.api.ubidots.com/api/v2.0/devices/';
  var method = 'GET';

  return makeRequest(url, method, token);
}
```

**Third-party libraries**:

```url
https://cdnjs.cloudflare.com/ajax/libs/highcharts/6.1.1/highcharts.js
```

<figure><img src="/files/L3nDMN4HXbq8sKJlbv7U" alt=""><figcaption></figcaption></figure>

### Example #3

This example is similar to the previous one, but it uses Plotly instead of Highcharts.

**HTML**:

```html
<div id="plotlyDiv" style="width:100%;height:400px;"></div>
```

**JavaScript**:

```javascript
var TOKEN = ubidots.token;
var VARIABLE = 'variable-id';

function makeHttpRequest(url, method, headers, body) {
  return new Promise(function(resolve, reject) {
    fetch(url, {
      method: method,
      headers: headers,
      body: body
    })
    .then(function(response) {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error('Error making HTTP request');
      }
    })
    .then(function(data) {
      resolve(data);
    })
    .catch(function(error) {
      reject(error);
    });
  });
}

function getDataFromVariable(variable, token, callback) {
  var url = 'https://industrial.api.ubidots.com/api/v1.6/variables/' + variable + '/values';
  var headers = {
    'X-Auth-Token': token,
    'Content-Type': 'application/json'
  };

  makeHttpRequest(url, 'GET', headers)
    .then(function(res) {
      callback(res.results);
    })
    .catch(function(error) {
      console.error(error);
    });
}

var layout = {
  xaxis: {
    type: 'date',
    title: 'Date'
  },
  yaxis: {
    title: 'Value'
  },
  title: 'My Variable'
};

Plotly.plot('plotlyDiv', [], layout);

getDataFromVariable(VARIABLE, TOKEN, function(values) {
  var trace = {
    x: [],
    y: [],
    mode: 'lines',
    type: 'scatter',
    name: '2000'
  };
  values.forEach(function(value) {
    trace.x.push(value.timestamp);
    trace.y.push(value.value);
  });

  Plotly.addTraces('plotlyDiv', trace);
});
```

**Third-party libraries**:

```url
https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.4/plotly.min.js
```

![](/files/oYT8cjMpEEmJHfZI3oyX)

### Example #4

This example uses the HTML Canvas widget to turn a dashboard's "real time" on or off.

**HTML**:

```html
<div class="notification--container" id="notification">
	<p>Dashboard Real Time</p>
</div>
<div class="real-time--container">
	<button type="button" class="real-time--button" id="real-time">Enable real time</button>
</div>
```

**CSS**:

```css
.real-time--container {
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}

.real-time--button {
background: #FFFFFF;
border: 2px solid #28AECD;
border-radius: 20px;
color: #28AECD;
font-size: 20px;
margin-top: 10px;
width: 100%;
}

.real-time--button:hover {
background: #28AECD;
cursor: pointer;
color: #FFFFFF;
}

.notification--container {
display: none;
}
```

**JavaScript**:

```javascript
var realTime = document.getElementById('real-time');
var notification = document.getElementById('notification');

realTime.addEventListener('click', function() {
  ubidots.setRealTime(true);
  notification.style.display = 'block';
});
```

<figure><img src="/files/wAxsHc06Y2CitbT9654V" alt=""><figcaption></figcaption></figure>


# Create an LCD screen with the HTML Canvas

This example shows how to create an LCD screen on a dashboard using the HTML Canvas widget.

#### HTML:

```html
<div id="image_background">
<p>Last value:</p>
<span id="image_background_text">No data</span>
</div>
```

#### CSS:

```css
@import url('https://fonts.googleapis.com/css?family=VT323');

#image_background {
background: url('https://i.imgur.com/uuYygjz.png') 0 / contain no-repeat;
font-family: "VT323";
font-size: 25px;
letter-spacing: 10.5px;
height: 220px;
width: 450px;
}

#image_background p, #image_background_text {
margin: 0;
left: 75px;
position: absolute;
}

#image_background p {
top: 88px;
font-size: 25px !important;
}

#image_background_text {
top: 121px;
}

```

#### JavaScript:

In the following code, set `VAR_ID` to the variable ID to display:

```javascript
var bg = document.getElementById("image_background");
var text = document.getElementById("image_background_text");
var lastValue = null;
var TOKEN = ubidots.token;
var VAR_ID = "variable-id";

setInterval(function () { getLastValue(VAR_ID, TOKEN); }, 2000);

function makeHttpRequest(method, url, headers, payload) {
  return new Promise(function (resolve, reject) {
    var options = {
      method: method,
      headers: headers,
    };

    if (payload) {
      options.body = JSON.stringify(payload);
    }

    fetch(url, options)
      .then(function (response) {
        if (!response.ok) {
          throw new Error(response.statusText);
        }
        return response.json();
      })
      .then(function (data) {
        resolve(data);
      })
      .catch(function (error) {
        reject(error);
      });
  });
}

function changeImage(value) {
  var choose = "normal";
  var background = {
    blue: "https://i.imgur.com/ZE0W7Yx.png",
    normal: "https://i.imgur.com/uuYygjz.png",
    yellow: "https://i.imgur.com/RHvDkUE.png",
  };
  if (value <= 30) {
    choose = "blue";
    bg.style.color = "white";
  } else if (value > 30 && value <= 70) {
    choose = "yellow";
    bg.style.color = "white";
  }
  bg.style.background = "url(" + background[choose] + ") 0 / contain no-repeat";
}

function getLastValue(variableId, token) {
  var url =
    "https://industrial.api.ubidots.com/api/v1.6/variables/" +
    variableId +
    "/values";
  var headers = {
    "X-Auth-Token": token,
    "Content-Type": "application/json",
  };

  makeHttpRequest("GET", url + "?page_size=1", headers)
    .then(function (res) {
      if (lastValue === null || res.results[0].value !== lastValue) {
        text.textContent = res.results[0].value;
        lastValue = res.results[0].value;
        changeImage(lastValue);
      }
    })
    .catch(function (error) {
      console.error(error);
    });
}
```

Once you save the settings, it looks like this:

<figure><img src="/files/AZgdJUoZC48JB3I3KqEE" alt="" width="563"><figcaption></figcaption></figure>

### Adjusting the screen's contrast

Update the HTML to:

```html
<div id="image_background">

<img id="display_background" src="https://i.imgur.com/meoKhMG.png" />

<p>Last value:</p>
<span id="image_background_text">No data</span>
</div>
```

Update the CSS to:

```css
@import url('https://fonts.googleapis.com/css?family=VT323');

#image_background {
background: url('https://i.imgur.com/ehNdUxY.png') 0 / contain no-repeat;
font-family: "VT323";
font-size: 25px;
letter-spacing: 10.5px;
height: 220px;
width: 450px;
}

#image_background p, #image_background_text {
margin: 0;
left: 75px;
position: absolute;
}

#image_background p {
top: 88px;
font-size: 25px !important;
}

#image_background_text {
top: 121px;
}

#display_background {
height: 94px;
position: absolute;
top: 70px;
left: 52px;
pointer-events: none;
}
```

Update the JavaScript to:

```javascript
var bg = document.getElementById("display_background");
var text = document.getElementById("image_background_text");
var lastValue = null;
var lastContrast = null;
var TOKEN = ubidots.token;
var VAR_ID_LV = "id-of-variable-to-be-displayed";
var VAR_ID_CONTRAST = "id-of-contrast-variable";
var scale = d3.scaleLinear().range([0, 4]).domain([0, 1023]);


setInterval(function () {
    // Get LCD Screen last value
    getLastValue(VAR_ID_LV, TOKEN, function (res) {
      var value = null;
      try {
        value = res.results[0].value;
      } catch (e) {
        console.log("No data");
      }
      if (
        (lastValue === null || value !== lastValue) &&
        value !== null
      ) {
        text.textContent = value;
        lastValue = value;
      }
    });

    // Get LCD Screen contrast
    getLastValue(VAR_ID_CONTRAST, TOKEN, function (res) {
      var value = null;
      try {
        value = res.results[0].value;
      } catch (e) {
        console.log("No data");
      }
      if (
        (lastContrast === null || value !== lastContrast) &&
        value !== null
      ) {
        bg.style.filter = "contrast(" + scale(value) + ")";
        lastContrast = value;
      }
    });
  }, 2000);


function makeHttpRequest(method, url, headers, payload) {
  return new Promise(function (resolve, reject) {
    var options = {
      method: method,
      headers: headers,
    };

    if (payload) {
      options.body = JSON.stringify(payload);
    }

    fetch(url, options)
      .then(function (response) {
        if (!response.ok) {
          throw new Error(response.statusText);
        }
        return response.json();
      })
      .then(function (data) {
        resolve(data);
      })
      .catch(function (error) {
        reject(error);
      });
  });
}

function getLastValue(variableId, token, cb) {
  var url =
    "https://industrial.api.ubidots.com/api/v1.6/variables/" +
    variableId +
    "/values";
  var headers = {
    "X-Auth-Token": token,
    "Content-Type": "application/json",
  };

  makeHttpRequest("GET", url + "?page_size=1", headers)
    .then(function (res) {
      if (typeof cb === "function") cb(res);
    })
    .catch(function (error) {
      console.error(error);
    });
}

```

Lastly, add the third-party library required for this implementation:

```
https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.0/d3.min.js
```

Result:

<figure><img src="/files/Fxui4eRZCvarYUqZJQjJ" alt=""><figcaption></figcaption></figure>


# Interacting with dashboard data

### Example #1

This example uses Ubidots listener events in an HTML Canvas widget to extract information and display it on the dashboard.

**HTML**:

```html
<div>
    <p id="token"></p>
    <p id="dashboard"></p>
    <p id="dateRange"></p>
    <p id="deviceSelected"></p>
    <p id="device"></p>
    <p id="isRealTimeActive"></p>
    <p id="dashboardRefreshed"></p>
    <p id="eventToken"></p>
    <p id="eventDashboard"></p>
    <p id="dashboardDateRange"></p>
    <p id="eventDeviceSelected"></p>
    <p id="eventDevice"></p>
    <p id="eventRefresh"></p>
    <p id="eventisRealTimeActive"></p>
</div>
```

**JavaScript**:

```javascript
var ubidots = new Ubidots();

ubidots.on('receivedToken', function (data) {
    document.getElementById('eventToken').innerText = "TOKEN: " + data;
});

ubidots.on('selectedDashboardObject', function (data) {
    document.getElementById('eventDashboard').innerText = "Dashboard object: " + JSON.stringify(data)
});

ubidots.on('selectedDashboardDateRange', function (data) {
    document.getElementById('dashboardDateRange').innerText = "Dashboard date range: " + JSON.stringify(data);
});

ubidots.on('selectedDevices', function(data) {
    let listOfIds = data.join(', ');
    document.getElementById('eventDeviceSelected').innerText = "Selected device IDs: " + listOfIds;
});

ubidots.on('selectedDeviceObjects', function (data) {
    let formattedData = JSON.stringify(data); // 2-space indentation
    document.getElementById('eventDevice').innerText = "Device object: \n" + formattedData;
});

ubidots.on('isRealTimeActive', function (data) {
    document.getElementById('eventisRealTimeActive').innerText = "Real-time button: " + JSON.stringify(data);
});

ubidots.on('dashboardRefreshed', function (data) {
    document.getElementById('eventRefresh').innerText = "The dashboard refresh button was pressed."
});
```

<figure><img src="/files/Mbgu0t1PQMcvq37yF7BX" alt=""><figcaption></figcaption></figure>

### Example #2

This example uses setter events and a third-party library to set the device in a dynamic dashboard.

**HTML**:

```html
<div class="notification--container" id="notification">
<p>Device set successfully</p>
</div>

<div class="set-device--container">
<input type="text" placeholder="Enter device ID" class="set-device--input" id="device" />
<button type="button" class="set-device--button" id="send-value">Set device</button>
</div>
```

**CSS**:

```css
.set-device--container {
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}

.set-device--input {
border: none;
border-bottom: 2px solid #28AECD;
display: block;
margin: 0 auto;
font-size: 14px;
outline: none;
}

.set-device--button {
background: #FFFFFF;
border: 2px solid #28AECD;
border-radius: 20px;
color: #28AECD;
font-size: 14px;
margin-top: 10px;
width: 100%;
}

.set-device--button:hover {
background: #28AECD;
cursor: pointer;
color: #FFFFFF;
}

.notification--container {
display: none;
}
```

**JavaScript**:

```javascript
var $setDevice = $('#send-value');
var $notification = $('#notification');

var ubidots = new Ubidots();

$setDevice.on('click', function () {
$device = $('#device'); 
ubidots.setDashboardDevice($device.val())
$notification.show();
});
```

**Third-party library**:

```
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
```

<figure><img src="/files/2kur8UBdNMp5qmZQ0LHN" alt=""><figcaption></figcaption></figure>


# Change the header's custom style

This example shows how to dynamically change a widget's header style when a user clicks a button in the HTML Canvas widget.

* Create a button element in the HTML tab. Paste in the following code:

```html
<button id="color">Click here to change header styling</button>
```

* Add the logic to change the header style when the button is clicked:

```javascript
const button = document.querySelector("#color");
button.addEventListener("click", () => {
    const event = new CustomEvent("widget_changeHeaderColor", {
        detail:{
            "backgroundColor": "#00bcd4",
            "borderRadius": 7,
            "color": "#ffffff",
            "boxShadow": "3px 3px 5px 6px #cccccc",
            "fontWeight": "bold"
        }
    });
    document.dispatchEvent(event);
});
```

Save the widget settings by clicking the green check mark. The result is shown below:

<figure><img src="/files/pTvbYKctgJARWBP5WCZN" alt=""><figcaption></figcaption></figure>


# Adding real time using Socket.IO

Using the socket.IO library, create a simple real-time widget with Ubidots and the HTML Canvas.

[Socket.IO](http://socket.io) is a JS library easy to implement that manages socket connections and messages, thus *enabling a bi-directional real-time connection protocol.* Ubidots has implemented this real-time protocol within its core servers to easily update devices, to retrieve and store data inside any JS variable, and without the need to implement any requests, using sockets. [Socket.IO](http://socket.io) is an easy-to-implement JavaScript library that manages socket connections and messages, enabling a bi-directional real-time connection protocol. Ubidots implements this real-time protocol in its core servers, so you can easily update devices and retrieve or store data inside any JavaScript variable without making requests.

The following example shows how to create an HTML Canvas widget that displays a variable's data in real time. This example builds on [Create an LCD screen with the HTML Canvas](/getting-started/sending-your-first-data-points), so complete that section first.

Create the HTML element where the data will be displayed:

```html
<p id="content"></p>
```

Add the widget logic with the following JavaScript code:

```javascript
var socket;
var srv = window.location.hostname + ":443"; //this allows the widget to work at the white label level
var VAR_ID = "variable-id"; // Put here your var Id
var TOKEN = "YOUR-TOKEN"  // Put here your token
$(document).ready(function () {
    // Implements the connection to the server
    socket = io.connect("https://" + srv, { path: '/notifications' });
    var subscribedVars = [];

    // Function to publish the variable ID
    var subscribeVariable = function (variable, callback) {
        // Publishes the variable ID that wishes to listen
        socket.emit('rt/variables/id/last_value', {
            variable: variable
        });
        // Listens for changes
        socket.on('rt/variables/' + variable + '/last_value', callback);
        subscribedVars.push(variable);
    };

    // Function to unsubscribed for listening
    var unSubscribeVariable = function (variable) {
        socket.emit('unsub/rt/variables/id/last_value', {
            variable: variable
        });
        var pst = subscribedVars.indexOf(variable);
        if (pst !== -1) {
            subscribedVars.splice(pst, 1);
        }
    };

    var connectSocket = function () {

        // Implements the socket connection
        socket.on('connect', function () {
            socket.emit('authentication', { token: TOKEN });
        });
        window.addEventListener('online', function () {
            socket.emit('authentication', { token: TOKEN });
        });
        socket.on('authenticated', function () {
            subscribedVars.forEach(function (variable_id) {
                socket.emit('rt/variables/id/last_value', { variable: variable_id });
            });
        });
    }

    /* Main Routine */

    connectSocket();
    // Should try to connect again if connection is lost
    socket.on('reconnect', connectSocket);

    // Subscribe Variable with your own code.
    subscribeVariable(VAR_ID, function (value) {
        var parsedValue = JSON.parse(value);
        console.log(parsedValue);
        $('#content').text(value);
    })
});
```

You will need the following third-party libraries:

```url
https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
https://cdn.jsdelivr.net/npm/socket.io-client@4.8.1/dist/socket.io.min.js
```

The result looks like this:

<figure><img src="/files/88fpH4HeyekZ6AbzTig5" alt="" width="563"><figcaption></figcaption></figure>


# Delete variable data from a device

Learn how to use an HTML Canvas widget to delete variable data from a device within a selected time window in Dynamic Dashboards.

Use this example to build an HTML Canvas widget that deletes a device variable's values within a selected time window.

* Create the structure. Paste the following into the HTML code editor tab:

```html
<div>
    <p id="token"></p>
    <p id="selectedDeviceID"></p>
	<p id="selectedDeviceLabel"></p>
	<select id = "labels"></select>
	<form id="date-form">
		<label for="date1">Start date:</label>
		<input type="datetime-local" id="date1" name="date1" value="2023-01-01T00:00" required>
		<br>
		<label for="date2">End date:</label>
		<input type="datetime-local" id="date2" name="date2" value="2023-01-01T12:00" required>		
		<br>
		<button type="submit">Delete values</button>
	</form>
</div>
```

* Paste the following into the CSS code editor tab:

  ```css
  body {
    font-family: Arial, sans-serif;
    max-width: 400px;
    margin: 0 auto;
    padding: 2rem;
  }

  form {
    display: flex;
    flex-direction: column;
  }

  label, input {
    margin-bottom: 1rem;
  }

  button {
    cursor: pointer;
    background-color: #0077cc;
    color: white;
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    font-size: 1rem;
    font-weight: bold;
  }

  button:hover {
    background-color: #005aa3;
  }
  ```
* Paste the following into the JavaScript code editor tab:

```javascript
var ubidots = new Ubidots();
const BASE_URL = 'https://industrial.api.ubidots.com';
const API1 = '/api/v1.6';
const API2 = '/api/v2.0';
var token;
var deviceLabel;
var deviceID;
var firstTime = true;
var deviceVariables;
var dropdown = document.getElementById('labels');
var selectedVariableLabel;
var selectedVariableID;


ubidots.on('ready', async function () 
{
	token = ubidots.token;
	deviceID = ubidots.selectedDevice;
	deviceLabel = await getDeviceLabelById(deviceID, token);
	deviceVariables = await getDeviceVariables(deviceID, token);
	deleteDropdownItems(dropdown);
	populateDropdownItems(deviceVariables, dropdown);
	displayDeviceData(deviceID, deviceLabel, token);
	firstTime = false;
});

ubidots.on('selectedDevice', async function (data) 
{
	deviceID = data[0];
	displayDeviceID(deviceID);
	if(!firstTime)
	{
		deviceLabel = await getDeviceLabelById(deviceID, token);
		deviceVariables = await getDeviceVariables(deviceID, token);
		deleteDropdownItems(dropdown);
		populateDropdownItems(deviceVariables, dropdown);
		displayDeviceData(deviceID, deviceLabel, token);
	}
    
});

dropdown.addEventListener("change", function() 
{
  	//selectedVariableLabel = this.options[this.selectedIndex].value;
	const este = this.options[this.selectedIndex].value;
	selectedVariableID = este.split(":")[1].trim();
	selectedVariableLabel = este.split(":")[0].trim();

});

document.getElementById('date-form').addEventListener('submit', async function(event)
{
	// Prevents form submission and page reload
	event.preventDefault();

	//Estas son las fechas que la persona pone en los campos
	const ts1 = toTimestamp(document.getElementById('date1').value);
	const ts2 = toTimestamp(document.getElementById('date2').value);
	if(ts1 > ts2)
	{
		console.error("ERROR: end date being earlier than start date.", ts1, ts2);
		console.log("startDate: ", ts1);
		console.log("endDate: ", ts2);
		return {"ERROR": -1};
	}
	
	console.log(selectedVariableID);
	console.log("startDate: ", ts1);
	console.log("endDate: ", ts2);	
	//url quemada con el endpoint, el device y los timestamps
	var url = "https://industrial.api.ubidots.com/api/v2.0/variables/" + selectedVariableID + "/_/values/delete/?startDate=" + ts1 + "&endDate=" + ts2;
	console.log("URL: ", url);
	const response = await fetchWrapper(url, 'POST', token);
	console.log(response);

});

function displayToken(Token)
{
	document.getElementById('token').innerText = "TOKEN: " + Token;
}
function displayDeviceLabel(DeviceLabel)
{
	document.getElementById('selectedDeviceLabel').innerText = "Selected Device label: " + DeviceLabel;
}

function displayDeviceID(DeviceID)
{
	document.getElementById('selectedDeviceID').innerText = "Selected Device id: " + DeviceID;
}

function displayDeviceData(DeviceID, DeviceLabel, Token, VariableLabel)
{
	displayToken(Token);
	displayDeviceLabel(DeviceLabel);
	displayDeviceID(DeviceID);
}

function populateDropdownItems(items, dropdownObj)
{
	var isFirst = true;
	items.forEach(function(item)
	{
		if(isFirst)
		{
			selectedVariableID = item.id;
			selectedVariableLabel = item.label;
		}
		var option = document.createElement('option');
  		option.text = item.label + " : " + item.id;
  		dropdownObj.add(option);
		isFirst = false;
	});
}

function deleteDropdownItems(dropdown)
{
	while (dropdown.firstChild) 
	{
    	dropdown.removeChild(dropdown.firstChild);
  	}
}

async function getDeviceLabelById(DeviceID, Token)
{
    var url = `${BASE_URL}${API2}/devices/${DeviceID}/?fields=label`;
	var response = await fetchWrapper(url, 'GET', Token);
	return response.label;
}

async function deleteDevicesValues(DeviceID, Token)
{
	
}

async function getDeviceVariables(DeviceID, Token)
{
	const url = 'https://industrial.ubidots.com/api/v2.0/devices/' + deviceID + '/variables';
	const response = await fetchWrapper(url, 'GET', Token);
	return response.results;
}

async function fetchWrapper(url, method, token = null, payload = null)
{
	const options =
	{
    	method: method,
    	//headers: {},
		headers: {'Content-Type' : 'application/json'}
	};

	//options.headers['Content-Type'] = 'application/json';

	if (payload)
	{
    	options.body = JSON.stringify(payload);
  	}

  	if (token)
	{
    	options.headers['X-Auth-Token'] = token;
  	}

	const response = await fetch(url, options);
	const jsonData = await response.json();
	return jsonData;
}

function toTimestamp(date) 
{
	const dateObj = new Date(date);
	const timestamp = dateObj.getTime();
	return timestamp;
}
```

The result is a widget like this:

<figure><img src="/files/lWokWnTRSHhJXex3ZlS9" alt="" width="563"><figcaption></figcaption></figure>

Use the dropdown to select a variable from the current device in the Dynamic Dashboard. Then choose a time window with the date pickers.

When you click **Delete values**, Ubidots deletes that variable's data for the selected time window.

Ubidots runs this action asynchronously. You may need to wait a few minutes before the values disappear.


# Delete variable data from Device Groups or Types

Use this example to build an HTML Canvas widget that lets you select a variable label, choose a time window, and delete the matching data.

Use this example to build an HTML Canvas widget that deletes variable data from **Device Types** and **Device Groups.**

Ubidots supports two device grouping types: **Device Types** and **Device Groups**.

* Create the structure. Paste the following into the HTML code editor tab:

```html
<p id="deviceLabelMessage"></p>

<div id="inputContainer"></div>

<form id="date-form">
	<label for="date1">Start date:</label>
	<input type="datetime-local" id="date1" name="date1" value="2023-01-01T00:00" required>
	<br>
	<label for="date2">End date:</label>
	<input type="datetime-local" id="date2" name="date2" value="2023-01-01T12:00" required>
	<br>
	<button type="submit">Delete values</button>
</form>
```

This creates the date selectors, the variable input, and the submit button.

* Set the logic. Paste the following into the JavaScript tab:

```javascript
var ubidots = new Ubidots;
var dashboardID;
var ubidotsToken;
var selectedVariableID;
var selectedVariableLabel;
var dashboardObject;
var inputContainer;
var displayedMsg;
var firstTime = true;


ubidots.on('ready', async function()
{
	//obtain the required variables
    dashboardID = ubidots.dashboardObject.id;
    ubidotsToken = ubidots.token;
	
	//get the dashboard object from ubidots api
    dashboardObject = await getDashboardObject(dashboardID, ubidotsToken);
	//get the element containing either the dropdown menu or the input text
    inputContainer = document.getElementById("inputContainer");  
	//get the elemente displaying the message
    displayedMsg = document.getElementById("deviceLabelMessage");
	//change the widget so it displays either a dropdown menu or an input type
    await setGUIAccordingToDeviceTypeFilter(dashboardObject, inputContainer, displayedMsg);
	firstTime = false;
	console.log("EXIT");
});

ubidots.on('selectedDevice', async function (data) 
{
	if(!firstTime)
	{
		//selectedVariableLabel = getVariableLabelFromContainer(inputContainer);
		deleteDropdownItems(inputContainer);
		await setGUIAccordingToDeviceTypeFilter(dashboardObject, inputContainer, displayedMsg);

		//populateDropdownItems(deviceVariables, dropdown);
		//displayDeviceData(deviceID, deviceLabel, token);
	}
});



document.getElementById('date-form').addEventListener('submit', async function(event)
{
	event.preventDefault();
	
	const ts1 = toTimestamp(document.getElementById('date1').value);
	const ts2 = toTimestamp(document.getElementById('date2').value);
	
	if(!assertTimestamps(ts1, ts2))
	{
		return false;
	}

	selectedVariableLabel = getVariableLabelFromContainer(inputContainer);
	console.log(selectedVariableLabel);
	
	const devicesInDashboardIDs = await getDashboardDevicesIDs(dashboardID, ubidotsToken);
	const req = await isTypesOrGroups(dashboardObject, deleteVariableFromGroup, deleteVariableFromGroup, devicesInDashboardIDs, ubidotsToken, selectedVariableLabel, ts1, ts2);
	console.log(req);
});

function populateDropdownItems(items, dropdownObj)
{
	var isFirst = true;
	items.forEach(function(item)
	{
		var option = document.createElement('option');
  		option.text = item.label + " : " + item.id;
  		dropdownObj.add(option);
		isFirst = false;
	});
}


function deleteDropdownItems(dropdown)
{
	while (dropdown.firstChild) 
	{
    	dropdown.removeChild(dropdown.firstChild);
  	}
}

function assertTimestamps(ts1, ts2)
{
	if(ts1 > ts2)
	{
		console.error("ERROR: end date being earlier than start date.", ts1, ts2);
		console.log("startDate: ", ts1);
		console.log("endDate: ", ts2);
		return false;
	}
	return true;
}

async function deleteVariableFromGroup(deviceID = [], ubidotsToken, variableLabel, ts1, ts2)
{
	const promisesList = [];
	await Promise.all(deviceID.map(async (id) => 
	{
  		const deviceVariables = await getDeviceVariables(id, ubidotsToken);
 		deviceVariables.forEach(async (variable) => 
		{
    		if(variable.label === variableLabel) 
			{
				const req = await deleteVariable(variable.id, ts1, ts2, ubidotsToken);
				promisesList.push(req);
    		}
  		});
	}));
	return promisesList;
}

async function isTypes(deviceID = [], ubidotsToken)
{
	console.log("ENTRY");
	return {"msg":1};
}

async function deleteVariable(variableID, ts1, ts2, ubidotsToken)
{
	var debugMsg = {"var id" : 0, "url": ".", "task id" : 0};
	const url = "https://industrial.api.ubidots.com/api/v2.0/variables/" + 
		variableID + "/_/values/delete/?startDate=" + ts1 + "&endDate=" + ts2;
	const response = await fetchWrapper(url, 'POST', ubidotsToken);
	debugMsg["var id"] = variableID;
	debugMsg["url"] = url;
	debugMsg["task id"] = response;
	return debugMsg;
}

function getVariableLabelFromContainer(container)
{
	var value = container.firstElementChild.value;	
	return value;
}

async function setGUIAccordingToDeviceTypeFilter(dashboardObject, inputContainer, displayedMsg)
{
    if(dashboardObject.context.deviceFilterType == 'device_type')
    {
        var a = await getDeviceVariables(ubidots.selectedDevice, ubidots.token);
        var labels = [];
        a.forEach(element =>
        {
            labels.push(element.label);
        });
    }
    isTypesOrGroups(dashboardObject, populateDropdown, populateTextField, inputContainer, displayedMsg, labels);
}

function populateDropdown(inputContainer, displayedMsg, dropdownElements)
{
    var dummyStr = '';
    dropdownElements.forEach(element =>
    {
        dummyStr = dummyStr + '<option value=\"' + element + '\">' + element + '</option>' + ' ';
    });
    inputContainer.innerHTML = `<select id="inputType">` + dummyStr + `</select>`;
    displayedMsg.innerText = "Select variable";
}

function populateTextField(inputContainer, displayedMsg)
{
	displayedMsg.innerText = "Enter a variable's label";
    inputContainer.innerHTML = `<input type="text" id="inputType">`;
}


function isTypesOrGroups(dashboardObject, typeCallback, groupCallback, ...rest)
{
    if(dashboardObject.context.deviceFilterType == "device_group")
    {
        return groupCallback(...rest);
    }
    else if(dashboardObject.context.deviceFilterType == "device_type")
    {
        return typeCallback(...rest);
    }
    else
        return {"ERROR" : "device filter type not recognized"};
}

async function getDeviceVariables(deviceID, token)
{
	const url = 'https://industrial.ubidots.com/api/v2.0/devices/' + deviceID + '/variables';
	const response = await fetchWrapper(url, 'GET', token);
	return response.results;
}

async function getDashboardObject(dashboardID, ubidotsToken)
{
    const url = 'https://industrial.api.ubidots.com/api/v2.0/dashboards/' + dashboardID;
    const req = await fetchWrapper(url, 'GET', ubidotsToken);
    return req;
}

async function getDashboardDevices(dashboardID, ubidotsToken)
{
    const url = 'https://industrial.ubidots.com/api/-/dashboards/' + dashboardID + '/devices?sort_by=name&page_size=500';
    const method = 'GET';
    const req = await fetchWrapper(url, method, ubidotsToken);
    return req;
}

async function getDashboardDevicesIDs(dashboardID, ubidotsToken)
{
    var req = await getDashboardDevices(dashboardID, ubidotsToken);
    var l = [];

    req.results.forEach(function(item)
	{
        l.push(item.id);
    });
	return l;
  
}

async function getDeviceObject(deviceID, ubidotsToken, filters = null)
{
	var url = 'https://industrial.api.ubidots.com/api/v2.0/devices/' + deviceID + '/';
	url = filters == null ? url : url + filters;
	const request = await fetchWrapper(url, 'GET', ubidotsToken);
	return request;
}

async function fetchWrapper(url, method, token = null, payload = null)
{
	const options =
	{
    	method: method,
		headers: {'Content-Type' : 'application/json'}
	};

	if (payload)
	{
    	options.body = JSON.stringify(payload);
  	}

  	if (token)
	{
    	options.headers['X-Auth-Token'] = token;
  	}

	const response = await fetch(url, options);
	const jsonData = await response.json();
	return jsonData;
}


function toTimestamp(date) 
{
	const dateObj = new Date(date);
	const timestamp = dateObj.getTime();
	return timestamp;
}
```

This implementation displays a different UI depending on whether the current dashboard uses a device type or device group filter. For device type dashboards, the variables shared by the selected device type appear in a dropdown menu. For device group dashboards, a text box appears so the user can enter the label of the variable to delete.\
\
The following GIF shows the widget in both dashboard types:

<figure><img src="/files/pqS1Ss3K2P5CvSldETWv" alt=""><figcaption></figcaption></figure>


# Navigation through dashboard

This example uses the HTML Canvas "Preload dashboard data" feature to implement dashboard navigation.

This dropdown lists all dashboards in your Ubidots account.

When you select one, it opens in a new browser tab.

**HTML**:

```html
<div>
    <h1>Navigate through your Dashboards</h1>
    <select id="dashboardsList"></select>
</div>
```

**JavaScript**:

```javascript
var token = ubidots.token;
const dashboardsDropdown = document.getElementById('dashboardsList');
dashboardsDropdown.addEventListener('change', handleSelectionChange);

getAllDashboards(token).then((data) =>
{
    // Iterate over the data and create options
    const dropdown = document.getElementById('dashboardsList');
    data.results.forEach(item => 
    {
        const option = document.createElement('option');
        option.value = item.id; // Adjust the value according to your data
        option.text = item.label; // Adjust the displayed text according to your data
        option.label = item.label;
        dropdown.appendChild(option);
    });
});

function handleSelectionChange() 
{
    const dashboardID = this.value; // Get the selected value
    const url = `https://industrial.ubidots.com/app/dashboards/${dashboardID}`;
    window.open(url, '_blank');
   
}

async function getAllDashboards(ubidotsToken)
{
    const url = "https://industrial.api.ubidots.com/api/v2.0/dashboards/";
    const method = "GET";
    const req = await fetchWrapper(url, method, ubidotsToken);
    return req;
}

async function fetchWrapper(url, method, token = null, payload = null)
{
	const options =
	{
    	method: method,
		headers: {'Content-Type' : 'application/json'}
	};

	if (payload)
	{
    	options.body = JSON.stringify(payload);
  	}

  	if (token)
	{
    	options.headers['X-Auth-Token'] = token;
  	}

	const response = await fetch(url, options);
	const jsonData = await response.json();
	return jsonData;
}

```

The result looks like this:

<figure><img src="/files/PVdKxAB8bDM9XVu4EPyD" alt=""><figcaption></figcaption></figure>


# Using a React library

This example shows how to import and use Material UI.

Add the [library CDN](https://v4.mui.com/components/buttons/) to the widget's *3rd party libraries*:

```
https://unpkg.com/@material-ui/core@4.11.4/umd/material-ui.development.js
```

In this path, `umd` indicates that the CDN bundle uses the UMD format.

In the code editor's HTML tab, add the following:

```html
<div id="root"></div>
```

Switch to the JavaScript (JSX) tab and paste the following:

```javascript
const { createRoot } = ReactDOM;
const { Button } = MaterialUI;


function App() {
    return (
        <>
            <Button variant="contained" color="primary">
                First button using React and Material UI
            </Button>
            <Button variant="outlined" color="secondary">
                Another button
            </Button>
        </>
    );
}


```

After you save the changes, the widget renders like this:

<figure><img src="/files/bwF7MwIAT9MvKVfH35ba" alt=""><figcaption></figcaption></figure>


# Code editor

The code editor has three tabs:

* HTML
* CSS
* JavaScript

Use each tab to write code in its corresponding language.

You can even use all three languages in the same file:

```html
<!DOCTYPE html>
<html>
<head>
<style>
body {
   background-color: lightblue;
}
h1 {
   color: white;
   text-align: center;
}
p {
   font-family: verdana;
   font-size: 20px;
}
</style>
</head>
<body>
<script>
  // Some javascript code
</script>
<h1> This is a h1 header </h1>
<p> This is a paragraph </p>
</body>
</html>
```

However, best practice is to separate the code into three files, one for each language. That is why the widget's code editor includes a dedicated tab for each one.


# HTML tab

<figure><img src="/files/Jm87p4rJAaqdfCxHGlij" alt=""><figcaption></figcaption></figure>

HTML (Hypertext Markup Language) is the standard markup language used to structure web pages. By using HTML elements, you tell the browser where and how to display content.

### Examples

* `<title> Page Title </title>`: This element tells the browser to display the "Page Title" text as the title of the web page.
* `<h1> This is a h1 header </h1>`: This element tells the browser to display the "This is a h1 header" message as an H1 header wherever it is placed in the HTML file.
* `<p> My first paragraph </p>`: This element tells the browser to display the "My first paragraph" message as a paragraph wherever it is placed in the HTML file.

### Typical HTML structure

Every HTML file requires the following structure:

```html
<html>
<head>
</head>
<body>
</body>
</html>
```

### HTML structure on the HTML Canvas widget

When you use HTML elements on the HTML Canvas widget, the following tags are optional:

* `<html></html>`
* `<head></head>`
* `<body></body>`

This means that all code placed in the HTML code editor tab is interpreted as if it were inside an HTML `<body>` element on the widget:

<table><thead><tr><th width="355" align="center">Typical HTML file</th><th align="center">Ubidots equivalent HTML inside the widget</th></tr></thead><tbody><tr><td align="center"><code>&#x3C;html></code><br><code>&#x3C;head></code><br><code>&#x3C;/head></code><br><code>&#x3C;body></code><br><code>&#x3C;h1> This is a h1 header &#x3C;/h1></code><br><code>&#x3C;p> This is a paragraph &#x3C;/p></code><br><code>&#x3C;p> This is another paragraph &#x3C;/p></code><br><code>&#x3C;/body></code><br><code>&#x3C;/html></code></td><td align="center"><code>&#x3C;h1> This is a h1 header &#x3C;/h1></code><br><code>&#x3C;p> This is a paragraph &#x3C;/p></code><br><code>&#x3C;p> This is another paragraph &#x3C;/p></code></td></tr></tbody></table>

### Advanced example

Suppose you want to display the following web content:<br>

<figure><img src="/files/Xh94OFoxusi3oZAbmfPa" alt=""><figcaption></figcaption></figure>

This can be achieved with the following HTML code:

```html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1> Online HTML Editor </h1>
<p> This is a real time online HTML Editor </p>
</body>
</html>
```

To achieve the same result on the HTML Canvas, use the following code in the HTML tab of the code editor:

```html
<h1> Online HTML Editor </h1>
<p> This is a real time online HTML Editor </p>
```

<figure><img src="/files/Jwnrg9YGddCc1vgjFolZ" alt=""><figcaption></figcaption></figure>

The resulting HTML Canvas widget is shown below on a dashboard:

<figure><img src="/files/anHYIFU2bm48kMy5WdDo" alt=""><figcaption></figcaption></figure>


# CSS tab

<figure><img src="/files/ZaZmSr5247szvc2hV6Lt" alt="" width="563"><figcaption></figcaption></figure>

CSS (Cascading Style Sheets) defines styles that apply to HTML elements and customize how they appear in the browser.

### Examples

| CSS Element                                                                                                      | Effect on the corresponding HTML element                                 |
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| <p><code>body {</code><br><code>background-color: lightblue;</code><br><code>}</code></p>                        | Sets the HTML body background color to *lightblue*                       |
| <p><code>h1 {</code><br><code>color: white;</code><br><code>text-align: center;</code><br><code>}</code></p>     | Sets `h1` headings to use the color *white* and centers the text         |
| <p><code>p {</code><br><code>font-family: verdana;</code><br><code>font-size: 20px;</code><br><code>}</code></p> | Sets paragraphs to use the *verdana* font family and a font size of 20px |

### Advanced example

The following HTML + CSS file modifies the styles of the body, `h1`, and paragraph elements.

```html
<!DOCTYPE html>
<html>
<head>
<style>
body {
   background-color: lightblue;
}
h1 {
   color: white;
   text-align: center;
}
p {
   font-family: verdana;
   font-size: 20px;
}
</style>
</head>
<body>
<h1> This is a h1 header </h1>
<p> This is a paragraph </p>
</body>
</html>
```

It results in the following content in the browser:

<figure><img src="/files/VQDifDQLfcIvCiGqjh8D" alt=""><figcaption></figcaption></figure>

To achieve this on the HTML Canvas:

* Add the following code to the HTML code editor tab:<br>

  ```html
  <h1>This is a h1 header</h1>
  <p> This is a paragraph </p>
  ```
* Add the following code to the CSS tab:<br>

  ```css
  body {
    background-color: lightblue;
  }

  h1 {
    color: white;
    text-align: center;
  }

  p {
    font-family: verdana;
    font-size: 20px;
  }
  ```

This is the resulting HTML Canvas widget on a dashboard:

<figure><img src="/files/l7eRjZ2y1NQhkp3R7T8z" alt=""><figcaption></figcaption></figure>


# JavaScript tab

<figure><img src="/files/ZqmKujPXmCODVJH2Qwq4" alt="" width="563"><figcaption></figcaption></figure>

JavaScript is a scripting language that lets users interact with web page elements, define page logic, and dynamically update content.

### Examples

<table><thead><tr><th width="546">JavaScript code</th><th>Action</th></tr></thead><tbody><tr><td><code>const Paragraph = document.querySelector("p");</code></td><td>Selects a paragraph element and stores a reference to it in the <em>Paragraph</em> variable</td></tr><tr><td><code>function updateName()</code><br><code>{</code><br><code>const name = prompt("Enter a new name");</code><br><code>para.textContent = Player 1:</code><br><code>${name};</code><br><code>}</code></td><td>Defines a function that can be called with <em>updateName()</em></td></tr><tr><td><code>Paragraph.addEventListener("click", updateName);</code></td><td>Attaches a click event listener to the Paragraph element so the <em>updateName</em> function runs when the paragraph is clicked</td></tr><tr><td><code>const para = document.getElementById("player");</code></td><td>Gets a reference to the element with the ID "player" from the DOM</td></tr></tbody></table>

### Advanced example #1

Assume the following requirement:

* Create a paragraph element that displays the message "PLAYER 1: name", where *name* is a value set by the user when the paragraph element is clicked.

This can be accomplished with the following HTML + CSS + JavaScript example:

```html
<!DOCTYPE html>
<html>
<head>
  <style>
    p {
      /* set the font family, size, and style */
      font-family: "Helvetica Neue", Helvetica, sans-serif;
      letter-spacing: 1px;
      text-transform: uppercase;
      /* set the border, background, color, and box shadow */
      border: 2px solid rgba(0, 0, 200, 0.6);
      background: rgba(0, 0, 200, 0.6);
      color: rgba(255, 255, 255, 1);
      box-shadow: 1px 1px 2px rgba(0, 0, 200, 0.4);
      /* set the border radius and padding */
      border-radius: 10px;
      padding: 3px 10px;
      /* set the display to inline-block and cursor to pointer */
      display: inline-block;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <!-- add an ID attribute to the <p> element -->
  <p id="player-name">Player 1: Chris</p>
  <script>
    // get a reference to the <p> element using its ID
    const para = document.getElementById("player-name");

    // add a click event listener to the <p> element
    para.addEventListener("click", updateName);

    function updateName() {
      // prompt the user to enter a new name
      const name = prompt("Enter a new name");

      // update the text content of the <p> element
      para.textContent = `Player 1: ${name}`;
    };
  </script>
</body>
</html>
```

<figure><img src="/files/eg0N4hqk711Yl7MgmtRE" alt=""><figcaption></figcaption></figure>

The same result can be reproduced in Ubidots by using the HTML Canvas widget as follows:

* Add the following HTML to the widget's HTML tab:

```html
<!-- add an ID attribute to the <p> element -->
<p id="player-name">Player 1: Chris</p>
```

* Add the following styles to the widget's CSS tab:

```css
p {
      /* set the font family, size, and style */
      font-family: "Helvetica Neue", Helvetica, sans-serif;
      letter-spacing: 1px;
      text-transform: uppercase;
      /* set the border, background, color, and box shadow */
      border: 2px solid rgba(0, 0, 200, 0.6);
      background: rgba(0, 0, 200, 0.6);
      color: rgba(255, 255, 255, 1);
      box-shadow: 1px 1px 2px rgba(0, 0, 200, 0.4);
      /* set the border radius and padding */
      border-radius: 10px;
      padding: 3px 10px;
      /* set the display to inline-block and cursor to pointer */
      display: inline-block;
      cursor: pointer;
}
```

* Add the following code to the widget's JavaScript tab:

```javascript
// get a reference to the <p> element using its ID
const para = document.getElementById("player-name");

// add a click event listener to the <p> element
para.addEventListener("click", updateName);

function updateName() 
{
    // prompt the user to enter a new name
    const name = prompt("Enter a new name");

    // update the text content of the <p> element
    para.textContent = `Player 1: ${name}`;
};
```

This is the result on a Ubidots dashboard:

<figure><img src="/files/CoidycLlnWbPM0ZOInhe" alt="" width="563"><figcaption></figcaption></figure>


# Content Security Policy (CSP) restrictions

Security guidelines and coding restrictions enforced by the Content Security Policy (CSP) when developing custom code in HTML Canvas widgets and Ubidots Pages.

### Content Security Policy (CSP) restrictions

Ubidots enforces a Content Security Policy (CSP) on application domains to protect end users against cross-site scripting (XSS) and code-injection attacks. This policy applies to all custom code running inside an application, which includes both **HTML Canvas widgets** and **Ubidots Pages**.

Because of this policy, some coding patterns that browsers would normally allow are blocked at the application level. Code that doesn't follow the guidelines below may silently fail to execute, even if it works in a local environment or in the widget's preview.

> **Note:** The policy is enforced per application domain. A widget or page may behave differently on `industrial.ubidots.com` than on your white-label application domain (e.g., `app.yourcompany.com`). Always test your custom code on the same domain your end users access.

#### 1. Inline JavaScript is not executed

The CSP does not allow inline scripts. This means the following patterns will be blocked:

* Inline event handler attributes in HTML, such as `onclick`, `onchange`, `onsubmit`, `onload`, etc.
* `<script>` blocks written directly inside the HTML tab (or inside a Page's HTML).
* `javascript:` URLs in links or buttons.

**All JavaScript must live in the JavaScript tab** of the HTML Canvas (or the JavaScript section of a Page), and event handlers must be attached programmatically using `addEventListener`.

❌ **Blocked — inline handler in the HTML tab:**

```html
<button id="export-btn" onclick="exportData()">Export</button>
```

✅ **Allowed — element in the HTML tab, logic in the JavaScript tab:**

```html
<!-- HTML tab -->
<button id="export-btn">Export</button>
```

```javascript
// JavaScript tab
document.getElementById('export-btn').addEventListener('click', exportData);

function exportData() {
  // Same logic as before — no functional changes required
}
```

Migrating from inline handlers to event listeners requires no changes to your business logic: the same functions are invoked, only the way they are bound to the DOM changes.

#### 2. Avoid dynamic code evaluation

Avoid APIs that compile code from strings, as they may be blocked by the policy:

* `eval()`
* `new Function('...')`
* `setTimeout('...' /* string */)` and `setInterval('...' /* string */)` — always pass a function reference instead of a string.

#### 3. Loading external scripts and libraries

* Load third-party libraries through the widget's **Third-party libraries** setting (see [Third-party packages](https://claude.ai/dashboards-and-widgets/html-canvas/3rd-party-packages.md)) rather than injecting `<script>` tags from the HTML tab.
* External scripts must be served over **HTTPS**. Scripts loaded over HTTP will be blocked.
* Some external script sources may be restricted on hardened application domains. If a library or external API (for example, a maps or charting provider) fails to load for end users, check the browser console for CSP errors and contact Ubidots support so the required origin can be reviewed.

#### 4. Styles

Inline styles (the `style` attribute and `<style>` blocks) are currently permitted. However, we recommend keeping all styling in the **CSS tab** for maintainability and to future-proof your widgets against further policy hardening.

#### Troubleshooting CSP issues

If a button stops responding, a modal opens empty, or part of your widget/page does not render, open the browser's developer console (F12 → Console). CSP violations are reported with messages such as:

<figure><img src="/files/OJ7C1UGAexwPoABA44jY" alt=""><figcaption></figcaption></figure>

```
Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self' ..."
```

or

```
Refused to load the script 'https://example.com/lib.js' because it
violates the following Content Security Policy directive: ...
```

Common fixes:

| Symptom                                                                     | Likely cause                            | Fix                                                                                                      |
| --------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| A button or control does nothing when clicked                               | Inline `onclick`/`onchange` handler     | Move the logic to the JavaScript tab and bind it with `addEventListener`                                 |
| A `<script>` block in the HTML tab never runs                               | Inline script blocked                   | Move the code to the JavaScript tab                                                                      |
| An external library or map fails to load for end users but works for owners | Script origin blocked on the app domain | Verify the HTTPS source, load it via *Third-party libraries*, and contact support if it is still blocked |
| Code works in preview but not in the published app                          | Different CSP between domains           | Test on the end-user application domain                                                                  |


# Custom UI

This article covers the Custom UI widget, its features, and how to use it.

Custom UI is a Ubidots widget that maps XML-like tags to form elements. Use it to create interactive forms with text fields, dropdown menus, and toggle buttons.

Here is a quick example of a simple UI created with the Custom UI widget:

<figure><img src="/files/EZShYikw74AAAmIQW6IZ" alt="" width="563"><figcaption></figcaption></figure>

**The following XML maps to the UI shown above:**

```xml
<form>

    <paragraph type="h1">Send data form</paragraph>

    <inputcombo
        type="text"
        id="variable_label"
        label="Variable label"
        description="Choose the variable to send data to"
        placeholder="Enter the name of the variable"
    />

    <inputcombo
        type="number"
        id="variable_value"
        label="Variable value"
        description="Set the value that you want to send"
        placeholder="Enter the value that you want to send"
        min="0" 
        max="100"
    />

    <inputcombo
        type="date"
        id="start_date"
        label="Date"
        description="Set the date on which the measurement was performed"
        placeholder="Set the date"
    />

    <inputcombo
        type="number.buttons"
        id="variable_value_button"
        label="Variable value using buttons"
        description="Set the value that you want to send using buttons"
        placeholder="Enter the value that you want to send"
        min="0"
        max="100"
    />
    
    <inputcombo
        type="toggle"
        id="enable_device"
        label="Enable device"
        description="Set the state of the device"
    />

    <inputcombo
        type="hour"
        id="start_hour"
        label="Start Time"
        description="Set the hour at which the measurement was performed"
    />
    
    <inputcombo
        type='dropdown.list'
        id='variable_from_list'
        label="Select a variable"
        description="Select a common variable from all devices"
        placeholder='Choose an option'
    >
       <menu>
		   <item id='temperature'>Temperature</item>
		   <item id='humidity'>Humidity</item>
		   <item id='iluminance'>Iluminance</item>
		   <item id='magnetic_flux'>Magnetic flux</item>
      </menu>
    </inputcombo>
    
    <inputcombo
        type='dropdown.checkbox'
        id='devices_from_list'
        label="Select multiple options"
        description="Select multiple devices to send data"
        placeholder='Choose multiple options'
    >
        <menu>
            <item id='dev_1'>device 1</item>
            <item id='dev_2'>device 2</item>
            <item id='dev_3'>device 3</item>
            <item id='dev_4'>device 4</item>
        </menu>
    </inputcombo>

    <button type="success" click="make_request">Send</button> 

</form>
```

## Available UI Components

There are three main components that can be used within the Custom UI widget:

* Paragraph
* Input combo
* Button

| Custom UI element | Description                                           | Properties                                                                                                                                                                                                                                                                                                  |
| ----------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Input combo       | Displays an input field based on the `type` property. | <ul><li>type: the type of input field</li><li>id: a unique identifier used to access this field</li><li>label: the text label shown next to the input</li><li>description: a short description shown below the label to guide the user</li><li>placeholder: the text shown inside the input field</li></ul> |
| Paragraph         | Displays headings or paragraphs based on the `type`.  | <ul><li>type: paragraph type</li></ul>                                                                                                                                                                                                                                                                      |
| Button            | Displays a button whose color depends on the `type`.  | <ul><li>type: defines the button color</li><li>click: a user-defined label that serves as a unique ID to link the button to an action.</li></ul>                                                                                                                                                            |

The following screenshot shows each part of the UI displayed at the beginning of this section:

<figure><img src="/files/lyPSGHkKY5Ln03twSA4k" alt=""><figcaption></figcaption></figure>


# Paragraph

Displays headings or paragraphs depending on the \`type\` property.

<figure><img src="/files/jiZSU8tn5VecCV0vASNs" alt="" width="563"><figcaption><p>Paragraph example</p></figcaption></figure>

This element makes it easy to apply different text styles. The following table describes each available paragraph type:

| Paragraph type | Description                                            |
| -------------- | ------------------------------------------------------ |
| normal         | Displays standard text without any special formatting. |
| bold           | Displays text in bold style.                           |
| h1, h2, h3, h4 | Displays the corresponding heading.                    |

## Paragraph type: normal

Displays a paragraph similar to the HTML `<p>` tag.

## Paragraph type: bold

Displays bold text, similar to the HTML `<b>` or `<strong>` tags.

## Paragraph types: h1 to h4

Displays heading text according to the selected heading level. Similar to the HTML `<h1>` to `<h4>` tags.

#### Examples

The following XML maps to the screenshot shown at the beginning of this section.

```xml
<form>
    <paragraph type="normal">NORMAL</paragraph>
    <paragraph type="bold">BOLD</paragraph>
    <paragraph type="h1">H1</paragraph>
    <paragraph type="h2">H2</paragraph>
    <paragraph type="h3">H3</paragraph>
    <paragraph type="h4">H4</paragraph>
</form>
```


# Input combo

Displays an input field based on the \`type\` property.

Input combos share the following properties across all types:

<table><thead><tr><th width="141.33333333333331">Attribute</th><th>Description</th><th>Values</th></tr></thead><tbody><tr><td>type</td><td>The input field type</td><td><ul><li><code>text</code></li><li><code>number</code></li><li><code>number.buttons</code></li><li><code>date</code></li><li><code>hour</code></li><li><code>toggle</code></li><li><code>dropdown.list</code></li><li><code>dropdown.checkbox</code></li></ul></td></tr><tr><td>id</td><td>A unique identifier for this field</td><td>Any user-defined string</td></tr><tr><td>label</td><td>The text label shown next to the input</td><td>Any user-defined string</td></tr><tr><td>description</td><td>Small helper text below the label</td><td>Any user-defined string</td></tr><tr><td>placeholder</td><td>The placeholder text shown inside the input field</td><td>Any user-defined string</td></tr><tr><td>shape</td><td>Used to align the label and the input field</td><td><ul><li><code>compactVertical</code></li><li><code>wideVertical</code></li><li><code>wideHorizontal</code></li></ul></td></tr><tr><td>value</td><td>Sets a default value.</td><td>Depends on the input combo type. Check each section.</td></tr></tbody></table>

{% hint style="warning" %}
When you set the `value` property, it overrides `placeholder`. The field shows the default value instead of the placeholder text.
{% endhint %}

### Examples

```xml
<form>
    <paragraph type="h1">Send data form</paragraph>

    <inputcombo
      description="Choose the variable to which you want to send data"
      id="variable_label"
      label="Variable label"
      placeholder="Enter the label of the variable"
      type="text"
      value="default"
    />

    <inputcombo
      description="Set the value that you want to send"
      id="variable_value"
      label="Variable value"
      max="100"
      min="0"
      placeholder="Enter the value that you want to send"
      step="2"
      type="number"
      value="34"
    />

    <inputcombo
      description="Set the date when the measurement was taken"
      id="start_date"
      label="Date"
      placeholder="Set the date"
      type="date"
      value="2024-07-19T17:30"
    />

    <inputcombo
      description="Set the value that you want to send using buttons"
      id="variable_value_button"
      label="Variable value with buttons"
      max="100"
      min="0"
      placeholder="Enter the value that you want to send"
      type="number.buttons"
      value="50"
    />

    <inputcombo
      description="Set the state of the device"
      id="enable_device"
      label="Enable device"
      type="toggle"
      value="true"
    />

    <inputcombo
      description="Set the time when the measurement was taken"
      id="start_hour"
      label="Start Time"
      type="hour"
      value="21:00"
    />

    <inputcombo
      description="Select a common variable from all devices"
      id="variable_from_list"
      label="Select a variable"
      placeholder="Choose an option"
      type="dropdown.list"
      value="temperature"
    >
      <menu>
        <item id="temperature">Temperature</item>
        <item id="humidity">Humidity</item>
        <item id="iluminance">Illuminance</item>
        <item id="magnetic_flux">Magnetic flux</item>
      </menu>
    </inputcombo>

    <inputcombo
      description="Select multiple devices to send data"
      id="devices_from_list"
      label="Select multiple options"
      placeholder="Choose multiple options"
      type="dropdown.checkbox"
      value="dev_1,dev_2"
    >
      <menu>
        <item id="dev_1">Device 1</item>
        <item id="dev_2">Device 2</item>
        <item id="dev_3">Device 3</item>
        <item id="dev_4">Device 4</item>
      </menu>
    </inputcombo>

    <button click="make_request" type="success">Send</button>
  </form>
```


# Text

Use text inputs within Custom UI widget

<figure><img src="/files/0jmR0lXED7LrosIEHB64" alt=""><figcaption></figcaption></figure>

## Input combo type text <a href="#h_1c49526ab9" id="h_1c49526ab9"></a>

Displays a standard text input field.

## Usage example

```xml
<form>
    <inputcombo
        type="text"
        id="variable_label"
        label="Variable label"
        description="Choose the variable to which you want to send data"
        placeholder="Enter the label of the variable"
        value="default-text"
    />
</form>
```

## Properties

* `id`: Unique identifier for this field
* `label`: Text label shown next to the input
* `description`: Small helper text below the label
* `placeholder`: Placeholder text shown inside the input
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `value`: Sets a default value for the `inputcombo`


# Numeric

Use numeric input fields within the Custom UI widget

<figure><img src="/files/OO3EQF8hJdBNskSruFJD" alt=""><figcaption></figcaption></figure>

## Input combo type number <a href="#h_c24b6ca6c3" id="h_c24b6ca6c3"></a>

Displays a standard input field that accepts only numbers. The decimal mark is a dot (`.`).

## Usage example

```xml
<form>
    <inputcombo
        type="number"
        id="variable_value"
        label="Variable value"
        description="Set the value that you want to send"
        placeholder="Enter the value that you want to send"
        step="2"
        value="48"
    />
</form>
```

## Properties

These properties are shared by input combo type number and input combo type number.buttons:

* `id`: Unique identifier for accessing this field's data
* `label`: Header text
* `description`: Short descriptive text
* `placeholder`: Placeholder value
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `min`: Minimum number accepted
* `max`: Maximum number accepted
* `step`: Step when increasing or decreasing the number using the buttons.
* `value`: Sets a default value. Although this is a number field, the property value must be set as a string (`value="48"`)


# Numeric with buttons

Use numeric input fields with buttons within the Custom UI widget

<figure><img src="/files/oEB25JphlQEM3EgTNIGn" alt=""><figcaption></figcaption></figure>

## Input combo type number.buttons

Displays an input field that accepts only numbers. It also includes a pair of buttons to increment or decrement the current value in the field. The decimal mark is a dot (`.`).

## Usage example

```xml
<form>
    <inputcombo
        type="number.buttons"
        id="variable_value_button"
        label="Variable value button using"
        description="Set the value that you want to send using buttons"
        placeholder="Enter the value that you want to send"
        min="-100"
        max="100"
        step = "2"
        value = "48"
    />
</form>
```

## Properties

These properties are shared by input combo type number and input combo type number.buttons:

* `id`: Unique identifier for accessing this field's data
* `label`: Header text
* `description`: Short descriptive text
* `placeholder`: Placeholder value
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `min`: Minimum number accepted
* `max`: Maximum number accepted
* `step`: Step when increasing or decreasing the number using the buttons.
* `value`: Sets a default value. Although this is a number field, the property value must be set as a string (`value="48"`)


# Date

Use date input fields within the Custom UI widget

<figure><img src="/files/yYliFY29sbQG47u8S1tj" alt=""><figcaption></figcaption></figure>

## Input combo type date

Displays a date input field in the format `mm/dd/yy, HH:MM` (12-hour format).

## Usage example

```xml
<form>
    <inputcombo
        type="date"
        id="start_date"
        label="Date"
        description="Set the date on which the measurement was performed"
        value="2024-07-19T17:30"
    />    
</form>
```

## Properties

* `id`: Unique identifier for this field
* `label`: Header text
* `description`: Short descriptive text
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `value`: Sets a default date. Use the ISO 8601 format: `yyyy-MM-dd'T'HH:mm`


# Time

Use time input fields within the Custom UI widget

<figure><img src="/files/Fq00Alm6nVypvdHO857d" alt=""><figcaption></figcaption></figure>

## Input combo type hour <a href="#h_7e42bb68cf" id="h_7e42bb68cf"></a>

Displays a time input field in the format `HH:MM` (12-hour format).

## Usage example

```xml
<form>
    <inputcombo
        type="hour"
        id="start_hour"
        label="Start Time"
        description="Set the time at which the measurement was performed"
        value="21:00"
    />      
</form>
```

## Properties

* `id`: Unique identifier for this field
* `label`: Header text
* `description`: Short descriptive text
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `value`: Sets a default time. Use the 24-hour format. It is automatically converted to 12-hour format.


# Toggle

Use toggle buttons in the Custom UI widget

<figure><img src="/files/WHH4HU83QZcKdtfvmaCW" alt="" width="563"><figcaption></figcaption></figure>

## Input combo type toggle

Displays a toggle button that can be set to either the on or off state.

## Usage example

```xml
<form>
    <inputcombo
        type="toggle"
        id="enable_device"
        label="Enable device"
        description="Set the state of the device"
        value="true"
    />
</form>
```

## Properties

* `id`: Unique identifier to access this field's data
* `label`: Header text
* `description`: Short descriptive text
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `value`: Sets either `true` or `false` as the default value.


# Dropdown

Use dropdown lists in the Custom UI widget

<figure><img src="/files/JRI6RTTdHvfYhH7zh8nz" alt="" width="563"><figcaption></figcaption></figure>

## Input combo type dropdown.list

Displays a drop-down menu that lets users select one option.

## Usage example

```xml
<form>
    <inputcombo
        type='dropdown.list'
        id='variable_from_list'
        label="Select a variable"
        description="Select a common variable from all devices"
        placeholder='Choose an option'
        value="temperature"
    >
       <menu>
            <item id='temperature'>Temperature</item>
	    <item id='humidity'>Humidity</item>
	    <item id='iluminance'>Iluminance</item>
	    <item id='magnetic_flux'>Magnetic flux</item>
      </menu>
    </inputcombo>

</form>
```

## Properties

* `id`: Unique identifier for this field
* `label`: Header text
* `description`: Short descriptive text
* `placeholder`: Placeholder text
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `value`: Sets a default value from the defined menu items. This value must match the `id` of one menu option. Otherwise, no default value is set and the placeholder is displayed.

## Option properties

`id`: Unique identifier for the selected value.


# Multiple selection dropdown

Use multiple selection dropdown lists in the Custom UI widget

<figure><img src="/files/sc6Ep9Ya4l1lnXiPiGnv" alt=""><figcaption></figcaption></figure>

### Input combo type dropdown.checkbox <a href="#h_438b28582a" id="h_438b28582a"></a>

Displays a drop-down menu that lets users select multiple options.

## Usage example

```xml
<form>
    <inputcombo
        type='dropdown.checkbox'
        id='devices_from_list'
        label="Select multiple options"
        description="Select multiple devices to send data"
        placeholder='Choose multiple options'
        value="dev_1,dev_2"
    >
        <menu>
            <item id='dev_1'>device 1</item>
            <item id='dev_2'>device 2</item>
            <item id='dev_3'>device 3</item>
            <item id='dev_4'>device 4</item>
        </menu>
    </inputcombo>
</form>
```

## Properties

* `id`: Unique identifier for this field
* `label`: Header text
* `description`: Short descriptive text
* `placeholder`: Placeholder text
* `shape`: Used to align the label and the input field. Allowed values:
  * `compactVertical`
  * `wideVertical`
  * `wideHorizontal`
* `value`: Sets default values from the defined menu items. These values must match the `id` of one or more menu options. Otherwise, no default value is set and the placeholder is displayed. To set multiple default values, use the format `dev_1,dev_2`, with options separated by commas and no spaces.

## Option properties

`id`: Unique identifier for the selected value.


# Button

This element displays a button whose color reflects its status based on its type.

You can add a button at the end of your form to trigger a custom action. This component supports the following attributes:

<table><thead><tr><th width="155.33333333333331">Property</th><th width="264">Description</th><th>Values</th></tr></thead><tbody><tr><td>type</td><td>The button type.</td><td><ul><li><code>primary</code>: Default blue button</li><li><code>success</code>: Green button</li><li><code>warning</code>: Orange button</li><li><code>danger</code>: Red button</li><li><code>muted</code>: Gray button</li></ul></td></tr><tr><td>click</td><td>A user-defined label that acts as a unique ID to link the button to an action.</td><td>Any user-defined label that matches the label of one of the supported actions.</td></tr></tbody></table>

## Supported button actions

Available actions inside the Custom UI widget are:

* **Trigger webhook**: When the button is clicked, an HTTP request is sent to the configured URL.
* **Go to dashboard**: When the button is clicked, the configured dashboard opens in a new tab.
* **Go to URL**: When the button is clicked, the configured URL opens in a new tab.

## Example

The following snippet displays a green button that, when clicked, runs the action labeled `make_request`, which sends an HTTP POST request to a configured Ubidots endpoint:

```xml
<button type="success" click="make_request">Send</button>
```

Here, the button's `click` property defines the label of the action to perform, `make_request`. Configure the same label in the widget settings under the **Actions** section:

<figure><img src="/files/nT01bLs8NSWuxNYgPcvP" alt="" width="563"><figcaption></figcaption></figure>

In the GIF above, the **Label** field in the widget's action configuration uses the same value as the button's `click` property.


# Custom style

Customize your App beyond the branding level with the Custom Style editor for Dashboards and Widgets

## **Introduction**

When deploying an IoT application to end users, a large part of user perception and adoption depends on its look and feel. If the interface is visually appealing, it can improve perception and make onboarding easier.

For that reason, in addition to Ubidots' basic customization options at the App branding level, there is an advanced editor for Dashboards and Widgets that provides more ways to customize their appearance.

## Definition

Custom Style is an editor available for all Dashboards and Widgets that lets you modify their style.

The editor expects a JSON object with root-level keys for each component (`Widget`, `Dashboard`, and `ContextBar`). Inside each key, define the properties you want to modify.

The following is an example of the default Custom Style JSON for a recently created dashboard:

```json
{
    "widget": {
        "color": "#5e5e5e",
        "header": {
            "color": "#5e5e5e",
            "borderColor": "inherit",
            "borderStyle": "none",
            "borderWidth": 0,
            "backgroundColor": "#ffffff"
        },
        "fontSize": 14,
        "borderColor": "inherit",
        "borderStyle": "none",
        "borderWidth": 0,
        "borderRadius": 0,
        "backgroundColor": "#ffffff"
    },
    "dashboard": {
        "color": "#5e5e5e",
        "backgroundColor": "#f2f2f2"
    },
    "contextBar": {
        "color": "#ffffff",
        "title": {
            "fontSize": 24
        },
        "fontSize": 14,
        "backgroundColor": "#192c54"
    }
}
```

## When to use Custom Styles?

Use Custom Styles whenever you need to customize a Widget or Dashboard beyond the default style.

## Which elements can be customized?

* Dashboards, along with their ContextBar
* All the Widgets in a dashboard at once
* Widgets, individually

## How to set Custom Style on Dashboards?

Custom Style is available in the Dashboard **Appearance** tab, as shown below. When you click it, a text editor appears where you can enter valid JSON to create a custom style.

{% hint style="warning" %}
The Dashboard custom style overrides any customization set at the [app level](https://help.ubidots.com/en/articles/830962-application-branding-how-to-customize-your-ubidots-user-interface).
{% endhint %}

<figure><img src="/files/WndAW4x82KkSFj1OKYZ0" alt="" width="563"><figcaption></figcaption></figure>

## How to set Custom Style on all the Widgets in a dashboard, at once?

At the dashboard customization level, place the JSON for the Custom Style you want to apply to all Widgets inside the `widget` key.

<figure><img src="/files/0SlOlQQJCDfd8pDAenvG" alt="" width="563"><figcaption></figcaption></figure>

{% hint style="warning" %}
This overrides every Widget's individual Custom Style.
{% endhint %}

## How to set Custom Style on Widgets individually?

On the Widget's **Appearance** tab, there is an option labeled **Custom Style**. When you click it, a text editor appears where you can enter valid JSON to create a custom style.

<figure><img src="/files/1S68r30055fMTp0YwFnB" alt="" width="563"><figcaption></figcaption></figure>

{% hint style="warning" %}

* The current Custom Style only applies to this Widget.
* Setting a Widget Custom Style after setting it globally at the Dashboard level overrides the global settings.
  {% endhint %}


# Dashboards

The following is the Dashboard Custom Style editor. From here, you can:

1. Set a Custom Style for the **Dashboard**.
2. Set a Custom Style for the **ContextBar**.
3. Set a Custom Style for all **Widgets** in the current **Dashboard** at once.

<figure><img src="/files/QUI0OCbwfBvCpOxzGczr" alt="" width="563"><figcaption></figcaption></figure>

### 1. Dashboard customization

Set the `dashboard` properties inside the `dashboard` key.

### Available properties:

<table><thead><tr><th width="260">Property</th><th width="240">Accepted values</th><th>Behavior</th></tr></thead><tbody><tr><td><code>color</code></td><td><ul><li>Named colors.</li><li>Hex colors.</li><li>RGB, RGBa</li><li>HSL, HSLa</li></ul></td><td>Sets the color of text and text decorations.</td></tr><tr><td><code>backgroundColor</code></td><td>Any <code>color</code></td><td>Applies solid colors as background on an element.</td></tr></tbody></table>

{% hint style="warning" %}

* Applying any styling to a **Dashboard** overrides the settings defined by the App style.
* Any accepted value other than a number must be wrapped in double quotes as a string.
* The **`ContextBar`** and **`Widget`** components in a **Dashboard** inherit the `color` property from it.
  {% endhint %}

### 2. ContextBar customization: <a href="#h_ba8fc29b1f" id="h_ba8fc29b1f"></a>

Set the `contextBar` properties inside the `contextBar` key.

### Available properties: <a href="#h_ba8fc29b1f" id="h_ba8fc29b1f"></a>

| Property                      | Accepted values                                                                                                                                                                                                                                                                                                                 | Behavior                                                 |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| <p><code>title</code><br></p> | <p>A JSON containing the following properties:</p><ul><li><code>fontSize</code></li><li><code>fontFamily</code></li><li><code>fontWeight</code></li></ul>                                                                                                                                                                       | Specifies the properties of the selected title.          |
| `fontSize`                    | <ul><li>Keywords: xx-small, x-small, small, medium, large, x-large, xx-large.</li><li>Length units: mm, cm, in, pt, pc, em, rem, px.</li><li>Percentages.</li></ul>                                                                                                                                                             | Specifies the size, or height, of the font               |
| `boxShadow`                   | <ul><li>Horizontal offset (required): positive or negative number</li><li>Vertical offset (required): positive or negative number</li><li>Blur radius (required): positive number</li><li>Spread radius (optional): positive or negative number</li><li>Color (required): named colors, hex, RGB, RGBA, HSL and HSLA.</li></ul> | Puts shadows on elements                                 |
| `borderRadius`                | Numeric value.                                                                                                                                                                                                                                                                                                                  | Gives any element “rounded corners”                      |
| `fontFamily`                  | <ul><li>Family name.</li><li>Generic family name.</li></ul>                                                                                                                                                                                                                                                                     | Defines the font that is applied to the selected element |
| `backgroundColor`             | Any `color`                                                                                                                                                                                                                                                                                                                     | Applies solid colors as background on an element.        |
| `color`                       | <ul><li>Named colors.</li><li>Hex colors.</li><li>RGB and RGBa colors.</li><li>HSL and HSLa colors.</li></ul>                                                                                                                                                                                                                   | Sets the color of text and text decorations.             |

{% hint style="warning" %}

* Setting the `color` property for the **contextBar** overrides the value inherited from the **Dashboard**.
* Any accepted value other than a number must be wrapped in double quotes as a string.
  {% endhint %}

## 3. Widgets customization

Set the `widgets` properties inside the `widgets` key. Go to the **Widgets** section to know what properties can be modified for Widgets.

{% hint style="warning" %}

* This sets the same Custom Style for all widgets in the Dashboard, overriding their individual settings.
* Modifying the Custom Style globally only affects Widgets whose default Custom Style has not been modified.
  {% endhint %}

## Dashboard Custom Style Examples

### **Example 1:** Set the `backgroundColor` property

The following examples show how to set the background color to `"black"` for a Dashboard in different ways:

* Named colors:<br>

  ```json
  {
      "dashboard":{
          "backgroundColor" : "black",
          "color": "#f2f2f2",
      },
  }
  ```
* Hex colors:<br>

  <pre class="language-json"><code class="lang-json"><strong>{
  </strong><strong>    "dashboard":{
  </strong><strong>        "backgroundColor" : "#000000",
  </strong>        "color": "#f2f2f2",
      },
  }
  </code></pre>
* RGB:<br>

  <pre class="language-json"><code class="lang-json"><strong>{
  </strong><strong>    "dashboard":{
  </strong><strong>        "backgroundColor" : "rgb(0,0,0)",
  </strong>        "color": "#f2f2f2",
      },
  }
  </code></pre>
* RGBA:<br>

  <pre class="language-json"><code class="lang-json"><strong>{
  </strong><strong>    "dashboard":{
  </strong><strong>        "backgroundColor" : "rgba(0,0,0,1)",
  </strong>        "color": "#f2f2f2",
      },
  }
  </code></pre>
* HSL:<br>

  <pre class="language-json"><code class="lang-json"><strong>{
  </strong><strong>    "dashboard":{
  </strong><strong>        "backgroundColor" : "hsl(0, 0%, 0%)",
  </strong>        "color": "#f2f2f2",
      },
  }

  </code></pre>
* HSLA:<br>

  <pre class="language-json"><code class="lang-json"><strong>{
  </strong><strong>    "dashboard":{
  </strong><strong>        "backgroundColor" : "hsl(0, 0%, 0%, 1)",
  </strong>        "color": "#f2f2f2",
      },
  }

  </code></pre>

The following is the Dashboard before applying the Custom Style:

<figure><img src="/files/gxKqCGNIkaHmwPj7SPUO" alt=""><figcaption></figcaption></figure>

The following is the Dashboard after applying the Custom Style:

<figure><img src="/files/XdJ796JeQA7COLkHwcYB" alt=""><figcaption></figcaption></figure>

### **Example 2:** Inherit the `color` property from the Dashboard to all Widgets in it

Let's take a Dashboard containing Widgets with no Custom Style set yet:

<figure><img src="/files/0GyolyQA16fUk94GfO5H" alt=""><figcaption></figcaption></figure>

Go to the Dashboard Custom Style editor and do the following:

1. Delete the `color` property inside the `Widget` key in the Style Editor at the Dashboard level:

   <figure><img src="/files/N4UmHLmg1iinK66NP6iW" alt="" width="563"><figcaption></figcaption></figure>
2. Set the Dashboard `color` property to **red** and save the changes:

   <figure><img src="/files/0ipsPTdjtl9Rq6oprvfQ" alt="" width="563"><figcaption></figcaption></figure>
3. If you look at the Widgets inside the Dashboard, nothing seems to have changed. This happens because each Widget's individual settings override the `color` property. With that in mind, remove the `color` property from the Widget that you want to inherit this property from the Dashboard.

   <figure><img src="/files/UaMSAxDJSYecQvDQmYzS" alt=""><figcaption></figcaption></figure>

Once the Widget `color` property is deleted, it inherits that property from the Dashboard.

## ContextBar Custom Style examples

### **Example 1**: Change the **ContextBar** title font, size, color, and background color

The following is the default Custom Style for every **ContextBar**:

<figure><img src="/files/JKWbdktIEGw8AencSKdA" alt=""><figcaption></figcaption></figure>

Open the Dashboard Custom Style settings and set the following style for the **ContextBar** in the **Style Editor**. This changes the **ContextBar** background to white, changes the font color to black, and updates the font size:

```json
"contextBar": {
        "title": {
            "color": "#000000",
            "fontSize": 14
        },
        "fontSize": 24,
        "backgroundColor": "#ffffff",
        "color": "#000000",
    }
```

The following is the result:

<figure><img src="/files/vd02b9Gpsv4iJCXvsdBG" alt=""><figcaption></figcaption></figure>

## Widget Custom Style examples

### **Example 1:** Set a Custom Style for all Widgets in a Dashboard

By modifying the elements inside the `widget` key in the Dashboard Style Editor, you can apply the same style to all widgets on the Dashboard.<br>

Consider the following Dashboard with no Custom Style set yet:

<figure><img src="/files/954u1LyqsSAAcvQdYNcn" alt=""><figcaption></figcaption></figure>

Suppose you want to apply the following style to all widgets in the current Dashboard at once:

* Set the header font color to white
* Set header border color to black
* Set the header border style to dotted
* Set the header border width to 1
* Set the header border radius to 10
* Set the header background color to black
* Set the font size to 30
* Set the box shadow to `10px 5px 5px orange`
* Set the border color to `#5e5e5e`
* Set the border style to solid
* Set the border width to 5
* Set the border radius to 10
* Set the background color to black.<br>

The following JSON describes that Custom Style:

```json
"header": {
    "color": "#FFFFFF",
    "borderColor": "#000000",
    "borderStyle": "dotted",
    "borderWidth": 1,
    "borderRadius": 10,
    "backgroundColor": "#000000"
},
"fontSize": 30,
"boxShadow": "10px 5px 5px orange",
"borderColor": "#5e5e5e",
"borderStyle": "solid",
"borderWidth": 5,
"borderRadius": 10,
"backgroundColor": "#000000"


```

To apply this style to all Widgets at once, go to the Dashboard Style Editor and paste it into the `widget` key. It will look like this:

```json
{
    "widget": {
         "header": {
            "color": "#FFFFFF",
            "borderColor": "#000000",
            "borderStyle": "dotted",
            "borderWidth": 1,
            "borderRadius": 10,
            "backgroundColor": "#000000"
        },
        "fontSize": 30,
        "boxShadow": "10px 5px 5px orange",
        "borderColor": "#5e5e5e",
        "borderStyle": "solid",
        "borderWidth": 5,
        "borderRadius": 10,
        "backgroundColor": "#000000"
    },
    "dashboard": {
        "color": "#5e5e5e",
        "backgroundColor": "#f2f2f2"
    },
    "contextBar": {
        "color": "#000000",
        "title": {
            "color": "#000000",
            "fontSize": 14
        },
        "fontSize": 24,
        "backgroundColor": "#ffffff"
    }
}
```

Doing so results in the following:

<figure><img src="/files/jidD5wnA0fdEOsi9G5tr" alt=""><figcaption></figcaption></figure>

All Widgets in the current Dashboard now share the same Custom Style.


# Widgets

The following is the Widget Custom Style editor. From here, you can set a Custom Style for the Widget.

| Property          | Accepted values                                                                                                                                                                                                                                                                                                                             | Behavior                                                                                                                                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `color`           | <ul><li>Named colors.</li><li>Hex colors.</li><li>RGB, RGBA.</li><li>HSL, HSLA.</li></ul>                                                                                                                                                                                                                                                   | Sets the text color.                                                                                                                                                                                        |
| `backgroundColor` | Any `color`                                                                                                                                                                                                                                                                                                                                 | Applies solid colors as background on an element.                                                                                                                                                           |
| `background`      |                                                                                                                                                                                                                                                                                                                                             | Sets all background style properties at once, such as color, image, origin, size, or repeat method. Component properties not set in the `background` shorthand declaration are set to their default values. |
| `borderColor`     | <ul><li>Any <code>color</code></li><li>inherit</li></ul>                                                                                                                                                                                                                                                                                    | Sets the color of an element's four borders.                                                                                                                                                                |
| `borderRadius`    | Numeric value                                                                                                                                                                                                                                                                                                                               | Gives any element rounded corners.                                                                                                                                                                          |
| `borderStyle`     | <ul><li>solid.</li><li>none.</li><li>hidden.</li><li>dashed.</li><li>dotted.</li><li>double.</li><li>groove.</li><li>ridge.</li><li>inset.</li><li>outset.</li></ul>                                                                                                                                                                        | Specifies the type of line drawn around the element.                                                                                                                                                        |
| `borderWidth`     | <ul><li>Numeric values: px, em, rem, vh and vw units.</li><li>Thin: The equivalent of 1px.</li><li>Medium: The equivalent of 3px.</li><li>Thick: The equivalent of 5px.</li></ul>                                                                                                                                                           | Specifies the thickness of the border.                                                                                                                                                                      |
| `header`          | <p>A JSON object containing the following properties, along with valid values for each one:</p><ul><li><code>color</code></li><li><code>backgroundColor</code></li><li><code>fontWeight</code></li><li><code>boxShadow</code></li><li><code>borderRadius</code></li><li><code>borderStyle</code></li><li><code>borderWidth</code></li></ul> | Specifies the properties of the selected header.                                                                                                                                                            |
| `fontSize`        | <ul><li>Keywords: xx-small, x-small, small, medium, large, x-large, xx-large.</li><li>Length units: mm, cm, in, pt, pc, em, rem, px.</li><li>Percentages.</li></ul>                                                                                                                                                                         | Specifies the size, or height, of the font.                                                                                                                                                                 |
| `fontFamily`      | <ul><li>Family name.</li><li>Generic family name.</li></ul>                                                                                                                                                                                                                                                                                 | Defines the font applied to the selected element.                                                                                                                                                           |
| `boxShadow`       | <ul><li>Horizontal offset (required): positive or negative number</li><li>Vertical offset (required): positive or negative number</li><li>Blur radius (required): positive number</li><li>Spread radius (optional): positive or negative number</li><li>Color (required): named colors, hex, RGB, RGBA, HSL and HSLA.</li></ul>             | Puts shadows on elements                                                                                                                                                                                    |
| `fontWeight`      | <ul><li>Keywords: normal, bold, bolder, lighter.</li><li>Predefined numeric values: 100, 200, 300, 400, 500, 600, 700, 800, 900.</li></ul>                                                                                                                                                                                                  | Sets the weight, or thickness, of a font.                                                                                                                                                                   |

{% hint style="warning" %}

* Any accepted value other than a number must be wrapped in double quotes as a string.
* Setting any of these properties overrides the value inherited from the Dashboard.
* Setting any of these properties prevents the Widget Custom Style from being modified at the Dashboard level.
  {% endhint %}

## Examples

### Example 1: Set a Widget's header Custom Style

Let's give a metric widget a Custom Style with the following changes:

* Rounded corners with a border radius of `10`
* An outset border style
* A bluish background color
* White font color
* A border width of `1`

The following JSON defines that Custom Style:

```json
{
    "color": "#FFFFFF",
    "header": {
        "color": "#FFFFFF",
        "backgroundColor": "#165a72",
        "borderStyle": "dotted"
    },
    "fontSize": 14,
    "borderRadius": 15,
    "backgroundColor": "#000000"
}
```

To apply the dotted border to the whole Widget, move `"borderStyle": "dotted"` from the `header` section to the root. You must also set the `borderWidth` property for the border to display correctly:

```json
{
    "color": "#FFFFFF",
    "header": {
        "color": "#FFFFFF",
        "backgroundColor": "#165a72"
    },
    "fontSize": 14,
    "borderColor": "white",
    "borderStyle": "dotted",
    "borderRadius": 15,
    "borderWidth": 5,
    "backgroundColor": "#000000"
}
```

This is the Widget with no Custom Style applied:

<figure><img src="/files/M34lngJb9136f2htuRlN" alt="" width="375"><figcaption></figcaption></figure>

This is the same Widget after applying the Custom Style above:

<figure><img src="/files/PvYEq5AXaX4h0uS3TMha" alt="" width="375"><figcaption></figcaption></figure>

### Example 2: Set a Widget's Body Custom Style

Let's modify the Widget body for the metric widget above. Set the following properties:

* Black font color
* Font size to `20`
* Border radius to `10`
* Background color to `#38b01e`
* Box shadow to `30px -8px teal`

The following JSON defines that Custom Style:

```json
{
    "color": "black",
    "header": {
        "color": "white",
        "borderStyle": "outset",
        "borderWidth": 1,
        "borderRadius": 10,
        "backgroundColor": "#10435b"
    },
    "fontSize": 20,
    "borderRadius": 10,
    "backgroundColor": "#38b01e",
    "boxShadow": "30px -8px teal"
}
```

After applying the Custom Style above, the Widget looks like this:

<figure><img src="/files/ZIGM2cYMfnif1wTdgIoU" alt="" width="375"><figcaption></figcaption></figure>

### Example 3: Using linear gradient background

Let's use the previous metric Widget again for this example. Instead of a flat green background color, use a linear gradient.

Set the `background` property to `linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c)` as shown below:

<figure><img src="/files/TFFiGDaNLV0YtRr32RWG" alt="" width="563"><figcaption></figcaption></figure>

This is the resulting widget:

<figure><img src="/files/HCXlXi2nG2fHJZQtWv4W" alt="" width="375"><figcaption></figcaption></figure>

You can also use the `background` property in the widget header, as shown below:

```json
{
    "color": "black",
    "header": {
        "color": "white",
        "borderStyle": "outset",
        "borderWidth": 1,
        "borderRadius": 10,
        "background": "linear-gradient(to left, #333, #333 50%, #eee 75%, #333 75%)"
    },
    "fontSize": 20,
    "borderRadius": 20,
    "background": "linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c)",
    "boxShadow": "30px -8px teal"
}
```

<figure><img src="/files/9s9A6tKhnDohVtcpaIQ5" alt="" width="375"><figcaption></figcaption></figure>


# Line Chart

Visualize time series data in Ubidots with the Line Chart widget

The Line Chart widget includes several options to display and customize data visualizations in your application. Color, time span, and aggregation method are some of the options available in this widget.

For a basic overview of the widget, visit [this article](https://help.ubidots.com/en/articles/2596623-line-chart-and-double-axis-widgets), which covers the basics of setting up a Line Chart widget.

This section covers only the advanced features of the Line Chart widget.

## ECharts configuration

{% hint style="warning" %}
This option is only available to Enterprise users.
{% endhint %}

[ECharts](https://echarts.apache.org/en/index.html) is a powerful open-source visualization library designed to create interactive and customizable charts. ECharts provides extensive customization options that let you tailor the appearance and behavior of charts to fit specific requirements. With it, you can add new layers of customization to your widgets, simplify monitoring, and better support your users' needs.

The Line Chart widget is built on top of the ECharts library. This gives users access to all features provided by the library through the **ECharts configuration** option in the **APPEARANCE** tab:

<figure><img src="/files/oHOxROqd1reCApkkHLKn" alt="" width="563"><figcaption></figcaption></figure>

After you click the option, a large drawer opens from the left and shows the [ECharts options JSON](https://echarts.apache.org/en/option.html#title). It maps all fields available in the UI and also provides an editing environment where users can take full advantage of the [Line](https://echarts.apache.org/en/option.html#series-line) and [Bar](https://echarts.apache.org/en/option.html#series-bar) series types.

{% hint style="info" %}
All fields exposed through the UI are mapped bidirectionally. This means that if you make a change in the UI, it is reflected in the ECharts options JSON, and vice versa.
{% endhint %}

<figure><img src="/files/6dMDR3lGSmCwS1MMzSVU" alt="" width="563"><figcaption></figcaption></figure>

### Valid placeholders

The following are valid placeholders that you can use at the device or variable level.

#### Device

Access via `device.<placeholder>`

```
id
name
label
description
context
context.<context_key>
context._icon
context._color
context._config
context._config.<context_key>
context._config.<context_key>.key
context._config.<context_key>.text
context._config.<context_key>.type
context._config.<context_key>.value
context._device_type
context._location_type
context._device_type_id
context._location_fixed
context._location_fixed.lat
context._location_fixed.lng
icon
color
```

#### Variable

Access via `variable.<placeholder>`

```
id
name
label
description
unit
icon
color
```

## Examples

The examples below use the ECharts configuration option to produce the results shown. Without this advanced option, you cannot get the same visualization using only the fields available in the UI.

### Area boundaries

This example creates colored areas in the widget that act as boundaries, making it easier to see whether the data is within expected ranges. Red indicates an alarm, while green and orange indicate safe and warning values, respectively.

<figure><img src="/files/RlUsnW78dtHofAX6xvo4" alt=""><figcaption></figcaption></figure>

This is the ECharts JSON for a single variable. You need to adjust the [`series[0].markArea.data`](https://echarts.apache.org/en/option.html#series-line.markArea.data) values so the limits reflect your specific needs and data behavior:

<details>

<summary>JSON example</summary>

```json
{
    "yAxis": [
        {
            "type": "value",
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5e5e5e"
                    ],
                    "opacity": 0.1
                }
            },
            "min": 5,
            "max": 30,
            "name": "",
            "nameLocation": "center",
            "nameGap": 70,
            "nameTextStyle": {
                "color": "#5E5E5E"
            },
            "position": "left",
            "show": true,
            "offset": 0,
            "gridIndex": 0
        }
    ],
    "series": [
        {
            "z": 0,
            "name": "{{variable.name}} [{{variable.unit}}] ({{device.name}})",
            "step": false,
            "type": "line",
            "stack": null,
            "symbol": null,
            "smooth": true,
            "markArea": {
                "data": [
                    [
                        {
                            "label": {
                                "show": true,
                                "color": "red",
                                "position": "insideLeft",
                                "formatter": "Alarm"
                            },
                            "yAxis": 25,
                            "itemStyle": {
                                "color": "rgba(255, 173, 177, 0.4)"
                            }
                        },
                        {
                            "yAxis": 99
                        }
                    ],
                    [
                        {
                            "yAxis": 24,
                            "itemStyle": {
                                "color": "rgba(252, 158, 7, 0.4)"
                            }
                        },
                        {
                            "yAxis": 25
                        }
                    ],
                    [
                        {
                            "label": {
                                "show": true,
                                "color": "green",
                                "position": "insideLeft",
                                "formatter": "Normal"
                            },
                            "yAxis": 15,
                            "itemStyle": {
                                "color": "rgba(204, 255, 153, 0.4)"
                            }
                        },
                        {
                            "yAxis": 24
                        }
                    ],
                    [
                        {
                            "yAxis": 14,
                            "itemStyle": {
                                "color": "rgba(252, 158, 7, 0.4)"
                            }
                        },
                        {
                            "yAxis": 15
                        }
                    ],
                    [
                        {
                            "label": {
                                "show": true,
                                "color": "red",
                                "position": "insideLeft",
                                "formatter": "Alarm"
                            },
                            "yAxis": 14,
                            "itemStyle": {
                                "color": "rgba(255, 173, 177, 0.4)"
                            }
                        },
                        {
                            "yAxis": -99
                        }
                    ]
                ],
                "itemStyle": {
                    "color": "rgba(255, 173, 177, 0.4)"
                }
            },
            "markLine": {},
            "animation": false,
            "itemStyle": {
                "color": "#465676",
                "opacity": 0.8,
                "borderColor": "#465676"
            },
            "lineStyle": {
                "color": "#465676",
                "width": 2
            },
            "showSymbol": false,
            "symbolSize": 8,
            "yAxisIndex": 0,
            "connectNulls": true
        }
    ],
    "xAxis": [
        {
            "type": "time",
            "name": "",
            "nameLocation": "center",
            "nameGap": 28,
            "nameTextStyle": {
                "color": "#A4A5A7"
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5E5E5E"
                    ],
                    "opacity": 0.1
                }
            },
            "axisTick": null,
            "z": 2
        }
    ],
    "dataZoom": [
        {
            "show": true,
            "filterMode": "none",
            "xAxisIndex": 0,
            "realtime": false,
            "textStyle": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "dataBackground": {
                "areaStyle": {
                    "color": "#5E5E5E"
                }
            },
            "right": 45,
            "left": 45
        }
    ],
    "legend": {
        "itemHeight": 7,
        "itemWidth": 15,
        "pageIconColor": "#5E5E5E",
        "pageIconInactiveColor": "#5E5E5E",
        "pageTextStyle": {
            "color": "#5E5E5E"
        },
        "textStyle": {
            "color": "#5E5E5E"
        },
        "top": 0,
        "type": "scroll",
        "show": true
    },
    "grid": {
        "bottom": 60,
        "containLabel": true,
        "left": 60,
        "right": 60,
        "top": 10
    },
    "tooltip": {
        "confine": true,
        "trigger": "axis"
    }
}
```

</details>

### Color-Coded Line Chart

This example shows a line chart where the line color changes dynamically based on the value of the data points.

<figure><img src="/files/Od4OY6bNZzaxDtihUPGf" alt=""><figcaption></figcaption></figure>

You can set the color ranges by modifying each of the [pieces](https://echarts.apache.org/en/option.html#visualMap-piecewise.pieces) within the [visualMap](https://echarts.apache.org/en/option.html#visualMap) element in the following ECharts JSON:

<details>

<summary>Color-Coded Line Chart ECharts JSON</summary>

```json
{
    "yAxis": [
        {
            "type": "value",
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5e5e5e"
                    ],
                    "opacity": 0.1
                }
            },
            "min": "dataMin",
            "max": "dataMax",
            "name": "Humidity",
            "nameLocation": "center",
            "nameGap": 70,
            "nameTextStyle": {
                "color": "#5E5E5E"
            },
            "position": "left",
            "show": true,
            "offset": 0,
            "gridIndex": 0
        }
    ],
    "series": [
        {
            "z": 0,
            "name": "{{variable.name}} [{{variable.unit}}] ({{device.name}})",
            "step": false,
            "type": "line",
            "stack": null,
            "symbol": null,
            "markLine": {},
            "animation": false,
            "itemStyle": {
                "color": "{{variable.color}}",
                "opacity": 0.8,
                "borderColor": "{{variable.color}}"
            },
            "showSymbol": false,
            "symbolSize": 8,
            "yAxisIndex": 0,
            "connectNulls": true
        }
    ],
    "xAxis": [
        {
            "type": "time",
            "name": "Time",
            "nameLocation": "center",
            "nameGap": 28,
            "nameTextStyle": {
                "color": "#A4A5A7"
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5E5E5E"
                    ],
                    "opacity": 0.1
                }
            },
            "axisTick": null,
            "z": 2
        }
    ],
    "dataZoom": [
        {
            "show": true,
            "filterMode": "none",
            "xAxisIndex": 0,
            "realtime": false,
            "textStyle": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "dataBackground": {
                "areaStyle": {
                    "color": "#5E5E5E"
                }
            },
            "right": 45,
            "left": 45
        }
    ],
    "legend": {
        "itemHeight": 7,
        "itemWidth": 15,
        "pageIconColor": "#5E5E5E",
        "pageIconInactiveColor": "#5E5E5E",
        "pageTextStyle": {
            "color": "#5E5E5E"
        },
        "textStyle": {
            "color": "#5E5E5E"
        },
        "top": 0,
        "type": "scroll",
        "show": false
    },
    "grid": {
        "bottom": 60,
        "containLabel": true,
        "left": 60,
        "right": 60,
        "top": 10
    },
    "tooltip": {
        "confine": true,
        "trigger": "axis"
    },
    "visualMap": {
        "show": false,
        "top": 50,
        "right": 10,
        "pieces": [
            {
                "gt": 0,
                "lte": 5,
                "color": "#93CE07"
            },
            {
                "gt": 5,
                "lte": 10,
                "color": "#FBDB0F"
            },
            {
                "gt": 10,
                "lte": 15,
                "color": "#FC7D02"
            },
            {
                "gt": 15,
                "lte": 20,
                "color": "#FD0100"
            },
            {
                "gt": 20,
                "lte": 25,
                "color": "#AA069F"
            },
            {
                "gt": 25,
                "color": "#AC3B2A"
            }
        ],
        "outOfRange": {
            "color": "#999"
        }
    }
}
```

</details>

### Color-Coded Area Line Chart

This example shows a line chart similar to the previous one. It maps the Y-axis value to a color and also colors the area under the chart:

<figure><img src="/files/Gl1L0AFZcEf4Dq3z1LN5" alt=""><figcaption></figcaption></figure>

Below is the configuration for this line chart. You can customize the colors and ranges for each of the [pieces](https://echarts.apache.org/en/option.html#visualMap-piecewise.pieces) within the [visualMap](https://echarts.apache.org/en/option.html#visualMap) as needed:

<details>

<summary>Color-Coded Area Line Chart JSON</summary>

```json
{
    "yAxis": [
        {
            "type": "value",
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5e5e5e"
                    ],
                    "opacity": 0.1
                }
            },
            "min": "dataMin",
            "max": "dataMax",
            "name": "Default Y-axis",
            "nameLocation": "center",
            "nameGap": 70,
            "nameTextStyle": {
                "color": "#5E5E5E"
            },
            "position": "left",
            "show": true,
            "offset": 0,
            "gridIndex": 0,
            "lineStyle": {
                "color": "#5470C6",
                "width": 3
            }
        }
    ],
    "series": [
        {
            "z": 0,
            "name": "{{variable.name}} [{{variable.unit}}] ({{device.name}})",
            "step": false,
            "type": "line",
            "stack": null,
            "symbol": null,
            "markLine": {},
            "animation": false,
            "areaStyle": {},
            "itemStyle": {
                "color": "{{variable.color}}",
                "opacity": 0.8,
                "borderColor": "{{variable.color}}"
            },
            "lineStyle": {
                "width": 2
            },
            "showSymbol": false,
            "symbolSize": 8,
            "yAxisIndex": 0,
            "connectNulls": true
        }
    ],
    "xAxis": [
        {
            "type": "time",
            "name": "",
            "nameLocation": "center",
            "nameGap": 28,
            "nameTextStyle": {
                "color": "#A4A5A7"
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5E5E5E"
                    ],
                    "opacity": 0.1
                }
            },
            "axisTick": null,
            "z": 2
        }
    ],
    "dataZoom": [
        {
            "show": true,
            "filterMode": "none",
            "xAxisIndex": 0,
            "realtime": false,
            "textStyle": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "dataBackground": {
                "areaStyle": {
                    "color": "#5E5E5E"
                }
            },
            "right": 45,
            "left": 45
        }
    ],
    "legend": {
        "itemHeight": 7,
        "itemWidth": 15,
        "pageIconColor": "#5E5E5E",
        "pageIconInactiveColor": "#5E5E5E",
        "pageTextStyle": {
            "color": "#5E5E5E"
        },
        "textStyle": {
            "color": "#5E5E5E"
        },
        "top": 0,
        "type": "scroll",
        "show": false
    },
    "grid": {
        "bottom": 60,
        "containLabel": true,
        "left": 60,
        "right": 60,
        "top": 10
    },
    "tooltip": {
        "confine": true,
        "trigger": "axis"
    },
    "visualMap": {
        "show": false,
        "type": "piecewise",
        "pieces": [
            {
                "gt": 0,
                "lt": 18,
                "color": "#1e13e8 "
            },
            {
                "gt": 18,
                "lt": 22,
                "color": "#e8dd13"
            },
            {
                "gt": 22,
                "lt": 26,
                "color": "#e85a13"
            },
            {
                "gt": 26,
                "lt": 28,
                "color": "#ed2005"
            },
            {
                "gt": 28,
                "color": "#ed2005"
            }
        ],
        "dimension": 1,
        "seriesIndex": 0
    }
}
```

</details>

## Animations

### Initial load animation

Set the initial load animation by adding the following attributes to the series:

```json
"animationDelay": <animation-duration-in-milliseconds>,
"animationEasing": "linear | cubicIn | cubicOut | cubicInOut | quadraticIn | quadraticOut | quadraticInOut | quarticIn | quarticOut | quarticInOut | sinusoidalIn | sinusoidalOut | sinusoidalInOut | exponentialIn | exponentialOut | exponentialInOut | bounceIn | bounceOut | bounceInOut | elasticIn | elasticOut | elasticInOut"
"animationDuration": <animation-delay-in-milliseconds>,
```

<details>

<summary>Initial Load Animation Line Chart JSON</summary>

```json

{
    "yAxis": [
        {
            "type": "value",
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5e5e5e"
                    ],
                    "opacity": 0.1
                }
            },
            "min": "dataMin",
            "max": "dataMax",
            "name": "Default Y-axis",
            "nameLocation": "center",
            "nameGap": 70,
            "nameTextStyle": {
                "color": "#5E5E5E"
            },
            "position": "left",
            "show": true,
            "offset": 0,
            "gridIndex": 0
        }
    ],
    "series": [
        {
            "z": 0,
            "name": "{{variable.name}} [{{variable.unit}}] ({{device.name}})",
            "type": "line",
            "markLine": {
                "data": [
                    {
                        "name": "Avg",
                        "type": "average"
                    }
                ],
                "symbol": "none"
            },
            "animation": true,
            "itemStyle": {
                "color": "{{variable.color}}",
                "opacity": 0.8,
                "borderColor": "{{variable.color}}"
            },
            "lineStyle": {
                "color": "{{variable.color}}",
                "width": 2
            },
            "showSymbol": false,
            "symbolSize": 8,
            "yAxisIndex": 0,
            "connectNulls": true,
            "animationDelay": 300,
            "animationEasing": "easingName",
            "animationDuration": 3000
        }
    ],
    "xAxis": [
        {
            "type": "time",
            "name": "",
            "nameLocation": "center",
            "nameGap": 28,
            "nameTextStyle": {
                "color": "#A4A5A7"
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5E5E5E"
                    ],
                    "opacity": 0.1
                }
            },
            "axisTick": null,
            "z": 2
        }
    ],
    "dataZoom": [
        {
            "show": true,
            "filterMode": "none",
            "xAxisIndex": 0,
            "realtime": true,
            "textStyle": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "dataBackground": {
                "areaStyle": {
                    "color": "#5E5E5E"
                }
            },
            "right": 45,
            "left": 45
        }
    ],
    "legend": {
        "itemHeight": 7,
        "itemWidth": 15,
        "pageIconColor": "#5E5E5E",
        "pageIconInactiveColor": "#5E5E5E",
        "pageTextStyle": {
            "color": "#5E5E5E"
        },
        "textStyle": {
            "color": "#5E5E5E"
        },
        "top": 0,
        "type": "scroll",
        "show": true
    },
    "grid": {
        "bottom": 60,
        "containLabel": true,
        "left": 60,
        "right": 60,
        "top": 10
    },
    "tooltip": {
        "confine": true,
        "trigger": "axis",
        "axisPointer": {
            "type": "shadow"
        }
    }
}
```

</details>

<figure><img src="/files/GLtaOgiPNN710w3HY8PF" alt=""><figcaption></figcaption></figure>

### Data update animation

Set the animation shown when the data series updates by setting the following attributes:

```json
"animationDelayUpdate":  <animation-duration-in-milliseconds>,
"animationEasingUpdate": "linear | cubicIn | cubicOut | cubicInOut | quadraticIn | quadraticOut | quadraticInOut | quarticIn | quarticOut | quarticInOut | sinusoidalIn | sinusoidalOut | sinusoidalInOut | exponentialIn | exponentialOut | exponentialInOut | bounceIn | bounceOut | bounceInOut | elasticIn | elasticOut | elasticInOut",
"animationDurationUpdate":  <animation-delay-in-milliseconds>,
```

<details>

<summary>Data Update Line Chart JSON</summary>

```json

{
    "yAxis": [
        {
            "type": "value",
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5e5e5e"
                    ],
                    "opacity": 0.1
                }
            },
            "min": "dataMin",
            "max": "dataMax",
            "name": "Default Y-axis",
            "nameLocation": "center",
            "nameGap": 70,
            "nameTextStyle": {
                "color": "#5E5E5E"
            },
            "position": "left",
            "show": true,
            "offset": 0,
            "gridIndex": 0
        }
    ],
    "series": [
        {
            "z": 0,
            "name": "{{variable.name}} [{{variable.unit}}] ({{device.name}})",
            "type": "line",
            "markLine": {
                "data": [
                    {
                        "name": "Avg",
                        "type": "average"
                    }
                ],
                "symbol": "none"
            },
            "animation": true,
            "itemStyle": {
                "color": "{{variable.color}}",
                "opacity": 0.8,
                "borderColor": "{{variable.color}}"
            },
            "lineStyle": {
                "color": "{{variable.color}}",
                "width": 2
            },
            "showSymbol": false,
            "symbolSize": 8,
            "yAxisIndex": 0,
            "connectNulls": true,
            "animationDelay": 300,
            "animationEasing": "easingName",
            "animationDuration": 3000,
            "animationDelayUpdate": 0,
            "animationEasingUpdate": "easingName",
            "animationDurationUpdate": 2000
        }
    ],
    "xAxis": [
        {
            "type": "time",
            "name": "",
            "nameLocation": "center",
            "nameGap": 28,
            "nameTextStyle": {
                "color": "#A4A5A7"
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5E5E5E"
                    ],
                    "opacity": 0.1
                }
            },
            "axisTick": null,
            "z": 2
        }
    ],
    "dataZoom": [
        {
            "show": true,
            "filterMode": "none",
            "xAxisIndex": 0,
            "realtime": true,
            "textStyle": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "dataBackground": {
                "areaStyle": {
                    "color": "#5E5E5E"
                }
            },
            "right": 45,
            "left": 45
        }
    ],
    "legend": {
        "itemHeight": 7,
        "itemWidth": 15,
        "pageIconColor": "#5E5E5E",
        "pageIconInactiveColor": "#5E5E5E",
        "pageTextStyle": {
            "color": "#5E5E5E"
        },
        "textStyle": {
            "color": "#5E5E5E"
        },
        "top": 0,
        "type": "scroll",
        "show": true
    },
    "grid": {
        "bottom": 60,
        "containLabel": true,
        "left": 60,
        "right": 60,
        "top": 10
    },
    "tooltip": {
        "confine": true,
        "trigger": "axis",
        "axisPointer": {
            "type": "shadow"
        }
    }
}
```

</details>

<figure><img src="/files/Ydgsz9yLPTF8jV4bR0YC" alt=""><figcaption></figcaption></figure>

## Data point custom icon

Set the following attributes in the data series:

```json
"showSymbol": true,
"symbol": "image://<your-icon-cdn-url>",
"symbolSize": <desired-size>,
```

<figure><img src="/files/nxS9XpgUxMXQzRHamdqz" alt=""><figcaption></figcaption></figure>

<details>

<summary>Data Point Custom Icon JSON</summary>

```json
{
    "yAxis": [
        {
            "type": "value",
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5e5e5e"
                    ],
                    "opacity": 0.1
                }
            },
            "min": "dataMin",
            "max": "dataMax",
            "name": "Default Y-axis",
            "nameLocation": "center",
            "nameGap": 70,
            "nameTextStyle": {
                "color": "#5E5E5E"
            },
            "position": "left",
            "show": true,
            "offset": 0,
            "gridIndex": 0
        }
    ],
    "series": [
        {
            "z": 0,
            "name": "{{variable.name}} [{{variable.unit}}] ({{device.name}})",
            "type": "line",
            "symbol": "image://https://cdn-icons-png.flaticon.com/512/4201/4201973.png",
            "markLine": {
                "data": [
                    {
                        "name": "Avg",
                        "type": "average"
                    }
                ],
                "symbol": "none"
            },
            "animation": true,
            "itemStyle": {
                "color": "{{variable.color}}",
                "opacity": 0.8,
                "borderColor": "{{variable.color}}"
            },
            "lineStyle": {
                "color": "{{variable.color}}",
                "width": 2
            },
            "showSymbol": true,
            "symbolSize": 20,
            "yAxisIndex": 0,
            "connectNulls": true
        }
    ],
    "xAxis": [
        {
            "type": "time",
            "name": "",
            "nameLocation": "center",
            "nameGap": 28,
            "nameTextStyle": {
                "color": "#A4A5A7"
            },
            "axisLabel": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "axisLine": {
                "lineStyle": {
                    "color": "#5e5e5e40"
                }
            },
            "splitLine": {
                "lineStyle": {
                    "color": [
                        "#5E5E5E"
                    ],
                    "opacity": 0.1
                }
            },
            "axisTick": null,
            "z": 2
        }
    ],
    "dataZoom": [
        {
            "show": true,
            "filterMode": "none",
            "xAxisIndex": 0,
            "realtime": true,
            "textStyle": {
                "color": "#5E5E5E",
                "fontSize": 11
            },
            "dataBackground": {
                "areaStyle": {
                    "color": "#5E5E5E"
                }
            },
            "right": 45,
            "left": 45
        }
    ],
    "legend": {
        "itemHeight": 7,
        "itemWidth": 15,
        "pageIconColor": "#5E5E5E",
        "pageIconInactiveColor": "#5E5E5E",
        "pageTextStyle": {
            "color": "#5E5E5E"
        },
        "textStyle": {
            "color": "#5E5E5E"
        },
        "top": 0,
        "type": "scroll",
        "show": true
    },
    "grid": {
        "bottom": 60,
        "containLabel": true,
        "left": 60,
        "right": 60,
        "top": 10
    },
    "tooltip": {
        "confine": true,
        "trigger": "axis",
        "axisPointer": {
            "type": "shadow"
        }
    }
}
```

</details>


# Gauge

Visualize single-value data in Ubidots in the form of Gauge charts

The Gauge widget provides a clear and intuitive way to visualize single-value metrics. It is ideal for representing KPIs, performance scores, percentages, or real-time sensor readings. The widget lets users quickly assess the current state of a variable against defined ranges.

For a basic overview of the widget, visit [this article](https://help.ubidots.com/en/articles/8183881-ubidots-basics-widgets), which covers the basics of setting up a Gauge widget.

This section covers only the advanced features of the Gauge widget.

## ECharts configuration

{% hint style="warning" %}
This option is only available to Enterprise users.
{% endhint %}

[ECharts](https://echarts.apache.org/en/index.html) is a powerful open-source visualization library designed to create interactive and customizable charts. ECharts provides extensive customization options that let you tailor the appearance and behavior of gauges to fit specific requirements.

The Gauge widget is built on top of the ECharts library. This gives users access to all features provided by the library through the **ECharts configuration** option in the **APPEARANCE** tab:

<figure><img src="/files/dwQNaUmX31U73Pk1PqbO" alt="" width="563"><figcaption></figcaption></figure>

After you click the option, a large drawer opens from the left and shows the [ECharts options JSON](https://echarts.apache.org/en/option.html#title). It maps all fields available in the UI and also provides an editing environment where users can take full advantage of the available series types.

{% hint style="info" %}
All fields exposed through the UI are mapped bidirectionally. This means that if you make a change in the UI, it is reflected in the ECharts options JSON, and vice versa.
{% endhint %}

<figure><img src="/files/HMw88SACm5hSsB7UgIBt" alt="" width="563"><figcaption></figcaption></figure>

## Examples

The examples below use the ECharts configuration option to produce the results shown. Without this advanced option, you cannot get the same visualization using only the fields available in the UI.

## Full circle Gauge

<figure><img src="/files/NUyALMgOIMeiHsTwAxoJ" alt="" width="336"><figcaption></figcaption></figure>

<details>

<summary>JSON Example</summary>

```json
{
    "media": [
        {
            "query": {
                "maxHeight": 165
            },
            "option": {
                "series": [
                    {
                        "center": [
                            "50%",
                            "50%"
                        ],
                        "radius": "80%",
                        "axisLine": {
                            "lineStyle": {
                                "width": 24
                            }
                        }
                    },
                    {
                        "center": [
                            "50%",
                            "50%"
                        ],
                        "radius": "80%",
                        "progress": {
                            "width": 12
                        },
                        "detail": {
                            "rich": {
                                "unit": {
                                    "fontSize": 8,
                                    "lineHeight": 10
                                },
                                "value": {
                                    "fontSize": 10,
                                    "lineHeight": 12
                                }
                            },
                            "offsetCenter": [
                                0,
                                "6%"
                            ],
                            "width": 62,
                            "height": 20,
                            "borderRadius": 12
                        }
                    }
                ]
            }
        },
        {
            "query": {
                "maxAspectRatio": 1
            },
            "option": {
                "series": [
                    {},
                    {
                        "detail": {
                            "rich": {
                                "unit": {
                                    "fontSize": 10
                                },
                                "value": {
                                    "fontSize": 14
                                }
                            },
                            "offsetCenter": [
                                0,
                                "8%"
                            ]
                        }
                    }
                ]
            }
        },
        {
            "query": {
                "maxAspectRatio": 1.12,
                "minAspectRatio": 1
            },
            "option": {
                "series": [
                    {},
                    {
                        "detail": {
                            "rich": {
                                "unit": {
                                    "fontSize": 11
                                },
                                "value": {
                                    "fontSize": 18
                                }
                            },
                            "offsetCenter": [
                                0,
                                "8%"
                            ]
                        }
                    }
                ]
            }
        }
    ],
    "series": [
        {
            "type": "gauge",
            "min": 0,
            "max": 100,
            "startAngle": 90,
            "endAngle": -270,
            "center": [
                "50%",
                "50%"
            ],
            "radius": "86%",
            "pointer": {
                "show": false
            },
            "progress": {
                "show": false
            },
            "axisLine": {
                "lineStyle": {
                    "width": 32,
                    "color": [
                        [
                            1,
                            "#E9EDF2"
                        ]
                    ]
                }
            },
            "axisTick": {
                "show": false
            },
            "splitLine": {
                "show": false
            },
            "axisLabel": {
                "show": false
            },
            "title": {
                "show": false
            },
            "detail": {
                "show": false
            }
        },
        {
            "type": "gauge",
            "min": 0,
            "max": 100,
            "startAngle": 90,
            "endAngle": -270,
            "center": [
                "50%",
                "50%"
            ],
            "radius": "86%",
            "pointer": {
                "show": false
            },
            "axisLine": {
                "lineStyle": {
                    "width": 0
                }
            },
            "progress": {
                "clip": false,
                "show": true,
                "width": 16,
                "roundCap": true,
                "itemStyle": {
                    "borderColor": "#464646",
                    "borderWidth": 1
                }
            },
            "axisTick": {
                "show": false
            },
            "splitLine": {
                "show": false
            },
            "axisLabel": {
                "show": false
            },
            "title": {
                "show": false
            },
            "detail": {
                "rich": {
                    "unit": {
                        "padding": [
                            0,
                            0,
                            0,
                            2
                        ],
                        "fontSize": "10%",
                        "fontWeight": 400,
                        "lineHeight": 22
                    },
                    "value": {
                        "fontSize": "20%",
                        "fontWeight": "bold",
                        "lineHeight": 22
                    }
                },
                "formatter": "{value|{value}}{unit| {unit}}",
                "offsetCenter": [
                    0,
                    "10%"
                ],
                "valueAnimation": true,
                "width": 70,
                "height": 24,
                "borderWidth": 1,
                "borderRadius": 14,
                "color": "inherit",
                "borderColor": "inherit"
            }
        }
    ]
}
```

</details>

## Speed meter gauge

<figure><img src="/files/aejVjZzn97tVHtDcSWjn" alt="" width="375"><figcaption></figcaption></figure>

<details>

<summary>JSON Example</summary>

```json
{
    "media": [
        {
            "query": {
                "maxHeight": 180
            },
            "option": {
                "series": [
                    {
                        "detail": {
                            "rich": {
                                "unitFormatter": {
                                    "padding": [
                                        0,
                                        0,
                                        0,
                                        4
                                    ],
                                    "fontSize": 12,
                                    "lineHeight": 20
                                },
                                "valueFormatter": {
                                    "fontSize": 18,
                                    "fontWeight": "bold",
                                    "lineHeight": 20
                                }
                            },
                            "show": true,
                            "formatter": "{valueFormatter|{value}}{unitFormatter| {unit}}",
                            "offsetCenter": [
                                0,
                                "10%"
                            ],
                            "valueAnimation": true
                        },
                        "pointer": {
                            "show": false
                        },
                        "axisLine": {
                            "lineStyle": {
                                "width": 14
                            }
                        },
                        "axisTick": {
                            "show": false
                        },
                        "axisLabel": {
                            "show": false
                        },
                        "splitLine": {
                            "show": false
                        }
                    }
                ]
            }
        },
        {
            "query": {
                "maxHeight": 260
            },
            "option": {
                "series": [
                    {
                        "detail": {
                            "rich": {
                                "unitFormatter": {
                                    "padding": [
                                        0,
                                        0,
                                        0,
                                        4
                                    ],
                                    "fontSize": 14,
                                    "lineHeight": 24
                                },
                                "valueFormatter": {
                                    "fontSize": 22,
                                    "fontWeight": "bold",
                                    "lineHeight": 24
                                }
                            },
                            "show": true,
                            "color": "inherit",
                            "formatter": "{valueFormatter|{value}}{unitFormatter| {unit}}",
                            "offsetCenter": [
                                0,
                                "15%"
                            ],
                            "valueAnimation": true
                        },
                        "pointer": {
                            "show": true,
                            "itemStyle": {
                                "color": "auto"
                            }
                        },
                        "axisLine": {
                            "lineStyle": {
                                "width": 22
                            }
                        },
                        "axisTick": {
                            "show": true,
                            "length": 6,
                            "distance": -22,
                            "lineStyle": {
                                "color": "#fff",
                                "width": 2
                            },
                            "splitNumber": 1
                        },
                        "axisLabel": {
                            "show": false
                        },
                        "splitLine": {
                            "show": true,
                            "length": 18,
                            "distance": -22,
                            "lineStyle": {
                                "color": "#fff",
                                "width": 3
                            }
                        }
                    }
                ]
            }
        },
        {
            "query": {
                "maxAspectRatio": 1.05
            },
            "option": {
                "series": [
                    {
                        "detail": {
                            "rich": {
                                "unitFormatter": {
                                    "fontSize": 16,
                                    "lineHeight": 26
                                },
                                "valueFormatter": {
                                    "fontSize": 24,
                                    "lineHeight": 26
                                }
                            },
                            "offsetCenter": [
                                0,
                                "18%"
                            ]
                        },
                        "axisLabel": {
                            "show": true,
                            "color": "inherit",
                            "distance": 26,
                            "fontSize": 14
                        }
                    }
                ]
            }
        }
    ],
    "series": [
        {
            "max": 10,
            "min": 0,
            "type": "gauge",
            "detail": {
                "rich": {
                    "unitFormatter": {
                        "padding": [
                            0,
                            0,
                            0,
                            6
                        ],
                        "fontSize": 18,
                        "lineHeight": 30
                    },
                    "valueFormatter": {
                        "fontSize": 28,
                        "fontWeight": "bold",
                        "lineHeight": 30
                    }
                },
                "show": true,
                "color": "inherit",
                "formatter": "{valueFormatter|{value}}{unitFormatter| {unit}}",
                "offsetCenter": [
                    0,
                    "20%"
                ],
                "valueAnimation": true
            },
            "pointer": {
                "show": true,
                "itemStyle": {
                    "color": "auto"
                }
            },
            "axisLine": {
                "lineStyle": {
                    "color": [
                        [
                            0.3,
                            "#67e0e3"
                        ],
                        [
                            0.7,
                            "#37a2da"
                        ],
                        [
                            1,
                            "#fd666d"
                        ]
                    ],
                    "width": 30
                }
            },
            "axisTick": {
                "show": true,
                "length": 8,
                "distance": -30,
                "lineStyle": {
                    "color": "#fff",
                    "width": 2
                },
                "splitNumber": 1
            },
            "axisLabel": {
                "show": true,
                "color": "inherit",
                "distance": 40,
                "fontSize": 20
            },
            "splitLine": {
                "show": true,
                "length": 30,
                "distance": -30,
                "lineStyle": {
                    "color": "#fff",
                    "width": 4
                }
            }
        }
    ]
}
```

</details>

## Pressure gauge

<figure><img src="/files/323mTxGmcc4UutzxsSsz" alt="" width="375"><figcaption></figcaption></figure>

<details>

<summary>JSON Example</summary>

```json
{
    "media": [
        {
            "query": {
                "maxHeight": 165
            },
            "option": {
                "series": [
                    {
                        "center": [
                            "50%",
                            "66%"
                        ],
                        "detail": {
                            "rich": {
                                "u": {
                                    "padding": [
                                        0,
                                        0,
                                        0,
                                        2
                                    ],
                                    "fontSize": 9,
                                    "lineHeight": 16
                                },
                                "v": {
                                    "fontSize": 14,
                                    "fontWeight": "bold",
                                    "lineHeight": 16
                                }
                            },
                            "formatter": "{v|{value}}{u| {unit}}",
                            "borderWidth": 0,
                            "offsetCenter": [
                                0,
                                "8%"
                            ]
                        },
                        "radius": "82%",
                        "axisLine": {
                            "lineStyle": {
                                "width": 14
                            }
                        },
                        "axisTick": {
                            "length": 4,
                            "distance": -22,
                            "lineStyle": {
                                "color": "#999999",
                                "width": 1
                            }
                        },
                        "axisLabel": {
                            "color": "#999999",
                            "distance": -18,
                            "fontSize": 8
                        },
                        "splitLine": {
                            "length": 8,
                            "distance": -26,
                            "lineStyle": {
                                "color": "#999999",
                                "width": 2
                            }
                        }
                    }
                ]
            }
        }
    ],
    "series": [
        {
            "max": 2000,
            "min": 0,
            "type": "gauge",
            "title": {
                "show": false
            },
            "anchor": {
                "show": false
            },
            "center": [
                "50%",
                "70%"
            ],
            "detail": {
                "rich": {
                    "u": {
                        "padding": [
                            0,
                            0,
                            0,
                            6
                        ],
                        "fontSize": 14,
                        "lineHeight": 32
                    },
                    "v": {
                        "fontSize": 30,
                        "fontWeight": "bolder",
                        "lineHeight": 32
                    }
                },
                "color": "inherit",
                "formatter": "{v|{value}}{u| {unit}}",
                "borderWidth": 0,
                "offsetCenter": [
                    0,
                    "-8%"
                ],
                "valueAnimation": true
            },
            "radius": "84%",
            "pointer": {
                "show": false
            },
            "axisLine": {
                "lineStyle": {
                    "color": [
                        [
                            1,
                            "#E9EDF2"
                        ]
                    ],
                    "width": 22
                }
            },
            "axisTick": {
                "length": 6,
                "distance": -30,
                "lineStyle": {
                    "color": "#999999",
                    "width": 2
                },
                "splitNumber": 5
            },
            "endAngle": -20,
            "progress": {
                "show": true,
                "width": 22,
                "itemStyle": {
                    "color": "#FFAB91"
                }
            },
            "axisLabel": {
                "color": "#999999",
                "distance": -24,
                "fontSize": 14
            },
            "splitLine": {
                "length": 10,
                "distance": -34,
                "lineStyle": {
                    "color": "#999999",
                    "width": 3
                }
            },
            "startAngle": 200,
            "splitNumber": 10
        },
        {
            "max": 2000,
            "min": 0,
            "type": "gauge",
            "center": [
                "50%",
                "70%"
            ],
            "detail": {
                "show": false
            },
            "radius": "84%",
            "pointer": {
                "show": false
            },
            "axisLine": {
                "show": false
            },
            "axisTick": {
                "show": false
            },
            "endAngle": -20,
            "progress": {
                "show": true,
                "width": 6,
                "itemStyle": {
                    "color": "#FD7347"
                }
            },
            "axisLabel": {
                "show": false
            },
            "splitLine": {
                "show": false
            },
            "startAngle": 200
        }
    ]
}
```

</details>

## Humidity percentage

<figure><img src="/files/mBZSfIwpO1qbMRFZ6kCA" alt="" width="375"><figcaption></figcaption></figure>

<details>

<summary>JSON Example</summary>

```json
{
    "series": [
        {
            "type": "gauge",
            "min": 0,
            "max": 100,
            "startAngle": 180,
            "endAngle": 0,
            "splitNumber": 8,
            "center": [
                "50%",
                "75%"
            ],
            "radius": "90%",
            "axisLine": {
                "lineStyle": {
                    "width": 6,
                    "color": [
                        [
                            0.3,
                            "#FF6E76"
                        ],
                        [
                            0.6,
                            "#FDDD60"
                        ],
                        [
                            0.8,
                            "#58D9F9"
                        ],
                        [
                            1,
                            "#7CFFB2"
                        ]
                    ]
                }
            },
            "pointer": {
                "icon": "path://M12.8,0.7l12,40.1H0.7L12.8,0.7z",
                "length": "12%",
                "width": 20,
                "offsetCenter": [
                    0,
                    "-60%"
                ],
                "itemStyle": {
                    "color": "auto"
                }
            },
            "axisTick": {
                "length": 12,
                "lineStyle": {
                    "color": "auto",
                    "width": 2
                }
            },
            "splitLine": {
                "length": 20,
                "lineStyle": {
                    "color": "auto",
                    "width": 5
                }
            },
            "axisLabel": {
                "color": "auto",
                "fontSize": 14,
                "distance": -60,
                "rotate": "tangential",
                "formatter": "{value}"
            },
            "title": {
                "show": false
            },
            "detail": {
                "fontSize": 28,
                "valueAnimation": true,
                "offsetCenter": [
                    0,
                    "-35%"
                ],
                "color": "inherit",
                "rich": {
                    "v": {
                        "fontSize": 30,
                        "fontWeight": "bold",
                        "lineHeight": 36
                    },
                    "u": {
                        "fontSize": 14,
                        "lineHeight": 36,
                        "padding": [
                            0,
                            0,
                            0,
                            6
                        ]
                    }
                },
                "formatter": "{v|{value}}{u| {unit}}"
            }
        }
    ]
}
```

</details>


# Getting started

"UbiFunctions" is a serverless computing environment for IoT. Also known as FaaS (Functions as a Service), it lets you run custom functions on demand directly from Ubidots.

Although originally designed as a custom decoding tool, it has grown into an integration and analytics powerhouse.

![](/files/-MiUdQW45kGPwsIq39xc)

### Definition

An UbiFunction is user-defined NodeJS or Python code that runs on-demand, and can be invoked using any of these methods:

* Making an HTTP GET or POST request to the UbiFunction’s URL
* Publishing data through MQTT to the UbiFunction's URL
* Configuring an Ubidots event action
* A time-trigger specified by the user

### When to use UbiFunctions?

Customers commonly use UbiFunctions to:

* Extract data from third-party APIs and insert it into Ubidots
* Build a custom HTTP API gateway to receive data in a custom format
* Extract data from Ubidots, run a custom analysis or transformation, and insert the results back into Ubidots

### How to add UbiFunctions to my account?

This module is available on the [Industrial plan](https://help.ubidots.com/en/articles/7827108-plans-billing-understanding-the-advantages-of-the-industrial-plan) and above. To upgrade to an Industrial plan:

1. Select **My Profile** from the user dropdown in the top-right corner of your Admin App.
2. Select **Billing** from the left navigation panel.
3. Click **Change plan**, then select the **Industrial** plan or above.
4. Click **Update Plan** to save the change and activate UbiFunctions.

### Billing

The UbiFunctions module includes a limited number of executions and is billed based on usage:

* Includes 1 thousand executions per month
* When an execution exceeds 2 seconds, it counts as 2 or more executions in 2-second increments
* Executions are billed at **$5 per million executions.**


# Creating an UbiFunction

Step-by-step guide to creating a UbiFunction

You can create and develop UbiFunctions in two ways:

1. **Web UI** — Create and edit functions directly in your Ubidots account.
2. **CLI** — Create, test, and deploy functions locally with full runtime replication. Local development unlocks your preferred IDE and AI-powered tools like Claude Code.

```bash
ubidots functions new --name my-function --runtime python3.11:base
ubidots functions start --methods POST
```

{% hint style="info" %}
See the full [CLI reference for UbiFunctions](/sdks/cli/ubifunctions) for local development workflows, including push, pull, and live testing.
{% endhint %}

## Using the Web UI

1. In your Ubidots account, go to **Devices** and select **Functions**.

<figure><img src="/files/VnbeR0dnre19UpltY4mO" alt=""><figcaption></figcaption></figure>

2. Click the `+` icon to create your first UbiFunction.

<figure><img src="/files/4C0bVWJ8tiRrDUzkHF17" alt=""><figcaption></figcaption></figure>

3. Give your function a name.

{% hint style="warning" %}
Your function name becomes part of its URL. If you rename the function later, the URL stays the same.
{% endhint %}

<figure><img src="/files/z28GRISfdoDgGQjx1pNM" alt=""><figcaption></figcaption></figure>

Enter the name in the corresponding field:

<figure><img src="/files/cwYUikWJPJw1jTIUYN3V" alt=""><figcaption></figcaption></figure>

4. Scroll down to the *Runtime* section and select the runtime you need.

<figure><img src="/files/JgewrNTcICvtSM9tS6qT" alt=""><figcaption></figcaption></figure>

5. Scroll down to the *HTTP Method* section and select the HTTP method this function will allow.

<figure><img src="/files/vjhQK2mGwTkdJHPaJYUb" alt=""><figcaption></figcaption></figure>

6. Configure any additional options:

* **Token**: Select a token from your account to use in your function code.
* **Environment variables**: Select one or more variables from your account's [Global Properties](https://help.ubidots.com/en/articles/9265555-ubifunctions-using-global-properties) to use in the function code.
* **Max. execution time**: Set the maximum time your function can take to execute.
* **Time-based trigger**: Create a cron UbiFunction based on a time interval or a crontab expression.
* **Raw function**: Pass HTTP headers and URL paths to the function. Use content types other than `application/json`. Return custom response codes from the function.
* **CORS Policy**: Enable calls to this function from browser URLs other than `parse.ubidots.com`.

<figure><img src="/files/pSttImW4goxlcTxBq6rd" alt=""><figcaption></figcaption></figure>

7. Scroll to the bottom of the page and click *SAVE* to save these settings.

<figure><img src="/files/bhbGIvhiZDxEPGlXNMIg" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/jcUsLGsydoA9IvNavgnd" alt=""><figcaption></figcaption></figure>


# Coding an UbiFunction

## Runtimes

UbiFunctions accept [NodeJS](https://nodejs.org/en/blog/release/v10.0.0/) or [Python](https://www.python.org/downloads/release/python-370/). You can use these languages to write the logic required to extract, transform, and analyze data.

## Entrypoint

Regardless of the chosen runtime, the UbiFunction must define a *main* function. This function serves as the entry point, meaning it is the first function called when a request is made to the UbiFunction.

## UbiFunction arguments

UbiFunctions require a JSON payload in the body of the HTTP request. This payload is passed to the `main` function as a dictionary object named `args`. The `args` object contains all parameters sent in the request, making them accessible within your function for further use.

For UbiFunctions configured with the **GET** method, the `args` object instead contains the **query parameters** passed in the URL.

Additionally, when an `X-Auth-Token` header is included in the request, its value is automatically injected into `args` as `args["token"]`, regardless of whether the request is made via `GET` or `POST`.

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

```javascript
async function main(args) {
  var ubidots_token = args?.token;
  var device_label = args.device;
}
```

{% endtab %}

{% tab title="Python" %}

```python
def main(args):
    token = args.get('token', None)
    device = args.get('device', None)
```

{% endtab %}
{% endtabs %}

| Object       | Description                                                                                                     |
| ------------ | --------------------------------------------------------------------------------------------------------------- |
| `args`       | Any payload sent to the function will be contained in the `args` object.                                        |
| `args.token` | When an `x-auth-token` header is included in an HTTP invocation request, the `args.token` key will be included. |

### Behavior of the `args` object according to HTTP methods

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

```http
GET /prv/your_user/function_name?device=my_device&limit=10 HTTP/1.1
Host: parse.ubidots.com
X-Auth-Token: BBUS-abcdef0987654321

// Args within the UbiFunction
{
   "device": "my_device",
   "limit": "10",
   "token": "BBUS-abcdef0987654321"
}
```

{% endtab %}

{% tab title="POST" %}

```http
POST /prv/your_user/function_name HTTP/1.1
Host: parse.ubidots.com
Content-Type: application/json
X-Auth-Token: BBFF-1234567890abcdef

{
  "temperature": 22.5,
  "location": "lab"
}


// Args within the UbiFunction
//{
//  "temperature": 22.5,
//  "location": "lab",
//  "token": "BBFF-1234567890abcdef"
//}

```

{% endtab %}
{% endtabs %}

## Code

By default, every new UbiFunction includes sample code that uses input data (`token`, `device`, and a `variable value`) to make a request to the Ubidots API. Here are the default examples for reference:

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

```javascript
const axios = require('axios');

// Main function - runs every time the function is executed.
// "args" is a dictionary containing both the URL params and the HTTP body (for POST requests).
async function main(args) {

  // Grab the token and device label from URL parameters, then erase them from the args dictionary
  var ubidots_token = args.token;
  var device_label = args.device;
  delete args['token'];
  delete args['device'];

  // Use the remaining parameters as payload
  var payload = args;

  // Log the payload to the console for debugging purposes. You can access the function's logs using
  // the option in the header above.
  console.log(payload);

  // Send the payload to Ubidots
  var response = await ubidots_request(ubidots_token, device_label, payload);

  // Log Ubidots response to the console
  console.log(response);

  // Pass Ubidots' API response as the function's response
  return response;
}

// This function builds an HTTP POST request to Ubidots
async function ubidots_request(token, label, body) {
  let config = {
    method: 'post',
    url: 'https://industrial.api.ubidots.com/api/v1.6/devices/' + label,
    data: body,
    headers: {
      'Content-Type': 'application/json',
      'X-Auth-Token': token
    }
  }
  const response = await axios.request(config);
  return response.data;
}

```

{% endtab %}

{% tab title="Python" %}

```python
import requests
import time

BASE_URL = "https://industrial.api.ubidots.com"

REQUESTS_FUNCTIONS = {"get": requests.get, "post": requests.post}

def main(args):
    '''
    Main function - runs every time the function is executed.
    "args" is a dictionary containing both the URL params and the HTTP body (for POST requests).
    '''
    token = args.get('token', None)
    device = args.get('device', None)

    if token is None or device is None:
        print("[ERROR] Please send your Ubidots token and device label in your args")
        return {"status": "error"}
    
    del args['token']
    del args['device']

    # Log the payload to the console for debugging purposes. You can access the function's logs using
    # the option in the header above.

    print("[INFO] Payload to send: {}".format(args))

    # Use the remaining parameters as payload
    req = update_device(device, args, token)

    # Prints the request result

    print("[INFO] Request result:")
    print(req.text)

    return {"status": "Ok", "result": req.json()}

def update_device(device, payload, token):
    """
    updates a variable with a single dot
    """

    url = "{}/api/v1.6/devices/{}".format(BASE_URL, device)
    headers = {"X-Auth-Token": token, "Content-Type": "application/json"}

    req = create_request(url, headers, payload, attempts=5, request_type="post")
    
    return req

def create_request(url, headers, data, attempts, request_type):
    """
    Function to create a request to the server
    """

    request_func = REQUESTS_FUNCTIONS.get(request_type)

    kwargs = {"url": url, "headers": headers}

    if request_type == "post":
        kwargs["json"] = data

    try:
        req = request_func(**kwargs)
        print("[INFO] Request result: {}".format(req.text))
        status_code = req.status_code
        time.sleep(1)

        while status_code >= 400 and attempts < 5:
            req = request_func(**kwargs)
            print("[INFO] Request result: {}".format(req.text))
            status_code = req.status_code
            attempts += 1
            time.sleep(1)

        return req
    except Exception as e:
        print("[ERROR] There was an error with the request, details:")
        print(e)
        return None

```

{% endtab %}
{% endtabs %}

## Output Response

The output of a UbiFunction must be a JSON object. By default, the status code will be `200`, but this can be changed using [Raw Functions](/ubifunctions/advanced/raw-functions).

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

```javascript
function main(args) {
    return {'temperature' : 56}
}
```

{% endtab %}

{% tab title="Python" %}

```python
def main():
    return {'temperature' : 56}
```

{% endtab %}
{% endtabs %}


# Testing an UbiFunction

How to test-run your UbiFunction

To test an UbiFunction, click the *Save & Run Test* button:

<figure><img src="/files/j24R5M2LUHZbtQx0lxS7" alt=""><figcaption></figcaption></figure>

This opens a modal where you can enter a JSON object to test the UbiFunction. Click *SAVE* to execute the UbiFunction with the JSON object you entered.

<figure><img src="/files/Mkuj1YaEz9AS4oEiBsab" alt="" width="442"><figcaption></figcaption></figure>

{% hint style="info" %}
If the UbiFunction doesn't require parameters, pass an empty object: `{}`.
{% endhint %}


# Authentication

UbiFunctions' URLs are publicly exposed. This means that anyone with the URL path can invoke your code.

To add an authentication layer to your function, add a preliminary step before execution to check the token in the request.

Ubidots automatically passes the content of the **X-Auth-Token** header as a function argument:

| Request Header | Key inside Function |
| -------------- | ------------------- |
| X-Auth-Token   | TOKEN               |

See a Python example below:

```python
import requests as rq
import json

'''
Returns True or False based on the validity of the token
'''

def account_auth(token):

    response = {
        200: True,
        403: False
    }

    HOST = "https://industrial.api.ubidots.com"
    PATH = "/api/v1.6/user_check/"
    PARAMETERS = "?token={}".format(token)
    URL = "{}{}{}".format(HOST, PATH, PARAMETERS)

    r = rq.get(URL)

    code = r.status_code

    return response[code]

def main(args):

    token = args.get("token", None)

    if token is None:
        return {"ERROR": "Missing token"}

    valid = account_auth(token)

    if valid is not True:
        return {"ERROR": "Token not valid"}
```

{% hint style="info" %}
Please note that the function still executes even if the token is invalid, and it still counts toward your usage. We are working on a new method to optionally authenticate a function the same way you authenticate any Ubidots API request.
{% endhint %}


# Execution Time

By default, each UbiFunction times out after 30 seconds. To extend this limit, [contact our support team](https://help.ubidots.com/en/articles/4683984-need-support-find-ubidots-in-app-chat-channel).

{% hint style="info" %}
UbiFunctions are billed per million executions. If an execution runs longer than 2 seconds, it counts as 2 executions or more, based on its duration. **The cost is $5 per million UbiFunction executions.**

Keep this in mind when increasing the execution time. You can check your UbiFunctions usage in your [account profile](https://industrial.ubidots.com/accounts/me/usage).
{% endhint %}


# Logs

A great debugging resource is the ability to view your function logs. To do so, go to the left sidebar and select the **Logs** icon, as shown below, to view your function's execution history:

<figure><img src="/files/HFaAMkklPXMexkqts0jK" alt=""><figcaption></figcaption></figure>

In the function logs, you can confirm that data was successfully posted to Ubidots with the **201** response code:

<figure><img src="/files/tjXAOdPrQRs2mcNQEJEW" alt=""><figcaption></figcaption></figure>

This is what your function returns. It must be in JSON dictionary format. In your code, make sure to use **return** to exit the function and return the dictionary.

<figure><img src="/files/RhqcB2leDGveVo6mYyaG" alt="" width="257"><figcaption></figcaption></figure>

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

```javascript
return response.data;
```

{% endtab %}

{% tab title="Python" %}

```python
return {"status": "Ok", "result": req.json()}
```

{% endtab %}
{% endtabs %}

You can also use logs for debugging. Print any variables defined earlier, or print any message that needs to be displayed to the user.

<figure><img src="/files/CvUd3tgzCbZkpLHxfJUC" alt=""><figcaption></figcaption></figure>

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

```javascript
console.log("[INFO] Request result:");
console.log(response);
```

{% endtab %}

{% tab title="Python" %}

```python
print("[INFO] Request result:")
print(req.text)
```

{% endtab %}
{% endtabs %}


# Runtimes

Ubidots supports the following runtimes:

* [Python](/ubifunctions/runtimes/python)
* [NodeJS](/ubifunctions/runtimes/nodejs)
* [Custom Runtimes](/ubifunctions/runtimes/custom-runtimes)

## Runtime versions

As of this writing, Ubidots supports the following runtime versions:

* Python 3.9
* Python 3.11
* Node.js 20

## Layers

Ubidots runtime architecture has three layers. Each layer includes a specific set of libraries and capabilities.

* **Lite:** The lightest option. It offers a minimal environment that is fast and efficient.
* **Base:** A step up from `Lite`. It includes more libraries for broader functionality.
* **Full:** The most complete option. It is available only for Python runtimes on Enterprise licenses or above.


# Python

Python runtimes include the standard [Python batteries](https://docs.python.org/3/tutorial/stdlib.html), plus additional libraries depending on the selected runtime: `Lite`, `Base`, or `Full`.

{% hint style="info" %}
The heavier the runtime, the longer your UbiFunction takes to execute or update after changes. **We recommend using the `Lite` version unless a heavier runtime is strictly required.**
{% endhint %}

### Lite <a href="#h_5860f0afab" id="h_5860f0afab"></a>

| Library  | Version |
| -------- | ------- |
| requests | 2.32.3  |

### Base <a href="#h_3496c389a7" id="h_3496c389a7"></a>

All libraries included in the `Lite` runtime, plus:

<table><thead><tr><th width="432">Library</th><th>Version</th></tr></thead><tbody><tr><td>pytz</td><td>2024.1</td></tr><tr><td>msgpack</td><td>1.0.8</td></tr><tr><td>httpx</td><td>0.27.0</td></tr><tr><td>aiohttp</td><td>3.9.5</td></tr><tr><td>lxml</td><td>5.2.2</td></tr><tr><td>numpy</td><td>2.0.0</td></tr><tr><td>pandas</td><td>2.2.2</td></tr><tr><td>python-aqi</td><td>0.6.1</td></tr></tbody></table>

### Full <a href="#h_a6f43dab0f" id="h_a6f43dab0f"></a>

{% hint style="success" %}
Only available to [Enterprise license](https://ubidots.com/enterprise) holders and above.
{% endhint %}

All libraries included in the `Base` runtime, plus:

| Library     | Version |
| ----------- | ------- |
| scrapy      | 2.11.2  |
| matplotlib  | 3.9.0   |
| arrow       | 1.3.0   |
| pymongo     | 4.8.0   |
| psycopg     | 3.1.18  |
| pyodbc      | 5.1.0   |
| stripe      | 8.7.0   |
| pytesseract | 0.3.10  |
| holidays    | 0.52    |
| zeep        | 4.2.1   |
| scipy       | 1.14.0  |
| pyod        | 1.1.2   |
| openpyxl    | 3.1.5   |


# NodeJS

You can import any of the following libraries in your code, depending on the runtime layer you use.

### Lite <a href="#h_aaca622b56" id="h_aaca622b56"></a>

| Library | Version |
| ------- | ------- |
| axios   | 1.5.1   |

### Base <a href="#h_4867aaa70d" id="h_4867aaa70d"></a>

| Library         | Version |
| --------------- | ------- |
| axios           | 1.5.1   |
| qs              | 6.11.2  |
| nexmo           | 2.9.1   |
| lodash          | 4.17.21 |
| mqtt            | 5.0.5   |
| moment          | 2.29.4  |
| mongodb         | 4.9.0   |
| request-promise | 4.2.4   |
| node-fetch      | 3.3.2   |


# Custom runtimes

Custom runtimes are available on demand. Ubidots can build a custom layer for your desired runtime environment. This feature is only available on Enterprise plans. Please [reach out to our support team](https://help.ubidots.com/en/articles/4683984-need-support-find-ubidots-in-app-chat-channel) for more information.


# Invocation


# Time-based trigger

You can schedule your function's execution using the *Time-based trigger* in the settings panel. It lets you trigger the function **every x minutes** or with a **Crontab expression**:

<figure><img src="/files/zLLmqxGbmLZMHIjnQWgR" alt=""><figcaption></figcaption></figure>

### Every x minutes

You can trigger a function at periodic intervals, expressed in minutes. This trigger method runs the UbiFunction's `main` function. The minimum and maximum values allowed in this field are 1 and 60 minutes, respectively.

<figure><img src="/files/Rw4CVq8br1unof13QtBf" alt=""><figcaption></figcaption></figure>

### Crontab expression

A cron expression is a string made up of five subexpressions, or fields, that define the schedule. These fields are separated by whitespace and can contain any allowed values, with various combinations of the allowed characters for each field.

| Field        | Values          | Wildcards |
| ------------ | --------------- | --------- |
| Minutes      | 0-59            | , - \* /  |
| Hours        | 0-23            | , - \* /  |
| Day-of-month | 1-31            | , - \* /  |
| Month        | 1-12 or JAN-DEC | , - \* /  |
| Day-of-week  | 1-7 or SUN-SAT  | , - \* /  |

**Wildcards**

<table><thead><tr><th width="135">Wildcard</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td>comma ","</td><td>List of allowed values</td><td>In the Month field, JAN,FEB,MAR includes January, February, and March.</td></tr><tr><td>dash "-"</td><td>Range of allowed values</td><td>In the Day field, 1-15 includes days 1 through 15 of the specified month.</td></tr><tr><td>asterisk "*"</td><td>Includes all values in the field</td><td>In the Hours field, * includes every hour</td></tr><tr><td>slash "/"</td><td>Specifies increments</td><td>In the Minutes field, you could enter 1/10 to specify every tenth minute, starting from the first minute of the hour.</td></tr></tbody></table>

{% hint style="info" %}
Use [crontab.guru](https://crontab.guru/) to test your crontab expressions.
{% endhint %}

**Limitations**

* Cron expressions that lead to rates faster than 1 minute are not supported.

**Examples**

You can use the following sample cron strings when creating a rule with a schedule:

<table><thead><tr><th width="109">Minutes</th><th width="77">Hours</th><th width="117">Month day</th><th width="84">Month</th><th width="106">Week day</th><th>Meaning</th></tr></thead><tbody><tr><td>0</td><td>10</td><td>*</td><td>*</td><td>*</td><td>Run at 10:00 a.m. (UTC+0) every day</td></tr><tr><td>15</td><td>12</td><td>*</td><td>*</td><td>*</td><td>Run at 12:15 p.m. (UTC+0) every day</td></tr><tr><td>0</td><td>18</td><td>*</td><td>*</td><td>MON-FRI</td><td>Run at 6:00 p.m. (UTC+0) every Monday through Friday</td></tr><tr><td>0</td><td>8</td><td>1</td><td>*</td><td>*</td><td>Run at 8:00 a.m. (UTC+0) every 1st day of the month</td></tr><tr><td>0/15</td><td>*</td><td>*</td><td>*</td><td>*</td><td>Run every 15 minutes</td></tr><tr><td>0/10</td><td>*</td><td>*</td><td>*</td><td>MON-FRI</td><td>Run every 10 minutes Monday through Friday</td></tr><tr><td>0/5</td><td>8-17</td><td>*</td><td>*</td><td>MON-FRI</td><td>Run every 5 minutes Monday through Friday between 8:00 a.m. and 5:55 p.m. (UTC+0)</td></tr><tr><td>0/30</td><td>20-2</td><td>*</td><td>*</td><td>MON-FRI</td><td><p>Run every 30 minutes Monday through Friday between 10:00 p.m. on the starting day to 2:00 a.m. on the following day (UTC)</p><p>Run from 12:00 a.m. to 2:00 a.m. on Monday morning (UTC).</p></td></tr></tbody></table>

<figure><img src="/files/FEF6Xnz0tcROOuCsmLBN" alt=""><figcaption></figcaption></figure>


# HTTPS

You can invoke UbiFunctions with HTTP `POST` or `GET` requests.

| Method | URL          | Args                                                                                                                                                                                                                                                                           | Response                     |
| ------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
| POST   | Function URL | <ul><li>You can send any JSON object in the HTTP body. It will be available in the function as the <code>args</code> variable.</li><li>You can also send URL parameters, such as <code>?arg1=value1\&arg2=value2</code>. They will be parsed into <code>args</code>.</li></ul> | Defined inside your function |
| GET    | Function URL | <ul><li>No HTTP body is expected.</li><li>You can send URL parameters, such as <code>?arg1=value1\&arg2=value2</code>. They will be parsed into <code>args</code>.</li></ul>                                                                                                   | Defined inside your function |


# MQTT publish

You can invoke a UbiFunction by publishing data through MQTT. This makes it possible to integrate devices and gateways that may have a non-editable JSON payload that is not compatible with our [native MQTT broker](https://docs.ubidots.com/v1.6/reference/mqtt) or our [HTTP Data Ingestion API](https://docs.ubidots.com/v1.6/reference/http).

Because the UbiFunctions MQTT broker is shared across all functions, subscription requests are not supported **and only the MQTT Publish method is accepted**. Accordingly, all subscription requests are rejected.

{% hint style="success" %}
If you'd like to have a private MQTT broker mapped to a UbiFunction, [please reach out to our sales team](mailto:sales@ubidots.com). In this case, subscribe actions can be supported.
{% endhint %}

{% hint style="info" %}
The UbiFunction method must be set to `POST`.
{% endhint %}

Here are the MQTT settings needed to invoke a UbiFunction over MQTT:

| MQTT Settings | Value                                                   |
| ------------- | ------------------------------------------------------- |
| **URL**       | `functions.ubidots.com`                                 |
| **Port**      | <p>1883 for plain MQTT.<br>8883 for MQTT over TLS.</p>  |
| **Topic**     | `/prv/<your-username>/<function-label>`                 |
| **Username**  | Your account username                                   |
| **Password**  | Valid Token from your Ubidots account.                  |
| **ClientID**  | Any random string. Preferably longer than 15 characters |

### Topic structure

To trigger a UbiFunction through MQTT, the publish topic must follow this structure:

```
/prv/<username>/<function-label>
```

Where:

* `<username>` is your Ubidots account username.
* `<function-label>` is the function name in lowercase, with spaces replaced by dashes.

The base topic corresponds to the UbiFunction HTTPS Endpoint URL's path, as seen in the image below:

<figure><img src="/files/kkFxkFwhJGw6MjTzieae" alt=""><figcaption></figcaption></figure>

#### **Advanced Topics**

Additional topic levels are supported. To add more levels, simply add a forward slash ("`/`") after the base topic:

```
/prv/<username>/<function-label>/<level-1>/···/<level-N>
```

Where:

* `<level-N>` is any additional UTF-8 encoded string that you want to add to the topic.

Topics have the following limits:

| Topic Limits | Limit           |
| ------------ | --------------- |
| Length       | 1500 characters |
| Levels       | 10              |

### UbiFunction `args`

When triggering a UbiFunction through MQTT, you receive a JSON object in the `args` variable of the `main` function. The object has this structure:

```
{
  "topic": "<topic>",
  "payload": "<payload>",
  "from_client_id": "<client_id>",
}
```

Where:

| Key              | Type   | Value description                                          |
| ---------------- | ------ | ---------------------------------------------------------- |
| topic            | String | The multilevel publication topic                           |
| payload          | String | The payload sent by the device in the publication message. |
| from\_client\_id | String | The client ID used in the MQTT connection.                 |

### Certificates

Ubidots supports SSL V1.1, TLS V1.2, and V1.3. You can download our root certificates in different formats:

* [PEM file](https://z.cdn.ubidots.com/ustatic/certs/roots.pem): Certificate chain with two root certificates from our certificate authorities (CAs).
* [DER file](https://z.cdn.ubidots.com/ustatic/certs/roots.der): Same as the PEM file, with an alternative encoding.
* [CRT file](https://z.cdn.ubidots.com/ustatic/certs/roots.crt): Same as the PEM file, with a different extension. Often referred to as **.crt**, **.cert** or **.cer**.

### Examples

{% tabs %}
{% tab title="Port 1883" %}

```bash
mosquitto_pub \
-p 1883 \ 
-h functions.ubidots.com \
-t "/prv/<username>/<function-name>" \
-m '{"token": "TOKEN", "device": "device-label", "variable-label": value}' \
-u "<username>" \
-P "TOKEN" \
-q 1 -d
```

{% endtab %}

{% tab title="Port 8883" %}

```bash
mosquitto_pub \
-p 8883 \ 
-h functions.ubidots.com \
-t "/prv/<username>/<function-name>" \
-m '{"token": "TOKEN", "device": "device-label", "variable-label": value}' \
-u "<username>" \
-P "TOKEN" \
-q 1 -d \
--cafile /Users/user/Documents/Ubidots/roots.pem
```

{% endtab %}
{% endtabs %}


# CoAP

UbiFunctions can be invoked using the Constrained Application Protocol (CoAP), a lightweight request/response protocol designed for constrained devices and low-power networks. CoAP follows a REST-like interaction model similar to HTTP, but runs over UDP and minimizes message overhead, making it well-suited for embedded systems and sensor networks.

Ubidots supports CoAP for sending data to devices, variables, and user-defined functions. When invoking a UbiFunction through CoAP, devices can trigger custom server-side logic such as data processing, validation, or integrations with external services by using a minimal and efficient communication mechanism.

Currently, Ubidots supports **only the POST method** over CoAP.

***

### CoAP Settings

| Setting           | Value              |
| ----------------- | ------------------ |
| Protocol          | CoAP               |
| Host              | `coap.ubidots.com` |
| Port              | `5684`             |
| Supported methods | POST               |

**Notes**

* CoAP runs over UDP.
* TLS is not supported for CoAP.
* Only the POST method is currently available.

***

### Invoking an UbiFunction via CoAP

Each UbiFunction has a dedicated CoAP endpoint that lets you trigger it directly.

#### Endpoint

```
coap://coap.ubidots.com/functions/<username>/<function_label>
```

Where:

* `<username>` is your Ubidots account username.
* `<function_label>` is the label of the function you want to invoke.

All requests must be sent using the **POST** method.

***

### Authentication

Unlike sending data to devices or variables over CoAP, invoking a UbiFunction does not require an authentication token in the request.

No query parameters or headers are required for authentication when calling a function endpoint. Access is handled internally by Ubidots based on the function and account context.

***

### Payload Format

The payload sent to a UbiFunction over CoAP is fully defined by your function code.

UbiFunctions receive the request body exactly as it is sent. There is no fixed schema or enforced structure, allowing you to use:

* JSON objects
* Key-value pairs
* Arrays
* Plain text

Your function script is responsible for parsing and interpreting the payload according to your logic.

#### Example payload (JSON)

```json
{
  "device": "pump-01",
  "value": 42,
  "status": "ok"
}
```

***

### Example: Triggering a Function Using `coap-client`

You can use the `coap-client` utility from the `libcoap` package to test or invoke a UbiFunction from the command line.

```bash
coap-client \
  -m post \
  -e '{
    "device": "pump-01",
    "value": 42,
    "status": "ok"
  }' \
  "coap://coap.ubidots.com/functions/my-username/my-function_label/"
```

This request sends the payload directly to the function and triggers its execution.

***

### Function Response

The response returned by a function, if any, depends entirely on how the function is implemented. If your script generates a response, it will be returned to the CoAP client. Otherwise, no response content is expected.

***

### Limitations and Considerations

* Only the POST method is supported for CoAP requests.
* TLS is not supported for CoAP.
* Keep payloads compact to avoid fragmentation at the UDP/CoAP layer.
* The function CoAP endpoint does not create devices or variables. It only triggers function execution.
* Response behavior depends on the function implementation and should not be relied on for guaranteed delivery.


# Ubidots event

To trigger a UbiFunction through the Ubidots Events Engine, follow these steps:

### Create a new Event <a href="#id-1-create-a-new-event" id="id-1-create-a-new-event"></a>

Start by creating a new event from the [Events](https://industrial.ubidots.com/app/events/) section under the **Data** tab in the Ubidots navbar.

<figure><img src="/files/F3b6HkrQ4AIAduQThdVE" alt="" width="252"><figcaption></figcaption></figure>

### Configure the Event Triggers <a href="#id-2-configure-the-event-if-trigger" id="id-2-configure-the-event-if-trigger"></a>

Next, configure the event logic using Ubidots' `AND` and `OR` operators.

For additional details on how to configure conditional and complex Event triggers, check out our [Creating Conditional Events and Alerts](https://help.ubidots.com/user-guides/events-creating-conditional-events-and-alerts) user guide.

<figure><img src="/files/5Hhafg5P62RlFe3RW1l0" alt="" width="563"><figcaption></figcaption></figure>

### Configure the UbiFunction Action <a href="#id-3-configure-the-ubifunction-then-action" id="id-3-configure-the-ubifunction-then-action"></a>

Once the **Triggers** logic is configured, create the action that calls a UbiFunction:

**Step 1.** Select the **UbiFunction** action.

<figure><img src="/files/lxoFxuryWcAWh8P5JTdz" alt="" width="563"><figcaption></figcaption></figure>

**Step 2.** Select the method used to trigger the UbiFunction: `GET` or `POST`. The UbiFunction must use the same method. **Note:** When you choose `GET`, the default `Content-Type: application/json` header disappears.

**Step 3.** Search for and select the UbiFunction in the dropdown menu.

**Step 4.** Add the required headers. `POST` requests require the default `Content-Type: application/json` header.

<figure><img src="/files/OXVYJYfP9fbu8FwyJE2X" alt="" width="563"><figcaption></figcaption></figure>

**Step 5.** Customize the **Active trigger** and **Back to normal** messages. **Note:** These fields always expect a JSON object. When the HTTP method is `GET`, the JSON is automatically converted into query parameters and appended to the UbiFunction URL.

<figure><img src="/files/NluElaTzTRDkk07oOvbi" alt="" width="563"><figcaption></figcaption></figure>

**Step 6.** Name the event, set the active days and hours, and save it.

<figure><img src="/files/FtXDgfdNddyp8QvJiRly" alt="" width="563"><figcaption></figcaption></figure>

Your UbiFunction now runs whenever the event is triggered.


# Advanced


# Account token

Securely access your token from a UbiFunction by selecting it in the panel dropdown and calling it with the `_auth_token` key from the `args` input argument. This avoids copying and pasting API tokens directly into your code.

<figure><img src="/files/b91qOeOQLliBWERQDePd" alt=""><figcaption></figcaption></figure>

For example:

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

```javascript
function main(args) {
    token = args._auth_token;
    console.log(`The token selected is: ${token}`);
    return {}
}
```

{% endtab %}

{% tab title="Python" %}

```python
def main(args):
    token = args["_auth_token"]
    print(f"The token selected is: {token}")
    return {}
```

{% endtab %}
{% endtabs %}


# Execution time

Within the UbiFunctions configuration, you can extend the execution time up to 40 seconds for tasks that take longer than expected. By default, the execution time is 10 seconds.

<figure><img src="/files/KrhUWktasiFYfY3vLzQ0" alt=""><figcaption></figcaption></figure>


# Raw functions

For simplicity, default UbiFunctions do not let you use custom response codes or a `Content-Type` header other than `application/json`.

But what if your application needs this level of customization? This is why **Raw functions** exist. They let you:

* Capture the entire path inside the function, allowing you to use custom endpoint paths in addition to the UbiFunction’s native path. This lets you execute different routines based on the endpoint path.
* Receive and return custom headers. For example, `Authorization` headers.
* Set custom response codes such as 2xx, 4xx, and 5xx beyond the standard `200` in UbiFunctions. This lets the HTTP client know the exact result of the execution.
* Receive and return custom `Content-Type` headers for the request body and response, such as `application/json` or `text/plain`.

To enable **Raw functions**, toggle the option in the left-side settings section of the UbiFunction.

<figure><img src="/files/cp6wG8VDPI4xlFNyb9K5" alt=""><figcaption></figcaption></figure>

### args Object

Once enabled, the `args` object received in the UbiFunction’s main function follows this structure:

```
{
    "path": string,
    "headers": object, 
    "body": string,
}
```

Where:

| Key     | Type   | Description                                                 |
| ------- | ------ | ----------------------------------------------------------- |
| path    | string | UbiFunction path. For example: `/prv/<function-name>/`      |
| headers | object | Key-value object containing all the headers of the request. |
| body    | string | Body of the request                                         |

The base UbiFunction URL structure must remain intact. Otherwise, the request points to a different UbiFunction.

### Response codes

When a default UbiFunction finishes, it returns a `200` status code if everything goes well, or a `4xx` code if the execution fails. However, with Raw functions, you can customize the response code, body, and headers. This way, the HTTP client making the request knows the precise result of the execution.

Use the following JSON structure to customize the response:

```
{
 "status_code": int,
 "headers": object, 
 "body": string, 
}
```

Where:

| Key          | Type   | Description                                                                                                                             |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| status\_code | int    | Status codes to indicate whether the UbiFunction request has been successfully completed.                                               |
| headers      | object | <p>Key-value object containing headers of the response.<br><br><strong>Default value</strong>: {"Content-Type": "application/json"}</p> |
| body         | string | Formatted based on the Content-Type header.                                                                                             |

For more information, see:

{% embed url="<https://help.ubidots.com/en/articles/4936503-ubifunctions-using-raw-functions>" %}


# CORS policy

Cross-Origin Resource Sharing (CORS) is an HTTP header-based mechanism that lets a server specify which origins can load its resources. An origin includes a domain, scheme, or port. For more information, see the [Mozilla CORS article](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).

Browser security prevents a web page from making requests to a different domain than the one that served it. This restriction also applies to UbiFunctions and helps prevent malicious sites from reading sensitive data. In some cases, you may want to allow other sites to send cross-origin requests to your UbiFunctions.

To enable a CORS policy for a UbiFunction and allow calls from web browsers or the HTML Canvas widget, follow these steps:

**Step 1**: In the Ubidots header, go to **Devices** and select **Functions**.

**Step 2**: Open an existing UbiFunction or create a new one.

**Step 3**: In the UbiFunction settings, enable **CORS policy**.

**Step 4**: Click **Save**.

<figure><img src="/files/tB7bUfUMD73t1fxru01Q" alt=""><figcaption></figcaption></figure>


# Async execution

You can invoke a function through its HTTPS endpoint without waiting for a response. To do this, add the `?_blocking=false` parameter to the URL:

{% tabs %}
{% tab title="Request URL" %}

```
https://parse.ubidots.com/prv/username/my-function?_blocking=false
```

{% endtab %}

{% tab title="Async Function Response" %}

```
HTTP/1.1 202 Accepted
Content-Type: application/json

{"activationId":"653ca08ce2104290bca08ce21002909c"}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
👍 **Particle Webhooks**

Use **async execution** when sending data to a UbiFunction from external platforms such as [Particle.io](https://particle.io/) or LoRaWAN network servers. This helps prevent failed or timed-out requests, which can affect server ratings.

For example, Particle skips webhook attempts after too many timeouts or 4xx/5xx HTTP status codes. That can lead to data loss in your IoT application. [Learn more](https://docs.particle.io/reference/device-cloud/webhooks/#limits) about Particle's API limits and policy.
{% endhint %}

### Reference

| URL Parameter  | Values                                                                                                                                                                                                                                                                                                                                                                           | Mandatory |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| **\_blocking** | <p><strong>Not specified,</strong> or "<strong>False":</strong><br></p><ul><li><strong>Not specified</strong>: By default, the function returns a response only after execution finishes.</li><li><strong>False</strong>: The function immediately returns a <code>202 Accepted</code> response with a JSON body that includes the function <code>activationId</code>.</li></ul> | No        |


# Examples

### Basic Examples

| Name                                                                                                                                                     | Description                                                                                              |
| -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [Ingest sensor data using HTTP GET requests](https://help.ubidots.com/en/articles/1539718-use-ubifunction-to-ingest-sensor-data-using-http-get-requests) | Send sensor data in the URL parameters of an HTTPS GET request, parse it, and send it to the Ubidots API |

### LoRaWAN Network Servers

| Name                                                                                                                                                                                                | Description                                                                                                                                             |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Integrate your The Things Industries data with Ubidots using UbiFunctions](https://help.ubidots.com/en/articles/3975015-integrate-your-the-things-industries-data-with-ubidots-using-ubifunctions) | Learn how to integrate data from The Things Industries with Ubidots using UbiFunctions                                                                  |
| [Integrate your TTN data with Ubidots – Manual UbiFunction Setup](https://help.ubidots.com/en/articles/1866089-integrate-your-ttn-data-with-ubidots-manual-ubifunction-setup)                       | Learn how to integrate your The Things Network (TTN) account data with Ubidots using UbiFunctions to transform the payload and launch your IoT solution |

### IoT Integrations

| Name                                                                                                                                                                                           | Description                                                                                              |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [UbiFunctions: Manage Downlink messages with Sigfox and Ubidots](https://help.ubidots.com/en/articles/2216553-ubifunctions-manage-downlink-messages-with-sigfox-and-ubidots)                   | Learn how to handle downlink messages from the Sigfox Backend with Ubidots                               |
| [UbiFunctions: Manage Uplink messages from the Sigfox Backend to Ubidots](https://help.ubidots.com/en/articles/2141802-ubifunctions-manage-uplink-messages-from-the-sigfox-backend-to-ubidots) | Learn how to integrate all the data received in your Sigfox Backend with Ubidots                         |
| [Connect a Sens'it to Ubidots using Sigfox over HTTP](https://help.ubidots.com/en/articles/2659361-connect-a-sens-it-to-ubidots-using-sigfox-over-http)                                        | Collect data using a Sens'it device version 2.1 and send it to your Ubidots account through UbiFunctions |
| [DIY Sigfox GPS asset tracking with Ubidots](https://help.ubidots.com/en/articles/1578342-diy-sigfox-gps-asset-tracking-with-ubidots)                                                          | Connect your Sigfox GPS asset tracking device to the Ubidots cloud                                       |

### Third-party integrations

| Name                                                                                                                                                                                                    | Description                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| [UbiFunctions: Process/Parse data from 3rd party platforms](https://help.ubidots.com/en/articles/1513776-ubifunctions-process-parse-data-from-3rd-party-platforms)                                      | Learn how to use UbiFunctions to create your own API and gain access to data, APIs, and tools using HTTPS.                                          |
| [Connect an AmbientWeather Professional Weather Station (WS-2902A) to Ubidots](https://help.ubidots.com/en/articles/2954794-connect-an-ambientweather-professional-weather-station-ws-2902a-to-ubidots) | Learn how to set up your AmbientWeather Weather Station with the Ubidots IoT Development Platform to start monitoring indoor and outdoor conditions |
| [UbiFunctions: Integrate Google's Geolocation API with Ubidots](https://help.ubidots.com/en/articles/2133222-ubifunctions-integrate-google-s-geolocation-api-with-ubidots)                              | Learn how to integrate the Geolocation API from Google with Ubidots using UbiFunctions                                                              |


# Specs and limits

Keep these specs and limits in mind when developing with UbiFunctions.

| Item                  | Description                                                                                                                                                                                                          | Value                                         |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| Supported libraries   | Libraries included in the Python and NodeJS runtimes for UbiFunctions.                                                                                                                                               | [Supported libraries](/ubifunctions/runtimes) |
| Code size             | The maximum code size for a UbiFunction.                                                                                                                                                                             | 48 MB                                         |
| Concurrent executions | The maximum number of activations for a single UbiFunction that can be running or queued at the same time. This limit is fixed, but it can be increased upon request.                                                | 1,000 concurrent executions                   |
| Logs                  | The maximum size of a UbiFunction log.                                                                                                                                                                               | 10 MB                                         |
| Memory                | The RAM available to the computing resources used by a UbiFunction. This limit can be increased upon request.                                                                                                        | 128 MB                                        |
| Rate limit            | The maximum number of activations that can be submitted per UbiFunction per minute. API calls that exceed this limit return HTTP `429 Too Many Requests`. This limit is fixed, but it can be increased upon request. | 1,800 activations per minute                  |
| Parameter size        | The maximum size of the body or payload attached to a request. This limit is fixed and cannot be changed.                                                                                                            | 1 MB                                          |
| Response size         | The maximum size of a UbiFunction response. This limit is fixed and cannot be changed.                                                                                                                               | 5 MB                                          |
| Timeout               | The maximum duration of a UbiFunction execution. This limit can be increased upon request, but it should never exceed 60,000 ms.                                                                                     | 40,000 ms                                     |
| Request headers       | The maximum size of a UbiFunction request headers.                                                                                                                                                                   | 8 KB                                          |
| Response headers      | The maximum size of a UbiFunction response headers.                                                                                                                                                                  | 4 KB                                          |


# Storage

<table data-header-hidden><thead><tr><th width="127"></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody><tr><td>Name<br></td><td>Exists before execution<br></td><td>Can be created during execution<br></td><td>Can be read during execution<br></td><td>Can be written during execution<br></td><td>Persists across executions<br></td><td>Use cases<br></td><td>Examples<br></td></tr><tr><td>Files in the `/tmp/` folder<br></td><td>NO<br></td><td>YES<br></td><td>YES<br></td><td>YES<br></td><td>NO<br></td><td>Intermediate file operations within a single execution — writing data to disk during processing before consuming or returning it, or using libraries that require a file path rather than an in-memory object.<br></td><td><br>Generating a temporary CSV from query results and reading it back; rendering a PDF or image with a library that writes to a file path; unzipping a payload before processing its contents.</td></tr><tr><td>Global properties<br></td><td>YES<br></td><td>NO<br></td><td>YES<br></td><td>NO<br></td><td>YES<br></td><td><br>Storing read-only configuration and sensitive values that must be available across multiple functions without hardcoding them in source code — credentials, constants, and lookup tables that change infrequently.</td><td><br>An API key or token shared by several functions (e.g., <code>aws_keys</code>, a Twilio auth token); a JSON lookup table mapping device model codes to human-readable names; a constant threshold value (e.g., max temperature alert level) reused across different processing functions.</td></tr><tr><td>Local files<br></td><td>YES<br></td><td>NO<br></td><td>YES<br></td><td>NO<br></td><td>YES<br></td><td>Bundling static reference data or configuration that is known at deploy time and needs to be read (but never written) during execution — decoder logic, device configuration, or parameter files that ship alongside the function code.<br></td><td><br>A <code>device_config.json</code> with per-device-type settings; a <code>calibration_table.csv</code> with sensor offsets; separate Python/JS modules (e.g., <code>decoder.py</code>, <code>utils.py</code>) organized in a <code>modules/</code> folder and imported by the main script.</td></tr><tr><td>External files<br></td><td>YES<br></td><td>YES<br></td><td>YES<br></td><td>YES<br></td><td>YES<br></td><td>Persisting files that must survive across executions and be created or updated at runtime — long-lived data that is too large or dynamic to fit in Global Properties, and that multiple functions or executions may need to read or write.<br></td><td><br>A running log file that each execution appends to; a trained ML model binary uploaded once and downloaded on each inference call; a shared reference dataset (e.g., a lookup CSV) updated periodically by one function and consumed by others.</td></tr></tbody></table>


# File storage API

The Ubidots File Storage API enables account owners to securely upload, retrieve, manage, and delete files within UbiFunctions.

## Overview

The **Ubidots File Storage API** provides a secure and efficient way to store and manage files in your Ubidots account. This service is available **from the Industrial license onward** and supports file upload, retrieval, and management workflows.

## Storage & Security Constraints

To ensure secure and reliable usage, the following limits apply:

**Storage Limit:**\
Each Ubidots account includes **50 MB** of storage.

**Restricted File Extensions:**\
To enhance security, potentially malicious file types are blocked. The following extensions **are not allowed**:

`.js`, `.exe`, `.sh`, `.php`, `.bat`, `.cmd`, `.msi`, `.vbs`, and other executable/script files.

#### Request Rate Limit

The API enforces a maximum of 5 requests per second per client IP. Requests beyond this limit return a `429 Too Many Requests` response.

**Filename Rules:**\
Only the following characters are allowed in filenames:

* **Letters (a-z, A-Z)**
* **Numbers (0-9)**
* **Underscores (\_), dashes (-), and dots (.)**

**Token Authentication:**\
All API requests **must include** an `X-Auth-Token` header. Only **account-level tokens** are accepted. Organization and device tokens are rejected.

## Supported API Operations

#### 1. Uploading a file

Upload a file to your Ubidots storage.

**API Endpoint:**

```
POST https://files.api.ubidots.com/files/{username}/{filename}
```

**Example Request (cURL)**

```bash
curl -X POST "https://files.api.ubidots.com/files/yourusername/file.txt" \
     -H "X-Auth-Token: YOUR_AUTH_TOKEN" \
     -F "file=@path/to/your/file.txt"
```

**Example Response**

```json
{
    "message": "File uploaded successfully",
    "filename": "file.txt",
    "username": "yourusername",
    "used_storage_mb": "10.5"
}
```

***

#### 2. Downloading a file

Retrieve a file from your Ubidots storage.

**API Endpoint:**

```
GET https://files.api.ubidots.com/files/{username}/{filename}
```

**Example Request (cURL)**

```bash
curl -X GET "https://files.api.ubidots.com/files/yourusername/testfile1.txt" \
     -H "X-Auth-Token: YOUR_AUTH_TOKEN" \
     -o fileDownloaded.txt
```

**Response:**

✅ **If successful (200 OK):** The file is downloaded.\
❌ **If the file does not exist (404 Not Found):** The request returns a `404 Not Found` response.

***

#### 3. Deleting a file

Delete a file from your storage.

**API Endpoint:**

```
DELETE https://files.api.ubidots.com/files/{username}/{filename}
```

**Example Request (cURL)**

```bash
curl -X DELETE "https://files.api.ubidots.com/files/yourusername/test1.txt" \
     -H "X-Auth-Token: YOUR_AUTH_TOKEN"
```

**Example Response**

```json
{
    "message": "File 'test1.txt' deleted successfully"
}
```

***

#### 4. Listing all files

Retrieve a list of all stored files and current storage usage.

**API Endpoint:**

```
GET https://files.api.ubidots.com/files/{username}
```

**Example Request (cURL)**

```bash
curl -X GET "https://files.api.ubidots.com/files/yourusername" \
     -H "X-Auth-Token: YOUR_AUTH_TOKEN"
```

**Example Response**

```json
{
    "username": "yourusername",
    "total_files": 2,
    "used_storage_mb": 18.42,
    "remaining_storage_mb": 31.58,
    "files": [
        {"filename": "test1.txt", "size_mb": 8.42},
        {"filename": "backup.zip", "size_mb": 10.0}
    ]
}
```

## API Response Codes

Each API operation returns specific HTTP status codes based on the request result:

| **Status Code**                | **Description**                                                                                  |
| ------------------------------ | ------------------------------------------------------------------------------------------------ |
| **200 OK**                     | Request was successful (file uploaded, downloaded, listed, or deleted).                          |
| **400 Bad Request**            | The request contained invalid parameters, such as an incorrect filename.                         |
| **401 Unauthorized**           | The provided `X-Auth-Token` is missing or invalid.                                               |
| **403 Forbidden**              | The user is not authorized to access this resource (wrong username or insufficient permissions). |
| **404 Not Found**              | The requested file does not exist in the storage.                                                |
| **413 Payload Too Large**      | The file exceeds the maximum storage limit of 50MB.                                              |
| **415 Unsupported Media Type** | The file has a restricted extension and cannot be uploaded.                                      |
| **429 Too Many Requests**      | Exceeded the limit of 5 requests per second from the same client IP or token.                    |
| **500 Internal Server Error**  | An unexpected error occurred on the server (e.g., S3 failure or token validation issue).         |
| **503 Service Unavailable**    | The authentication service or storage backend is temporarily unreachable.                        |


# Multiple files

## Overview

Supporting multiple files in UbiFunctions improves flexibility and scalability. When building serverless functions, splitting code into multiple files improves maintainability, enables reuse, and aligns with common software development practices.

UbiFunctions allows you to use multiple files such as:

* **Source files:** These files contain code that can be executed.
* **Config files:** These are typically `.json`, `.conf`, `.toml`, or similar files. They do not contain executable code. Instead, they provide the data and parameters your logic needs to run with a particular behavior.
* **Data files:** These are files that store information in various formats, such as `.csv`, `.txt`, or `.json`. They can contain structured, semi-structured, or unstructured data used as input for processing or analysis.

## Creating a file

To create a file in your UbiFunction root directory, click the **+** icon in the files pane, then select **New file**. Enter a name. The new file is added to your UbiFunction:

| <img src="/files/mStoTfiqqmAKNQjHHHZv" alt="" data-size="original"> | <img src="/files/oXYmbuOZJSsxatfs8aLm" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |

## Creating a folder

Folders help you organize modules more effectively. To create a folder, click the **+** icon in the files pane, then select **New folder**. Enter a name. The new folder is added to your UbiFunction:

| <img src="/files/9yUB4Nm8UMTlDCaCduoY" alt="" data-size="original"> | <img src="/files/zNvKeNITaIeWqupw1nSf" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |

### Creating files or folders within a folder

To create a file or folder inside an existing folder, hover over the folder, click the three-dot menu, and select **New file** or **New folder**.

| <img src="/files/gHttSogT3UrEtQddgdi7" alt="" data-size="original"> | <img src="/files/ZUMsk11TzAyPFVikTtiW" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |

## File structure and imports

When you add files or folders to a UbiFunction, they behave like a directory on your local machine, with the root directory as the working directory. For example, if you add `decoder.py|js` alongside the main script, you can import it like this:

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

```javascript
const decoder = require('./decoder');
```

{% endtab %}

{% tab title="Python" %}

```python
import decoder
```

{% endtab %}
{% endtabs %}

Similarly, if you create a folder `utils` and add a file `parser.py` or `parser.js` inside it, the structure behaves like a subdirectory on your computer. To import from it, use:

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

```javascript
const parser = require('./utils/parser');
```

{% endtab %}

{% tab title="Python" %}

```python
from utils import parser
```

{% endtab %}
{% endtabs %}

This means you can treat the UbiFunction file structure like a local project folder.

{% hint style="info" %}
UbiFunctions use a protected file system. You can create files and folders through the UI, but scripts run in a strictly read-only environment and cannot write files or create new files and folders.
{% endhint %}

## Data and config files

UbiFunctions let you include data and configuration files, such as `.json`, `.conf`, `.txt`, and `.csv`, to provide inputs or settings for your functions. To read a file, handle it the same way you would in a local script:

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

```javascript
const fs = require('fs');

// Read a JSON file
// Adjust the file's path accordingly
const settings = JSON.parse(fs.readFileSync('settings.json', 'utf-8'));
```

{% endtab %}

{% tab title="Python" %}

```python
import json

# Read a JSON file
# Adjust the file's path accordingly
with open('settings.json', 'r') as file:
    settings = json.load(file)
print(settings)
```

{% endtab %}
{% endtabs %}

## Example

Suppose you have several device types on an LNS sending data continuously. For each uplink, you need to decode the payload and forward it to the corresponding device in Ubidots.

You can solve this with a UbiFunction that uses separate scripts for each device type and calls them from the main function.

For this example, create:

* A folder called *modules*. Inside it, create:
  * A Python script called *s2100.py*. This is the decoder for the s2100 device.
  * A Python script called *s210x.py*. This is the decoder for the s210x device.
  * A Python script called *utils.py*. This file contains shared utility functions.
* A configuration file called *device\_config.json* that stores the configuration for each device type.

It should look like this:\ <img src="/files/bYip2uwDXejqAailW8Gs" alt="" data-size="original">

The following are the contents of each script:

**s2100.py script**

```python
def s2100_decode(data: int) -> dict:
    """Decode data for s2100 device."""
    temperature = (data & 0x3) >> 0
    humidity = (data & (0x3 << 2)) >> 2
    battery = (data & (0x3 << 4)) >> 4

    return {
        "temperature": temperature,
        "humidity": humidity,
        "battery": battery
    }
```

#### s210x.py script <a href="#h_bc270579ec" id="h_bc270579ec"></a>

```python
def s210x_decode(data: int) -> dict:
    """Decode data for s210x device."""
    soil_electrical_conductivity = (data & 0x3) >> 0
    battery = (data & (0x3 << 2)) >> 2

    return {
        "soil_electrical_conductivity": soil_electrical_conductivity,
        "battery": battery
    }
```

#### utils.py script <a href="#h_e0291278e3" id="h_e0291278e3"></a>

```python
def parse_data(data_str: str) -> int:
    """Convert a binary string to an integer."""
    return int(data_str, 2)

```

\
**device\_config.json**

```python
{
    "s2100" : {"color" : "#fefefe"},
    "s210x" : {"color" : "#ffffff"}
}
```

#### main.py <a href="#h_b306192085" id="h_b306192085"></a>

```python
import requests
import time
import json
import os
from modules.utils import parse_data
from modules.s2100 import s2100_decode
from modules.s210x import s210x_decode

decoders = {
    "s210x" : s2100_decode,
    "s2100" : s210x_decode
}

def main(args):

    """Decode payload based on device type."""
    device_type = args.get("device")
    data_str = args.get("data")
    
    if not device_type or device_type not in decoders:
        print(f"Device type: {device_type} not supported...")
        return {"status" : "error"}

    # Convert binary string to integer
    data = parse_data(data_str)
    decoded_data = decoders[device_type](data)

    # Open and read the JSON file
    with open("device_config.json", 'r') as file:
        config = json.load(file)

    print(config[device_type])
    print(decoded_data)
    return {"status" : "success" , "data" : decoded_data}
```

If you execute this UbiFunction with the following test payload:

```python
{"device": "s2100", "data": "0b01011010"}
```

It should output something like this:

```python
{'color': '#fefefe'}
{'soil_electrical_conductivity': 2, 'battery': 2}
```


# What is a plugin?

Plugins are a tool that allows users to develop and extend the platform's features and functionality to meet particular needs not addressed natively by Ubidots.

## Context

Ubidots is not an open-source platform, so users can't modify it to meet their particular requirements. Even if it were, users would need extensive knowledge of the several technologies that make up the platform in order to modify it.

## Motivation

We wanted to empower our users with a tool that allows them to develop features based on their needs and integrate them into their accounts.

## When to use plugins?

When a certain need or requirement is not offered natively by Ubidots.

## Plugin types

Plugins are categorized into *types* according to their purpose, thus allowing different levels of platform customization.

In general terms, they embed specific logic according to their type:

<table data-full-width="true"><thead><tr><th width="201.2" align="center">Type</th><th align="center">Purpose</th><th align="center">Example</th></tr></thead><tbody><tr><td align="center">Cron</td><td align="center">Periodically executes some logic.</td><td align="center">Retrieve data every 20 minutes from an external weather API and bring it into Ubidots.</td></tr><tr><td align="center">Webhook</td><td align="center">Executes some logic triggered by an HTTP request.</td><td align="center">Decode a LoRaWAN payload every time an uplink is received on Ubidots from an LNS.</td></tr><tr><td align="center">Widget</td><td align="center">Allows developing custom widgets that are made available as if they were native to Ubidots.</td><td align="center">Create a line chart using HighchartsJS that meets specific visualization requirements beyond the customization options offered by Ubidots' Line Chart widget.</td></tr><tr><td align="center">Device</td><td align="center">Allows creating custom Ubidots devices with embedded logic and user-defined filters and categories for visualization purposes.</td><td align="center">Create a device with a custom thumbnail and properties.</td></tr></tbody></table>


# Public vs. private

## Visibility

In addition to type, plugins are also categorized by visibility.

* Plugins developed by Ubidots are public.
* Plugins developed by Ubidots **users** are private.

## Public

Public plugins are available to every Ubidots account. This gives all users access to these standardized tools.

{% hint style="info" %}
Some public plugins are only available on certain Ubidots plans.
{% endhint %}

{% content-ref url="/pages/2gK6ne3iEfiBzzxToLL9" %}
[Public plugins](/plugins/public-plugins)
{% endcontent-ref %}

## Private

Private plugins are only available in the account where the user created them.

{% hint style="info" %}
Developing your own plugins requires an Enterprise license. Contact us at <sales@ubidots.com>.
{% endhint %}

{% content-ref url="/pages/y6JQhF1KBcDzwb9Vtae2" %}
[Private plugins](/plugins/private-plugins)
{% endcontent-ref %}


# Public plugins

Public plugins are designed for broader use cases that serve a large audience rather than niche needs.

For example, Ubidots provides a plugin that fetches weather data from the OpenWeather API and displays it as a device.

{% content-ref url="/pages/ifeimL8m7FxzKlMGAD4T" %}
[Cron](/plugins/public-plugins/cron)
{% endcontent-ref %}

{% content-ref url="/pages/EyqKMA52LBGjltHNcs5N" %}
[Webhook](/plugins/public-plugins/webhook)
{% endcontent-ref %}


# Cron

## Definition

A public Cron plugin is Ubidots-defined logic that runs periodically at intervals set by the user.

## How to use Cron plugins

Go to Ubidots → Devices → Plugins. Select the Cron plugin you want to create an instance of and configure its parameters, including the execution period.

After you create the plugin, its logic runs at the defined frequency.

The following screenshot shows the creation modal for a Cron plugin. The red rectangle highlights the *run every* setting, which defines how often the logic runs:

<figure><img src="/files/AEkoGwEoQW1lo0M5b0dx" alt="" width="563"><figcaption></figcaption></figure>

## Available public Cron plugins

<table><thead><tr><th width="349" align="center">Name</th><th align="center">Plan availability</th></tr></thead><tbody><tr><td align="center"><a href="https://help.ubidots.com/en/articles/5389848-plugins-backup-your-ubidots-data-with-aws-s3">AWS S3 Backup</a></td><td align="center">Enterprise</td></tr><tr><td align="center"><a href="http://help.ubidots.com/en/articles/8751084-plugins-anomaly-detector">Anomaly Detector</a></td><td align="center">Industrial and above</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/4307735-plugins-connect-openweather-with-ubidots">Open Weather</a></td><td align="center">Professional and above</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/4309512-plugins-connect-exchange-rates-apis-with-ubidots">Daily Exchange Rates</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="http://help.ubidots.com/en/articles/6989040-plugin-connect-ambient-weather-stations-and-sensors-to-ubidots">Ambient Weather</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/4824641-plugins-monitor-your-tts-lorawan-gateways-with-ubidots">The Things Stack (Gateways)</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/4309457-plugins-connect-usgs-waterwatch-with-ubidots">USGS WaterWatch</a></td><td align="center">All plans</td></tr></tbody></table>


# Webhook

## Definition

A public Webhook plugin is Ubidots-defined logic that runs when you send an HTTP request to its *HTTPS endpoint URL*.

## How to use Webhook plugins

Go to Ubidots → Devices → Plugins. Select the Webhook plugin you want to create and configure its input parameters.

After you create the plugin, send an HTTP request to its *HTTPS endpoint URL* to trigger it.

<figure><img src="/files/1PdVIJ15MVK2RJSKFNYU" alt=""><figcaption></figcaption></figure>

## Available public webhook plugins

<table><thead><tr><th width="171" align="center">Name</th><th align="center">Plan availability</th></tr></thead><tbody><tr><td align="center"><a href="https://help.ubidots.com/en/articles/5105507-plugins-connect-aws-iot-core-to-ubidots-using-https">AWS IoT Core</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/6289406-plugins-connect-eversensors-with-ubidots">Everactive</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/6022705-plugins-integrate-golioth-output-streams-with-ubidots">Golioth</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/5535599-plugins-connect-monnit-s-alta-devices-with-ubidots">Monnit ALTA</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/6362890-plugin-perform-vibration-analysis-using-ncd-sensors-and-fft-conversion-in-ubidots">NCD FFT</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/6383707-plugins-connect-senquip-devices-to-ubidots">Senquip</a></td><td align="center">All plans</td></tr><tr><td align="center">SenseCAP + TTS</td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/5528026-plugins-connect-sigfox-with-ubidots">Sigfox</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/5901749-plugins-connect-swarm-hive-with-ubidots">Swarm Hive</a></td><td align="center">All plans</td></tr><tr><td align="center"><a href="https://help.ubidots.com/en/articles/5096476-plugins-connect-the-things-stack-to-ubidots">The Things Stack</a></td><td align="center">All plans</td></tr></tbody></table>


# Widget

## Definition

A public widget plugin is similar to a native Ubidots widget, but it is tailored to specific needs. Examples include generating EDA (exploratory data analysis) reports and plotting specialized charts such as Pareto charts.

## How to use Widget plugins

Before you use a widget plugin in a dashboard, install it first. To do so, go to Ubidots → Dev Center → Plugins and create the required plugin. For example, if you want to use the **Pareto Bars** widget, you must first install that plugin.

| <img src="/files/9koEOtXCbzxSTzXelVDZ" alt="" data-size="original"> | <img src="/files/kbuRcEuyYnjv0bSQw3jh" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |

Once the plugin is created, go to a Ubidots dashboard and use the widget:

<figure><img src="/files/Ikw3jgo7kO5FQS9SPqyX" alt="" width="563"><figcaption></figcaption></figure>

### Available public widget plugins <a href="#available-public-widget-plugins" id="available-public-widget-plugins"></a>

| Name                                                                                            | Plan availability    |
| ----------------------------------------------------------------------------------------------- | -------------------- |
| [Bars pareto chart](https://help.ubidots.com/en/articles/9626833-plugins-bars-pareto-chart)     | Industrial and above |
| [Breakdown bar chart](https://help.ubidots.com/en/articles/9216260-plugins-breakdown-bar-chart) | Industrial and above |
| [Data profiling](https://help.ubidots.com/en/articles/8991281-plugins-data-profiling)           | Industrial and above |
| [Timeline chart](https://help.ubidots.com/en/articles/9642326-plugins-timeline-chart)           | Industrial and above |

<br>


# Private plugins

{% hint style="info" %}
This feature requires an Enterprise license. Please contact <sales@ubidots.com> to enable the *developer console* on your account.
{% endhint %}

Private plugins, unlike their public counterparts, are developed, maintained, and deployed by Ubidots **users** to meet requirements that the platform does not address natively.

A private plugin is structured around a specific set of files. These files not only determine the plugin type but also define its logic and overall functionality. The typical file structure for a plugin, regardless of its type, is outlined below:

```
├── src
│   ├── some_files_or_folders    
├── LICENSE
├── README.md
├── manifest.toml 
```

{% hint style="warning" %}
Following this file structure and including all the listed files is required for the plugin to deploy and work correctly.
{% endhint %}

## src directory

The `src` directory holds the primary components of the plugin. This is where the core logic, main functionality, and some configuration files are defined.

For more details about the contents of this directory, refer to the dedicated section for each plugin type:

{% content-ref url="/pages/7MziWvY1O31Eqt6xd5nm" %}
[Cron](/plugins/private-plugins/cron)
{% endcontent-ref %}

{% content-ref url="/pages/j5SPHRpQAJafR32D0plY" %}
[Webhook](/plugins/private-plugins/webhook)
{% endcontent-ref %}

{% content-ref url="/pages/LQSC6UFPk5LHXh6v4LlP" %}
[Widget](/plugins/private-plugins/widget)
{% endcontent-ref %}

{% content-ref url="/pages/2ba8br8YpAqOor51WY1E" %}
[Device](/plugins/private-plugins/device)
{% endcontent-ref %}

## manifest.toml

In broad terms, the manifest file defines the plugin's identity and behavior. It outlines:

* The plugin type
* The current plugin version
* The plugin price
* Environment variables

This file contains both shared components that apply to all plugin types and unique components that are specific to each plugin type.

The typical manifest file common to all plugins is composed of:

<table data-full-width="true"><thead><tr><th width="168.2" align="center">Section</th><th width="178" align="center">Key</th><th width="202" align="center">Value</th><th width="199" align="center">Description</th><th align="center">Example</th></tr></thead><tbody><tr><td align="center">[-]</td><td align="center">manifest_version</td><td align="center">A valid manifest version. Currently 2.0 is the only one supported.</td><td align="center">Manifest version.</td><td align="center"><pre><code>manifest_version = 2.0
</code></pre></td></tr><tr><td align="center">[settings]</td><td align="center">version</td><td align="center">Any number, according to your versioning system.</td><td align="center">Plugin's current version.</td><td align="center"><pre><code>version = "1.1.1"
</code></pre></td></tr><tr><td align="center">[settings]</td><td align="center">plugin_type</td><td align="center">cron, webhook, device, widget.</td><td align="center">Type of plugin</td><td align="center"><pre><code>plugin_type = "widget"
</code></pre></td></tr><tr><td align="center">[settings]</td><td align="center">license_name</td><td align="center">A valid license name.</td><td align="center">The name for your plugin's license.</td><td align="center"><pre><code>license_name = "MIT license"
</code></pre></td></tr><tr><td align="center">[pricing]</td><td align="center">price*</td><td align="center">A float number.</td><td align="center">This is the price that the developer wants to set for the plugin.</td><td align="center"><pre><code>price = 10
</code></pre></td></tr><tr><td align="center">[environment]</td><td align="center">Any variable name*</td><td align="center">Any value for the variable.</td><td align="center">Allows to set environment variables within the plugin.</td><td align="center"><code>UBIDOTS_URL = "https://industrial.api.ubidots.com"</code></td></tr></tbody></table>

With that in mind, a manifest file that is common to all plugins, regardless of their type, looks like this:

```
manifest_version = 2.0

[settings]
version = "1.1.1"
plugin_type = "cron" | "webhook" | "device" | "widget"
license_name = "MIT license"

[pricing]
price = 10

[environment]
UBIDOTS_URL = "https://industrial.api.ubidots.com"
```

For detailed information about the specific manifest file components particular to each plugin type, refer to their dedicated pages.

{% hint style="info" %}
Manifest file keys marked with a \* are optional.
{% endhint %}

## Runtimes

### Python

The plugin's Python runtime is based on Python 3.11. Use `python3.11` in the `manifest.toml` file. It includes the following libraries:

<table><thead><tr><th width="432" align="center">Library</th><th align="center">Version</th></tr></thead><tbody><tr><td align="center">requests</td><td align="center">2.32.3</td></tr><tr><td align="center">pytz</td><td align="center">2024.1</td></tr><tr><td align="center">msgpack</td><td align="center">1.0.8</td></tr><tr><td align="center">httpx</td><td align="center">0.27.0</td></tr><tr><td align="center">aiohttp</td><td align="center">3.9.5</td></tr><tr><td align="center">lxml</td><td align="center">5.2.2</td></tr><tr><td align="center">numpy</td><td align="center">2.0.0</td></tr><tr><td align="center">pandas</td><td align="center">2.2.2</td></tr><tr><td align="center">python-aqi</td><td align="center">0.6.1</td></tr></tbody></table>

### NodeJS <a href="#h_a6f43dab0f" id="h_a6f43dab0f"></a>

The plugin's NodeJS runtime is based on NodeJS 20. Use `nodejs:20` in the `manifest.toml` file. It includes the following libraries:

<table><thead><tr><th width="176" align="center">Library</th><th align="center">Version</th></tr></thead><tbody><tr><td align="center">axios</td><td align="center">1.5.1</td></tr></tbody></table>

## License

The `LICENSE` file provides information about the licensing terms under which the plugin is distributed.

Typically, a license file looks like this:

```

Copyright (c) 2021 Ubidots

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
```

{% hint style="warning" %}
This file can't be empty.
{% endhint %}

## README

This file is as important as the `src` directory because it guides plugin users. This file must be written in Markdown.

{% hint style="warning" %}
This file can't be empty.
{% endhint %}


# Cron

## Definition

A private cron plugin contains user-defined logic that runs periodically, based on a frequency set by the user.

## Required files

As stated before, any plugin type, and particularly a private cron plugin, follows this file structure:

```
├── src
│   ├── control     
│   ├── function
│   ├── view.xml
├── LICENSE
├── README.md 
├── manifest.toml 
```

## control

This is a script written in either Python or NodeJS. Think of it as a setup script, since it runs only once when the plugin is created.

## function

This is a script written in either Python or NodeJS. It runs according to the defined frequency.

## view\.xml

This file lets the developer build and display a form where users can enter the parameters required by the plugin.

The form is displayed when the cron plugin is being created.

For example, consider a plugin that requires the following information from the user:

* The geographical location of a device.
* The execution period of the plugin.
* An Ubidots token.

The form could look like this:

<figure><img src="/files/pLxlgGqHXVDhjnkgXjjy" alt="" width="563"><figcaption></figcaption></figure>

The developer can create a form that displays those fields by using `view.xml`, as shown below:

```xml
<form name="formName">

    <group name="locacionGroup">
        <group name="location">
            <label name="labelLocation">Click on the map to specify a location</label>
            <map name="map" />
        </group>
    </group>

    <group name="unitsGroup">
        <group name="unitsSelect">
            <label name="labelUnits">Units format</label>
            <select
              name="units"
              placeholder="Units format"
              value="metric"
              type="single"
            >
                    <option value="metric">Metric</option>
                    <option value="imperial">Imperial</option>
            </select>
        </group>
    </group>

    <group name="freq">        
        <group name="freqInfo">
            <frequency label="Run every {field} minutes"/>
        </group>
        <label name="labelUnits" help="Please note data is updated every 10 to 20 minutes"></label>
    </group>

    <group name="ubidotsInformation">
        <group name="fieldToken">
            <label name="labelToken">Ubidots Token</label>
            <token name="token" placeholder="Select Token"/>
        </group>
    </group>
</form>
```

All of these form parameters are accessible to the `function` and `control` scripts on each execution through the `_parameters` key in the incoming JSON object.

{% hint style="info" %}
For detailed information about the supported elements in the `view.xml` file, refer to its dedicated section in the [Plugins development](https://dev.ubidots.com/plugins/plugins-development/view.xml) page.
{% endhint %}

## LICENSE and README.md

Refer to the Private Plugins page for more information:

{% content-ref url="/pages/y6JQhF1KBcDzwb9Vtae2" %}
[Private plugins](/plugins/private-plugins)
{% endcontent-ref %}

## Manifest.toml

Aside from the manifest components common to all plugin types, the following are specific to cron plugins:

<table data-full-width="true"><thead><tr><th align="center">Section</th><th align="center">Key</th><th align="center">Value</th><th align="center">Description</th><th align="center">Example</th></tr></thead><tbody><tr><td align="center">[settings]</td><td align="center">runtime</td><td align="center">Any Python or NodeJS valid version.</td><td align="center">This is the environment in which the plugin will run.</td><td align="center"><code>runtime = "python:3.7"</code></td></tr><tr><td align="center">[settings.cron]</td><td align="center">interval</td><td align="center">Any integer number, T, in the range:<br>1 &#x3C; T &#x3C; y. This value is in minutes.</td><td align="center">Default frequency at which the plugin will be executed.</td><td align="center"><code>interval = 1</code></td></tr><tr><td align="center">[settings.cron]</td><td align="center">run_on_deploy*</td><td align="center"><em>true</em> or <em>false</em></td><td align="center">Determines if the plugin will be executed immediately upon creation</td><td align="center"><code>run_on_deploy = false</code></td></tr></tbody></table>

With that in mind, a typical `manifest.toml` file for a private cron plugin looks like this:

```
manifest_version = 2.0
 
[settings] 
version = "1.0.56" 
runtime = "nodejs:10" | "python:3.7"
plugin_type = "cron" 
license_name = "MIT license" 

[settings.cron] 
interval = 1 
run_on_deploy = false

[pricing]
price = 0 
```

{% hint style="info" %}
Manifest file keys marked with a \* are optional and can be omitted.
{% endhint %}

## Naming convention

You must use the names of all files and directories listed in the Required files section, since the plugins engine specifically looks for these files and their extensions.

For instance:

* If your plugin uses Python as its runtime, the script names should be `control.py` and `function.py`
* If your plugin uses NodeJS as its runtime, the script names should be `control.js` and `function.js`

{% hint style="info" %}
The language selected for the `control` and `function` scripts must match the value of the *`runtime`* key in the `manifest.toml` file.
{% endhint %}


# Webhook

## Definition

A private webhook plugin contains user-defined logic that is triggered by an HTTP request to its *HTTPS Endpoint URL*:

<figure><img src="/files/mxWdTkZoj7qxuWwQkveI" alt=""><figcaption></figcaption></figure>

## Required files

As stated before, any private plugin, regardless of its type, and particularly a private webhook plugin, follows the file structure shown below:

```
├── src
│   ├── control     
│   ├── function
│   ├── user_code
│   ├── view.xml
├── LICENSE
├── README.md
├── manifest.toml
```

## control

This is a script written in either Python or NodeJS. Think of it as a setup script, since it runs only once when the plugin is created.

## function

This is a script written in either Python or NodeJS. It contains the plugin's core logic and runs every time an HTTP request is made to the plugin's *HTTPS Endpoint URL*.

## user\_code

This is written either in Python or NodeJS.

If needed, the plugin developer can let users execute custom logic. This script gives users an additional level of customization.

For instance, if a developer creates a webhook plugin to fetch data from an external API, they might want to let users further process or redirect that data without altering the core logic.

The `user_code` script provides that flexibility. It preserves the plugin's encapsulation while offering a dedicated space for user-specific logic.

## Naming convention

You must use the names of all files and directories listed in the "Required files" section, since the plugins engine specifically looks for these files and their extensions.

For instance:

* If your plugin uses Python as its runtime, the script names should be `control.py`, `function.py`, and `user_code.py`
* If your plugin uses NodeJS as its runtime, the script names should be `control.js`, `function.js`, and `user_code.js`

{% hint style="info" %}
The language selected for the `control`, `function`, and `user_code` scripts must match the value of the *`runtime`* key in the `manifest.toml` file.
{% endhint %}

## view\.xml

This file lets the developer build and display a form that users can use to enter the parameters required by the plugin.

The contents of this form are displayed in the plugin creation modal.

For example, if we create a plugin to receive data from an LNS during device uplinks, we would need the user's LNS and Ubidots credentials. A GUI for this plugin could look like this:

<figure><img src="/files/o2FWYRUxhEkEVUD3L04g" alt="" width="563"><figcaption></figcaption></figure>

This can be achieved using text fields in the form as shown below:

```xml
<form name="formName">

    <group name="ttitenantInformation">
        <group name="ttinetworkTenant">
            <label name="tti-tenant-label" help="If you have a TTI Dedicated Cloud license, you can find the Tenant ID as the first portion of your account access URL. [https://www.thethingsindustries.com/docs/getting-started/cloud-hosted/addresses/](Learn more)">TTI Tenant ID</label>
            <input name="tti-tenant" type="text" value="tti"/>
        </group>
    </group>
    
     <group name="group2">
        <group name="ttiapiInformation">
            <label name="tti-api-label" help="A TTS API key with enough permissions to view and list gateways. [https://help.ubidots.com/en/articles/4824641-plugins-monitor-your-tts-lorawan-gateways-with-ubidots](Learn more).">TTI API Key</label>
            <input name="tti-api-key" type="password"/>
        </group>
    </group>

    <group name="ubidotsInformation">
        <group name="fieldToken">
            <label name="labelToken">Ubidots Token</label>
            <token name="token" placeholder="Select Token"/>
        </group>
    </group>

</form>
```

All of these form parameters are accessible to the `function`, `control`, and `user_code` scripts on each execution through the `_parameters` key in the incoming JSON object.

{% hint style="info" %}
For detailed information about the supported elements in the `view.xml` file, refer to its dedicated section in the [Plugins development](https://dev.ubidots.com/plugins/plugins-development/view.xml) page.
{% endhint %}

## LICENSE and README.md files

Refer to the Private Plugins page for more information:

{% content-ref url="/pages/y6JQhF1KBcDzwb9Vtae2" %}
[Private plugins](/plugins/private-plugins)
{% endcontent-ref %}

## Manifest.toml

Aside from the manifest components common to all plugin types, the following are specific to webhook plugins:

<table data-full-width="true"><thead><tr><th width="197" align="center">Section</th><th width="210.2" align="center">Key</th><th width="194" align="center">Value</th><th width="205" align="center">Description</th><th align="center">Example</th></tr></thead><tbody><tr><td align="center">[settings]</td><td align="center">runtime</td><td align="center">Any valid Python or NodeJS version.</td><td align="center">Environment in which the plugin will run.</td><td align="center"><code>runtime = "python:3.7"</code></td></tr><tr><td align="center">[settings.webhook]</td><td align="center">default_http_method</td><td align="center">A valid HTTP method.</td><td align="center">Default HTTP method to be used by the plugin.</td><td align="center"><code>default_http_method = "POST"</code></td></tr><tr><td align="center">[settings.webhook]</td><td align="center">allowed_http_methods</td><td align="center">A list of valid HTTP methods.</td><td align="center">HTTP methods allowed by the plugin.</td><td align="center"><code>allowed_http_methods = ["POST"]</code></td></tr></tbody></table>

With that in mind, a typical `manifest.toml` file for a private webhook plugin looks like this:

```
manifest_version = 2.0
 
[settings] 
version = "1.0.56" 
runtime = "python:3.7" 
plugin_type = "webhook" 
license_name = "MIT license" 

[settings.webhook]
default_http_method = "POST"
allowed_http_methods =  ["POST"]
```

{% hint style="info" %}
All of these manifest keys are mandatory.
{% endhint %}


# Widget

## Definition

A private widget plugin is a custom-developed widget that is available as if it were native to Ubidots. Like any other widget, it provides both execution logic and a graphical interface for user interaction.

Its visual rendering and logic execution happen when the dashboard loads. They continue only while the dashboard that contains it remains open.

For example, consider the following widget that uses the JavaScript Highcharts library for data plotting:

<figure><img src="/files/wRKmclObhv8rC33sygRG" alt=""><figcaption></figcaption></figure>

When a user opens the dashboard that contains this widget, the browser renders its visuals, and the logic that fetches and plots the data runs during load.

When the user leaves the dashboard, all tasks within the widget logic stop.

## Required files

As stated before, any private plugin, regardless of its type, and especially a private widget plugin, follows the file structure shown below:

```
├── src
│   ├── static                    
│   │   ├── widget.css      
│   │   ├── widget.html 
│   │   ├── widget.js             
│   ├── view.xml
│   ├── view_widget.xml
│   ├── control.py | control.js
│   ├── function.py | function.js
│   ├── user_code.py | user_code.js      
├── LICENSE
├── README.md
├── manifest.toml
```

## src and static directory

Unlike Cron and Webhook plugins, a Widget plugin includes an additional directory called `static`. This directory contains the widget's HTML, CSS, and JavaScript files.

{% hint style="warning" %}
Placing the CSS, HTML, and JS files in the root of *src*, instead of inside *static*, causes deployment errors.
{% endhint %}

## widget.css

Written in CSS, this file styles the widget's visual components.

## widget.html

Written in HTML, this file defines the layout of the widget's visual elements.

## widget.js

Written in JavaScript, this file handles the plugin's core logic and can also modify the CSS styles and HTML dynamically.

## Naming convention

You must keep the names of all files and directories listed in the "Required files" section, since the plugins engine specifically looks for these files and their extensions.

{% hint style="warning" %}
If you change the name of a file or directory, the plugin cannot be deployed.
{% endhint %}

## view\.xml

Although this file is only relevant to Cron and Webhook plugins, it must still contain valid placeholder data for the plugin to deploy.

## view\_widget.xml

The form that displays the widget's required parameters is created with `view_widget.xml`, **not** `view.xml`.

For example, suppose you are creating a widget that uses Highcharts to plot data. In that case, the widget requires the following information from the user:

* Variable label
* Title
* Subtitle
* X axis label
* Y axis label
* Series name

A form that contains those fields might look like this:

<figure><img src="/files/5pgVxrsrhwsLjs5uJsW5" alt="" width="563"><figcaption></figcaption></figure>

The form above can be built with `view_widget.xml` as follows:

```xml
<form>
    <inputcombo
        id="variable_label"
        type="text"
        label="Variable label"
        description="Device's variable label to use for the data"
        placeholder=""
        value=""
    />
    <inputcombo
        id="title"
        type="text"
        label="Chart's title"
        description="Chart's title"
        placeholder=""
        value=""
    />
    <inputcombo
        id="subtitle"
        type="text"
        label="Chart's subtitle"
        description="Chart's subtitle"
        placeholder=""
        value=""
    />
    <inputcombo
        id="x_axis_name"
        type="text"
        label="X-axis name "
        description="X-axis subtitle"
        placeholder=""
        value=""
    />
    <inputcombo
        id="y_axis_name"
        type="text"
        label="Y-axis name "
        description="Y-axis subtitle"
        placeholder=""
        value=""
    />
    <inputcombo
        id="series_name"
        type="text"
        label="Series name"
        description="Name for the series"
        placeholder="series 1"
        value=""
    />
</form>
```

All of these parameters in the form are accessible to the `widget.js` script using the Ubidots JavaScript class.

{% hint style="info" %}
For detailed information on how to access XML properties from within the `widget.js` script, refer to the corresponding section in the Plugins development page.
{% endhint %}

{% content-ref url="/pages/rS0xzSFJNeWovSJ42Sfh" %}
[Widget](/plugins/plugins-development/widget)
{% endcontent-ref %}

{% hint style="info" %}
For detailed information about the supported form elements, refer to the `view_widget.xml` section in the Plugins development page.
{% endhint %}

{% content-ref url="/pages/8k1p9YWRHCOVKqUXP9YJ" %}
[view\_widget.xml](/plugins/plugins-development/view_widget.xml)
{% endcontent-ref %}

## LICENSE and README.md files

Refer to the Private Plugins page for detailed information:

{% content-ref url="/pages/y6JQhF1KBcDzwb9Vtae2" %}
[Private plugins](/plugins/private-plugins)
{% endcontent-ref %}

## Manifest.toml

Aside from the previously mentioned manifest components common to all plugin types, the following are specific to widget plugins:

<table data-full-width="true"><thead><tr><th width="236">Section/subsection</th><th width="210.2">Key</th><th width="171">Value</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td>[settings.widget]</td><td>name</td><td>Any string</td><td>Name that the widget will display in the plugins drawer</td><td><pre><code>name = "My first widget plugin"
</code></pre></td></tr><tr><td>[settings.widget]</td><td>js_thirdparty_libraries*</td><td>A list of valid CDN URLs</td><td>List of libraries that the widget will import to use in its logic</td><td><pre><code>js_thirdparty_libraries = [
  "https://code.jquery.com/jquery-3.6.0.min.js",
  "https://code.jquery.com/moment-3.6.0.min.js"
]
</code></pre></td></tr><tr><td>[settings.widget]</td><td>css_thirdparty_libraries*</td><td>A list of valid CDN URLs</td><td>List of libraries that the widget will import to use in its styling</td><td><pre><code>css_thirdparty_libraries = [
  "https://code.jquery.com/jquery-3.6.0.min.css",
  "https://code.jquery.com/moment-3.6.0.min.css"
]
</code></pre></td></tr><tr><td>[settings.widget]</td><td>enable_lazy_load</td><td><code>true</code> or <code>false</code></td><td>Enables or disables lazy loading for the widget's visual components</td><td><pre><code>enable_lazy_load = true
</code></pre></td></tr></tbody></table>

With this in mind, a typical widget plugin `manifest.toml` file looks like this:

```
manifest_version = 2.0

[settings]
version = "1.1.1"
plugin_type = "widget"
license_name = "MIT license"

[settings.widget]
name = "Send data to device widget"  
js_thirdparty_libraries = [
  "https://code.jquery.com/jquery-3.6.0.min.js",
  "https://code.jquery.com/moment-3.6.0.min.js"
]
css_thirdparty_libraries = [
  "https://code.jquery.com/jquery-3.6.0.min.css",
  "https://code.jquery.com/moment-3.6.0.min.css"
]
enable_lazy_load = true
```

The keys marked with a \* in the table are optional. You do not need to import JS or CSS libraries. However, you should still define the key as an empty list.

For example, if no JS or CSS libraries need to be imported, the manifest looks like this:

```
manifest_version = 2.0

[settings]
version = "1.1.1"
plugin_type = "widget"
license_name = "MIT license"

[settings.widget]
name = "Send data to device widget"  
js_thirdparty_libraries = [ ] # No JS libraries are imported
css_thirdparty_libraries = [ ] # No CSS libraries are imported
enable_lazy_load = true
```


# Device

## Definition

A private **device** plugin lets Ubidots developers create custom devices with a native Ubidots experience. Unlike Ubidots native devices, these plugins let you define custom *categories* and *filters*. This gives you more control over how the device appears in the Devices drawer.

<figure><img src="/files/35lR0XiPvNKPDsw50bP0" alt="" width="563"><figcaption></figcaption></figure>

## Categories

Categories are set in the `manifest.toml` file and are the primary way to classify a Device plugin. In the screenshot above, categories are highlighted in red.

Each Device plugin can define its own categories, so the Devices drawer shows every category defined across your devices. If multiple devices share the same category, that category is displayed only once in the drawer and groups together all devices that define it.

For example, in the screenshot above, both devices define the `End Device` *category*. The `ALTA` device also defines the `Industrial Gateway` *category*. As a result, the drawer displays two categories:

* `End Device`
* `Industrial Gateway`

In the screenshot above, the **ALTA** device appears under two *categories* — **End Device** and **Industrial Gateway** — because a device can belong to multiple *categories* at the same time.

If the `categories` key is not specified in the `manifest.toml` file, the device is automatically added to a *category* called `plugin`.

{% hint style="warning" %}
If `categories` is specified but no value is given, the device will not be displayed.
{% endhint %}

## Filters

*Filters* are defined in the `manifest.toml` file. In the screenshot above, *filters* are highlighted in magenta.

This is the second way to classify devices and lets users define custom filters. These filters are built as key-value settings, where each value is a list of strings.

{% hint style="warning" %}
Neither *categories* nor *filters* can be used to filter devices in API requests. They only categorize devices in the drawer.
{% endhint %}

## Required files

As explained earlier, any private plugin, including a private device plugin, uses the file structure shown below:

```
├── src           
│   ├── view.xml           
├── LICENSE
├── README.md
├── manifest.toml
```

### manifest.toml <a href="#naming-convention" id="naming-convention"></a>

Aside from the manifest components shared by all plugin types, the following keys are specific to Device plugins:

<table><thead><tr><th width="121">Section/subsection</th><th width="115">Key</th><th width="161">Value</th><th width="157">Description</th><th>Example</th></tr></thead><tbody><tr><td>[settings]</td><td>categories</td><td>A list of strings</td><td>Each element in the list is the name of a category.</td><td><pre class="language-toml"><code class="lang-toml">categories = ["End Device"]
</code></pre></td></tr><tr><td>[settings]</td><td>filters</td><td><p>A dictionary with the following schema:</p><p><br><code>str: list</code></p></td><td>Each root key is the name of the filter, and its respective values are assigned to the device.</td><td><pre class="language-toml"><code class="lang-toml">filters = {brand = ["Advantech"], connectivity = ["Ethernet"]}
</code></pre></td></tr></tbody></table>

With this in mind, a typical device plugin manifest file looks like this:

```
manifest_version = 2.0

[settings]
version = "0.0.1"
plugin_type = "device"
license_name = "MIT license"
categories = ["End Device"]
filters = {brand = ["Advantech"], connectivity = ["Ethernet"]}

```

### view\.xml <a href="#view.xml" id="view.xml"></a>

This file is not currently used by Device plugins, but it must still contain valid placeholder data so the plugin can be deployed.

### LICENSE and README.md files

For more details, see [Private Plugins](/plugins/private-plugins).

### Naming convention <a href="#naming-convention" id="naming-convention"></a>

Keep the names of all files and directories mentioned in the previous section. The plugins engine looks specifically for those files and their extensions.


# Plugins development

The development of a plugin, regardless of its type, involves creating the source and configuration files that define the plugin's identity and functionality. These files are then organized into the file structure defined by Ubidots, as described in the previous sections. The process is illustrated in the following flowchart:

<figure><img src="/files/mmlKghF6FKcUW7EM33mG" alt="" width="375"><figcaption></figcaption></figure>

The process consists of the following steps:

1. Establish the plugin's file structure.
2. Add the appropriate content to the individual files that make up the plugin.
3. Define the plugin version in the manifest file.
4. Compress the files.

For detailed information about each plugin type, visit its dedicated page:

{% content-ref url="/pages/rOXxLSuVRECFLwBSShdE" %}
[Cron](/plugins/plugins-development/cron)
{% endcontent-ref %}

{% content-ref url="/pages/uMjYgcn6XkaTixPJ3k3G" %}
[Webhook](/plugins/plugins-development/webhook)
{% endcontent-ref %}

{% content-ref url="/pages/rS0xzSFJNeWovSJ42Sfh" %}
[Widget](/plugins/plugins-development/widget)
{% endcontent-ref %}

{% content-ref url="/pages/3swuePrz8tIrfNH4G7Er" %}
[Device](/plugins/plugins-development/device)
{% endcontent-ref %}


# Getting started

Step-by-step walkthrough for building your first widget plugin using the Highcharts library.

## Widget plugin

Let's create a widget that uses the Highcharts JavaScript library to plot time series data. It will look like this:

<figure><img src="/files/yULAW3bub9oAGJyig7PH" alt=""><figcaption></figcaption></figure>

Download the ZIP file that contains the source code for this widget from [here](https://res.cloudinary.com/di2vaxvhl/raw/upload/v1694454062/Plugin%20Assets/Widget%20plugin/CustomHighCharts.zip). Then go to Ubidots ⟶ Devices ⟶ Plugins. Once there, click the Developer console icon:

<figure><img src="/files/yj2OBzGLDr1YXBTdLdRk" alt=""><figcaption></figcaption></figure>

Click the + icon to create a new plugin, set a name and description, then click the done button. After that, your new plugin will be created:

<figure><img src="/files/ai5gxWKjQBjS2co4ECie" alt=""><figcaption></figcaption></figure>

Click the newly created plugin's name. In the *General information* tab, click *Add Plugin Icon* to upload the icon that will appear in the plugins drawer, then click *Save* in the *Display information* section. You can download the icon from [here](https://res.cloudinary.com/di2vaxvhl/image/upload/v1693863919/Plugin%20Assets/Widget%20plugin/highChartsLogo.png).

<figure><img src="/files/ouzLJBZIyteocuYsAIM7" alt="" width="563"><figcaption></figcaption></figure>

Scroll down to the *Plugin description* section, paste the following README, and click the *Save* button in that section.

```
# Ubidots Highcharts Widget
Visualize your Ubidots variable data using the Highcharts graphing library with this widget. Easily configure titles, axis names, and more!
![](https://res.cloudinary.com/di2vaxvhl/image/upload/v1693865751/Plugin%20Assets/Widget%20plugin/WidgetDisplay.png)

## Features

- Seamless integration with Ubidots.
- Real-time fetching of variable data based on the provided label.
- Customizable graph elements like title, subtitle, x-axis & y-axis names, and the series name.

## Table of Contents

- [Ubidots Highcharts Widget](#ubidots-highcharts-widget)
  - [Features](#features)
  - [Table of Contents](#table-of-contents)
  - [Prerequisites](#prerequisites)
  - [Setup and Installation](#setup-and-installation)
  - [Widget's inputs](#widgets-inputs)
  - [License](#license)

## Prerequisites
- An active Ubidots account with Enterprise license.

## Setup and Installation
1. Log in to your Ubidots account.
2. Navigate to the **Dashboards** section and select the one in which you want to place the widget.
3. Click on **Add Widget** and select **HighCharts widget**.

## Widget's inputs 

After adding the widget to your dashboard:

1. Configure the widget settings:
   - **Chart's title**: Define the main title of the graph.
   - **Chart's Subtitle**: Provide a subtitle, if needed.
   - **X Axis Name**: Specify the label for the x-axis.
   - **Y Axis Name**: Specify the label for the y-axis.
   - **Variable Label**: Click on the "ADD VARIABLE" button and follow the variable selection wizard.
     - **Series Name**: Define the label that will be displayed within the graph line.

2. Save the changes and the graph will automatically fetch the data and display it.

## License
This project is licensed under the MIT License. See the `LICENSE` file for more details.
```

Navigate to the *Version* tab in the sidebar menu. Click the "+" icon and upload the ZIP file. The plugin is now in your Ubidots account, but it still needs to be deployed.

In the *Versions* tab, scroll down to the *Version history* section and click the icon that looks like a paper airplane:

<figure><img src="/files/0kFafZci1tL3bZvlRAW3" alt=""><figcaption></figcaption></figure>

A confirmation dialog will appear. Click the *Release* button:

<figure><img src="/files/0yl9RQsVa90uSaQ3SonS" alt="" width="375"><figcaption></figcaption></figure>

After that, your plugin's status will change from *Draft* to *Released*:

<figure><img src="/files/3DskIzzS1GcNK1RbYdIu" alt=""><figcaption></figcaption></figure>

Go to *Devices* ⟶ *Plugins* and click the *Create new plugin* button. In the plugins drawer, search for the plugin you just created, *Highcharts custom widget*, fill in the required *Ubidots token* field, and click *Done*. After that, you will see that a new plugin has been created:

<figure><img src="/files/oCdlMi6Fvk7yQxLBRNHh" alt=""><figcaption></figcaption></figure>

Go to the dashboard where you want to place the widget and click the *Add new widget* button. A drawer will pop up and display all available widgets. Search for the widget you just created and click it. An instance of the plugin will be placed on the dashboard:

<figure><img src="/files/q7bPZSpPgwgVI4egQe8j" alt=""><figcaption></figcaption></figure>

Fill in the widget fields based on the variable whose data you want to visualize. The following screenshot shows an example:

<figure><img src="/files/Ima8MJexq3S5FWOOmVGq" alt="" width="563"><figcaption></figcaption></figure>

The widget is now created on the dashboard. This is how it looks:

<figure><img src="/files/ld88zU1NQdtOfmLtodBz" alt=""><figcaption></figcaption></figure>


# Cron

Learn the specifics of developing a Cron plugin.

## Select the running environment

The first step in developing a new cron plugin is to determine which environment to use:

* Python
* NodeJS

## Create the file structure

Set up a directory structure with the following files:

```
├── src
│   ├── control.py or control.js  
│   ├── function.py or function.js
│   ├── view.xml
├── LICENSE
├── README.md
├── manifest.toml 
```

## control and function scripts

Fill these files with the content you need according to the table below, then save them.

<table data-full-width="true"><thead><tr><th width="125">Script</th><th width="315">Executed</th><th width="486">Purpose</th></tr></thead><tbody><tr><td>control</td><td>Only once when the plugin is created</td><td>Perform one time setup tasks</td></tr><tr><td>function</td><td>Periodically according to the frequency set</td><td>Perform time-triggered tasks</td></tr></tbody></table>

## view\.xml

Populate this file with content based on the form you'd like displayed when creating this plugin.

## LICENSE and README.md

Write the contents of the `README.md` and `LICENSE` files.

## manifest.toml

According to the Cron section of the Private Plugins page, `manifest.toml` should look like this:

```
manifest_version = 2.0
 
[settings] 
version = "1.0.56" 
runtime = "python3.11" <- replace as needed 
plugin_type = "cron"  <- fixed value. Do not change
license_name = "MIT license" 

[settings.cron] 
interval = 1 
run_on_deploy = false

[pricing]
price = 0 
```

## Compressing the files

Compress the files into a ZIP archive from the root of the file structure, not the parent folder.


# Webhook

Learn the specifics of developing a webhook plugin.

## Select the running environment

The first step in developing a new webhook plugin is to determine which environment to use:

* Python
* NodeJS

## Create the file structure

Set up a directory structure with the following files:

```
├── src
│   ├── control.py or control.js  
│   ├── function.py or function.js
│   ├── user_code.py or user_code.js
│   ├── view.xml
├── LICENSE
├── README.md
├── manifest.toml 
```

## control, function, and user\_code scripts

Fill these files with the content you need according to the table below, then save them.

<table><thead><tr><th width="127">Script</th><th width="244">Executed</th><th width="379">Purpose</th></tr></thead><tbody><tr><td>control</td><td>Only once when the plugin is created</td><td>Perform one-time setup tasks</td></tr><tr><td>function</td><td>Triggered by a webhook</td><td>Perform any task</td></tr><tr><td>user_code</td><td>Triggered by a webhook if invoked by the function script</td><td>Perform any task</td></tr></tbody></table>

## view\.xml

Populate this file with content based on the form you'd like displayed when creating this plugin.

## LICENSE and README.md

Write the contents of the `README.md` and `LICENSE` files.

## manifest.toml

According to the Webhook section of the Private Plugins page, `manifest.toml` should look like this:

```
manifest_version = 2.0
 
[settings] 
version = "1.0.56" 
runtime = "nodejs:20" | "python:3.11" 
plugin_type = "webhook" 
license_name = "MIT license" 

[settings.webhook]
default_http_method = "POST" | "GET"
allowed_http_methods =  ["POST", "GET"]

[pricing]
price = 0
```

## Compressing the files

Compress the files into a ZIP archive from the root of the file structure, not the parent folder.


# Widget

Learn the specifics of developing a widget plugin.

## Create the file structure

Set up a directory structure with the following files:

```
├── src
│   ├── static                    
│   │   ├── widget.css      
│   │   ├── widget.html 
│   │   ├── widget.js             
│   ├── view.xml
│   ├── view_widget.xml
│   ├── control.py
│   ├── function.py
│   ├── user_code.py  
├── LICENSE
├── README.md
├── manifest.toml
```

## widget.html, widget.css, and widget.js scripts

Fill these files with the content you need according to the table below, then save them.

<table><thead><tr><th width="170">File</th><th width="587">Purpose</th></tr></thead><tbody><tr><td>widget.html</td><td>Define the widget's component layout</td></tr><tr><td>widget.css</td><td>Define the widget's component styles</td></tr><tr><td>widget.js</td><td>Define the widget's logic and dynamically modify its styles.</td></tr></tbody></table>

{% hint style="info" %}
Widgets execute in the user's web browser as client-side applications. Runtime selection only applies to the server-side scripts `control`, `function`, and `user_code`.
{% endhint %}

## view\.xml

At the moment, this file is not fully implemented for widget plugins, so it is not relevant to this plugin type. However, it must contain dummy but valid data so the plugin can be deployed. For this reason, you can use the following `view.xml` file as a dummy file during widget plugin development:

```xml
<form name="formName">
    <group name="ubidotsInformation">
        <group name="fieldToken">
            <label name="labelToken">Ubidots Token</label>
            <token name="token" placeholder="Select Token"/>
        </group>
    </group>
</form>
```

## view\_widget.xml

Fill this file with the content you want to display when the user creates the widget in the dashboard.

## LICENSE and README.md

Write the contents of the `README.md` and `LICENSE` files.

## manifest.toml

Fill this file according to the Widget section of the Private Plugins page. `manifest.toml` should look like this:

```webmanifest
manifest_version = 2.0

[settings]
version = "1.1.1"
plugin_type = "widget_webhook"
license_name = "MIT license"
runtime = "python:3.11"
infrastructure = "aws"

[settings.webhook]
default_http_method = "POST"
allowed_http_methods =  ["POST"]

[settings.widget]
name = "Send data to device widget"

js_thirdparty_libraries = [
  "https://code.jquery.com/jquery-3.6.0.min.js",
  "https://code.jquery.com/moment-3.6.0.min.js"
]
css_thirdparty_libraries = [
  "https://code.jquery.com/jquery-3.6.0.min.css",
  "https://code.jquery.com/moment-3.6.0.min.css"
]
enable_lazy_load = true
```

## Compressing the files

Compress the files into a ZIP archive from the root of the file structure, not the parent folder.


# Device

Learn the specifics of developing a device plugin.

## Create the file structure <a href="#create-the-file-structure" id="create-the-file-structure"></a>

Set up a directory structure with the following files:

```
├── src           
│   ├── view.xml           
├── LICENSE
├── README.md
├── manifest.toml
```

## manifest.toml

Fill this file with the proper configuration according to the *Device* section of the Private Plugins page. Based on that, the manifest should look like this:

```
manifest_version = 2.0

[settings]
version = "0.0.1"
plugin_type = "device"
license_name = "MIT license"
categories = ["End Device"]
filters = {brand = ["Advantech"], connectivity = ["Ethernet"]}
```

## view\.xml

At the moment, this file is not fully implemented for Device plugins, so it is not relevant to this plugin type. However, it must contain dummy but valid data so the plugin can be deployed. For this reason, you can use the following `view.xml` file as a dummy file during Device plugin development:

```xml
<form name="formName">
    <group name="ubidotsInformation">
        <group name="fieldToken">
            <label name="labelToken">Ubidots Token</label>
            <token name="token" placeholder="Select Token"/>
        </group>
    </group>
</form>
```

## LICENSE and README.md

Write the contents of the `README.md` and `LICENSE` files.

## Compressing the files

Compress the files into a ZIP archive from the root of the file structure, not the parent folder.


# view\.xml

This page lists all of the available tags within the view\.xml file and how to access them from within the plugin

This file lets developers create a form that is displayed as a GUI when the plugin is created.

Its contents use Ubidots-mapped, XML-like tags that render as React elements. Because of this, it differs from traditional XML tags.

## Form

This is the main tag because it wraps the entire form. It maps to a `form` element. It has the following attribute:

* **name:** Document-unique text that identifies the form.

Example:

```xml
<form name="form-name">
...
</form>
```

## Group

This tag maps to a `div` in the DOM. It acts as a container and lets you group elements. It has the following attribute:

* **name:** Document-unique text that identifies the group.

Example:

```xml
<group name="group-1">
    <group name="group-2">
        ...
    </group>
</group>
```

This `div` uses CSS flexbox so it renders correctly in the front end.

## Label

This tag maps to a `label` element in the DOM. It is usually used as the title for an input element, but it is not limited to that use. Its properties are:

* **name:** Document-unique text that identifies the label.
* **help (optional):** Help text displayed as a question mark icon next to the label. Clicking the icon reveals the text, which can also contain external links written in markdown.

Example:

```xml
// Label with help text
<label
  name="label-with-help"
  help="Some help text, [Click here](https://google.com)"
>
  Test label
</label>

// Label without help 
<label 
  name="label"
>Test label
</label>
```

## Input

This tag maps to an input element in the DOM. This field can be `password`, `checkbox`, or `text`. Its properties are:

* **name:** Document-unique text that identifies the input.
* **type:** Input type. Allowed values are `password`, `checkbox`, and `text`.
* **placeholder (optional):** Helper text displayed inside the input field.
* **value (optional):** Default value for the input.

```xml
// Text type input
<input
  name="input-text"
  type="text"
  placeholder="Test placeholder"
  value="test value"
/>
 
// Password type input
<input
  name="input-password"
  type="password"
  placeholder="Test placeholder"
/>

// Checkbox type input
<input
  name="input-checkbox"
  type="checkbox"
  value="false"
/>
```

The `text` and `password` inputs are sent to the plugin as plain text. The checkbox is sent as a boolean:

```json
{
  "input-text": "test value",
  "input-password": "",
  "input-checkbox": false
}
```

## Paragraph

This tag maps to a `p` element in the DOM. It lets you include text blocks within the form. Its properties are:

* **name:** Document-unique text that identifies the paragraph.

Example:

```xml
<paragraph name="paragraph">
  Lorem Ipsum is simply dummy text of the printing and typesetting industry. 
  Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, 
  when an unknown printer took a galley of type and scrambled it to make a 
  type specimen book. It has survived not only five centuries, but also 
  the leap into electronic typesetting, remaining essentially unchanged. 
  It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more 
  recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  [link](https://www.lipsum.com/)
</paragraph>
```

As shown above, this tag supports external links written in markdown. The link must include the HTTP or HTTPS protocol.

## Map

This tag represents a map in the browser. Like any other form field, it lets users select a location through Google Maps. Its properties are:

* **name:** Document-unique text that identifies the map within the form.
* **lat (optional):** If allowed by the user, their position will be retrieved and `lat` will be the user's position latitude. Else, it will be set to `37.428458` by default.
* **lng (optional):** If allowed by the user, their position will be retrieved and `lng` will be the user's position longitude. Else, it will be set to `-122.160901` by default.
* **zoom (optional):** Initial zoom value. The default is `12`.
* **type (optional):** Map type. Accepted values are `roadmap`, `satellite`, and `hybrid`. The default is `hybrid`.

Example:

```xml
// Map with all properties set to their default values
<map
  name="map"
/>

// Map with custom properties
<map
  name="map-2"
  lat="37.420015"
  lng="-122.160901"
  zoom="13"
  type="satellite"
/>
```

The `lat` and `lng` values are sent to the plugin in an object like this:

```json
{"map": { lat: 37.428458, lng: -122.160901 }}
```

This is how the map looks:

<figure><img src="/files/kttXXIkDFXQVcmtu0WQo" alt="" width="563"><figcaption></figcaption></figure>

## Select

This element is rendered in the browser as a dropdown. It lets users select one or more items from a list. Its properties are:

* **name**: A unique identifier for the dropdown within the form.
* **type**: Determines if the dropdown allows single or multiple selections. Accepted values are `single` and `multiple`.
* **search** (optional): Enables a search feature within the dropdown. Accepted values are `enabled` and `disabled`.
* **placeholder**: A default message displayed in the dropdown before any selection.
* **value** (optional): Default value for the dropdown.

**Note**: For a `multiple` dropdown, the default value must be a comma-separated string with no spaces.

```xml
<!-- Single selection dropdown -->
<select 
  name="single-name" 
  placeholder="Single placeholder" 
  value="1" 
  type="single"
>
  
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>

</select>

<!-- Multiple selection dropdown with search enabled -->
<select 
  name="multiple-name" 
  placeholder="Multiple placeholder" 
  value="1,3,2" 
  type="multiple" 
  search="enabled">
  
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
  
</select>
```

For each case, the plugin interprets the values as follows:

```json
// Single selection
{"single-name":  "1"}

// Multiple selection
{
  "multiple-name": [
    { "id": "1", "name": "1"}, 
    { "id": "3", "name": "3"}, 
    { "id": "2", "name": "2"}
  ]
}

```

## Option

This tag represents an individual item within a dropdown. It can only be used inside a dropdown and cannot exist on its own. Its property is:

* **value**: The associated value of the item.

Example:

```xml
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
```

## Token

This dropdown lists all user tokens and lets one be selected for plugin configuration. Its properties are:

* **name**: A unique identifier for the token within the form.
* **placeholder**: A default message displayed in the dropdown before any selection.

Example:

```xml
<token 
    name="ubi-token" 
    placeholder="Ubidots token"
/>
```

This is sent to the plugin as:

```json
{"ubi-token": "fake-token"}
```

## Frequency

A text field with a label, both arranged on a single line. The text field is positioned inside the label based on a keyword. Its property is:

* **label**: Descriptive text for the interval input field. The label can contain the `{field}` keyword, which marks the position of the text field. If it is not included, the field is placed at the end of the label.

Example:

```xml
<frequency label="Run every {field} minutes"/>
```

## Connected Account field

This field lists all accounts connected to Ubidots, filtered by the `provider` attribute. It also lets users connect new accounts from the same provider. Its attributes are:

* **label** (optional): Descriptive text for the field.
* **provider**: Refers to the external service that can be connected. Currently, the only accepted value is `AWS_IAM`. This attribute is case-sensitive.

Example:

```xml
<connectedAccount label="Label test" provider="AWS_IAM"/>
```

**Note**: Information from this field can only be accessed from the control script through environment variables. Variables that reference this field use the `AUTH_CREDENTIALS_` prefix.

## Accessing view\.xml elements from within the plugin

Any element in the `view.xml` file can be accessed from the plugin scripts, namely `control` and `function`. For example, suppose your plugin uses the following `view.xml` file:

<pre class="language-xml"><code class="lang-xml">&#x3C;form>
  &#x3C;input
    name="input-text"
    type="text"
    placeholder="Test placeholder"
    value="test value"
  />
 
  &#x3C;input
    name="input-password"
    type="password"
    placeholder="Test placeholder"
  />

  &#x3C;input
    name="input-checkbox"
    type="checkbox"
    value="false"
  />
  
<strong>  &#x3C;select 
</strong>    name="age-range" 
    placeholder="Select your age range" 
    value="15-20" 
    type="single"
<strong>  >
</strong>    &#x3C;option value="1">15-20&#x3C;/option>
    &#x3C;option value="2">21-35&#x3C;/option>
    &#x3C;option value="2">More than 36&#x3C;/option>
  &#x3C;/select>

&#x3C;/form>
</code></pre>

To access this data in those scripts, read the `_parameters` key from the incoming JSON payload, which contains the function arguments.

In this case, the JSON would look like this:

```
{
    ... /Other data,
    .
    .
    .
    _parameters : {
        "input-text" : "some text",
        "input-password" : "some password",
        "input-checkbox" : "false"
    }
}
```

If you want to get the value of the `input-text` field, use a snippet like this in your script:

```
inputTextValue = args["_parameters"]["input-text"]
```

Likewise, the `input-password`, `input-checkbox`, and `age-range` values can be read like this:

```
inputPasswordValue = args["_parameters"]["input-password"]
inputCheckboxValue = args["_parameters"]["input-checkbox"]
ageRangeValue = args["_parameters"]["age-range"]
```


# view\_widget.xml

This page lists all of the available tags within the view\_widget.xml file and how to access them from within the widget

This file lets developers create a form that is displayed as a GUI when a widget is created on a dashboard.

Its contents use Ubidots-mapped, XML-like tags that render as React elements. Because of this, it differs from traditional XML tags. Also, `view.xml` and `view_widget.xml` are implemented differently, so they support different tags.

For the available elements and usage details, see [Custom UI](/dashboards-and-widgets/custom-ui).

## Basic structure

Ubidots native widgets include two configuration tabs:

* Settings
* Appearance

<figure><img src="/files/O3u4ipPW7uTuTs6bt9FW" alt="" width="563"><figcaption></figcaption></figure>

You can implement the same layout in a widget plugin drawer with the `tabs` element in the `view_widget.xml` file. The following example defines three tabs: `Settings`, `Appearance`, and `Advanced`.

```xml
<form>
    <tabs selected="settings">
        <tab id="settings" padded="false" title="Settings">
            <addvariable
                id="settings.variable-picker"
                maxvariables="1"
                dividername="Add Variables"
                widgetbehavior="true"
            >
                <variablelabel/>
                <inputcombo
                    id="custom-label"
                    label="Custom name"
                    placeholder="Water level"
                    type="text"
                />
            </addvariable>
        </tab>
        <tab id="appearance" title="Appearance">
            <inputcombo 
                id="appearance.title" 
                type="text" 
                label="Widget title" 
                placeholder="Water level" 
            />
            <inputcombo
                id="appearance.subtitle"
                type="text"
                label="Widget subtitle"
                placeholder="Water level in the tank"
            />
            <inputcombo
                id="appearance.external-item-text"
                type="text"
                label="External item text"
                placeholder="External item"
            />
            <inputcombo
                id="appearance.external-item-url"
                type="text"
                label="External item URL"
                placeholder="http://example.com"
            />
        </tab>
        <tab id="advanced" title="Advanced">
            <inputcombo
                id="advanced.widget-id"
                type="text"
                label="Widget ID"
                placeholder="widget-1"
            />
            <inputcombo
                id="advanced.widget-class"
                type="text"
                label="Widget class"
                placeholder="widget-class"
            />
            <inputcombo
                id="advanced.widget-style"
                type="text"
                label="Widget style"
                placeholder="width: 100%;"
            />
        </tab>
    </tabs>
</form>
```

This configuration renders the following drawer:

| Settings tab                                                        | Appearance tab                                                      | Advanced tab                                                        |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| <img src="/files/STjbxqMgdsxZaJcHMXeu" alt="" data-size="original"> | <img src="/files/EEU4Nwjk0eX0yNWYYiZ6" alt="" data-size="original"> | <img src="/files/0lxVcwr5htSIS2OwGW4t" alt="" data-size="original"> |

The number and names of tabs are not fixed. You can add and name tabs as needed beyond `settings` and `appearance`.

{% hint style="info" %}
To ensure `ubidots.getWidget().getSettings()` returns a structured object where child elements are nested under their respective tabs, use element IDs in this format:

```
parent-tab-id.child-element-id
```

{% endhint %}

## Tabs

Tabs help organize user input in the widget drawer and make the interface easier to use. Tabs have two attributes:

* **id:** Unique identifier for the tab element.
* **title:** Text displayed in the drawer to identify the tab.

Inside a tab, you can place any form element available in [Custom UI](/dashboards-and-widgets/custom-ui).

### **ID Naming Conventions**

**Using Parent-Referenced IDs**

This method explicitly associates each element with its parent tab. The resulting `xmlSettings` object keeps a structured hierarchy, with settings grouped under their respective tabs.

**Example XML:**

```xml
<tab id="settings" title="Settings">
    <inputcombo id="settings.title" type="text" label="Title"/>
    <inputcombo id="settings.subtitle" type="text" label="Subtitle"/>
</tab>

<tab id="appearance" title="Appearance">
    <inputcombo id="appearance.widget-title" type="text" label="Widget Title"/>
</tab>
```

**Resulting JSON (`ubidots.getWidget().getSettings()`):**

```json
{
   "xmlSettings": {
      "settings": {
         "title": "Title",
         "subtitle": "Subtitle"
      },
      "appearance": {
         "widget-title": "Widget Title"
      }
   }
}
```

This structure keeps settings grouped under their corresponding tab.

#### **Using Flat IDs (Without Parent Reference)**

If element IDs do not reference their parent tab, all elements are placed at the root of `xmlSettings`. This makes it harder to tell which tab they belong to.

**Example XML:**

```xml
<tab id="settings" title="Settings">
    <inputcombo id="title" type="text" label="Title"/>
    <inputcombo id="subtitle" type="text" label="Subtitle"/>
</tab>
<tab id="appearance" title="Appearance">
    <inputcombo id="widget-title" type="text" label="Widget Title"/>
</tab>
```

**Resulting JSON (`ubidots.getWidget().getSettings()`):**

```json
{
   "xmlSettings": {
      "title": "Title",
      "subtitle": "Subtitle",
      "widget-title" : "Widget Title"
   }
}
```

In this case, all child elements appear at the root level and lose their association with their parent tab.

**Best Practice**

To keep the configuration well structured, we strongly recommend following the `parent-tab-id.child-element-id` convention when defining IDs for elements inside tabs.

## 'Add Variables' element

Ubidots native widgets let users select the target variables for the widget and choose the **widget behavior** — static or dynamic.

<figure><img src="/files/0sx6HrsX6WT5Fe9tssKD" alt="" width="563"><figcaption></figcaption></figure>

You can implement the same behavior with the `addvariable` element in the `view_widget.xml` file.\
\
For example, to replicate the Metric widget UI shown above, you can use the following `view_widget.xml`:

```xml
<form>
    <addvariable
        id="variables"
        maxvariables="1"
        dividername="ADD VARIABLES"
    >
        <variablelabel
            label="Variable label"
            description=""
        />
        <inputcombo
            type='dropdown.list'
            id='aggregationMethod'
            label="Aggregation method"
            description=""
            placeholder='Last value'
        >
            <menu>
                <item id='last_value'>Last value</item>
                <item id='average'>Average</item>
                <item id='minimum'>Minimum</item>
                <item id='maximum'>Maximum</item>
                <item id='sum'>Sum</item>
                <item id='count'>Count</item>
            </menu>
        </inputcombo>
        <inputcombo
            type="span"
            id="span"
            label="Span"
            description=""
        />

    </addvariable>
</form>
```

Inside the `addvariable` tag, you can use any element described in [Custom UI](/dashboards-and-widgets/custom-ui).

### addvariable tag

This element renders the full modal used to add variables. It has the following attributes:

<table><thead><tr><th width="173">Attribute</th><th width="122">Mandatory</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>Yes</td><td>Unique identifier used internally. Do not modify its default value.</td></tr><tr><td><code>maxvariables</code></td><td>Yes</td><td>Maximum number of variables that can be added.</td></tr><tr><td><code>dividername</code></td><td>Yes</td><td>Divider label displayed in the UI.</td></tr></tbody></table>

### variablelabel tag

This element displays a label and description inside a container to provide additional context.

<table><thead><tr><th width="166">Attribute</th><th width="134">Mandatory</th><th>Description</th></tr></thead><tbody><tr><td>label</td><td>Yes</td><td>Message displayed when selecting a variable.</td></tr><tr><td>description</td><td>No</td><td>Message displayed below the label.</td></tr></tbody></table>

<figure><img src="/files/kfMVEBSYVMBG0xmcvuM7" alt="" width="563"><figcaption></figcaption></figure>

If you change the `label` attribute from `Variable label` to `Variables`, that message will be displayed instead.

## Adding variables' Aggregation methods

If your plugin widget requires the variable's aggregation method, you can use an `inputcombo` of type `dropdown.list` as shown above. For it to work correctly, the `id` attribute must be `aggregationMethod`.

## Adding variable's time-span

If your plugin widget requires a time span for the variable's data, you can use an `inputcombo` of type `span` as shown above. For it to work correctly, the `id` attribute must be `span`.

## Accessing \`view\_widget.xml\` data from the Widget's script

You can access `view_widget.xml` elements from `widget.js` by using the input combo `id` attribute.

For example, suppose your widget uses the following `view_widget.xml` file:

```xml
<form>

    <inputcombo 
        id="device_label" 
        type="text" 
        label="Device label" 
        description="Device's label to which send the data" 
        placeholder="" 
    />
    <inputcombo 
        id="title" 
        type="text" 
        label="Chart's title" 
        description="Chart's title" 
        placeholder="" 
    />
    <addvariable
        id="variables"
        maxvariables="1"
        dividername="ADD VARIABLES"
    >
        <variablelabel
            label="Variable label"
            description=""
        />
        <inputcombo
            type='dropdown.list'
            id='aggregation_method'
            label="Aggregation method"
            description=""
            placeholder='Last value'
        >
            <menu>
                <item id='last_value'>Last value</item>
                <item id='average'>Average</item>
                <item id='minimum'>Minimum</item>
                <item id='maximum'>Maximum</item>
                <item id='sum'>Sum</item>
                <item id='count'>Count</item>
            </menu>
        </inputcombo>
        <inputcombo
            type="span"
            id="span"
            label="Span"
            description=""
        />
    </addvariable>
</form>
```

You can access this data as follows:

```javascript
const settings = ubidots.getWidget().getSettings()["xmlSettings"];
```

Then `settings` will look like this:

```json
{
   "title":"my chart",
   "variables":[
      {
         "span":"inherit",
         "color":{
            "a":1,
            "b":62,
            "g":131,
            "r":131
         },
         "label":"variable-cron",
         "variableColor":{
            "a":1,
            "b":62,
            "g":131,
            "r":131
         },
         "aggregationMethod":"last_value"
      }
   ],
   "device_label":"my device",
   "widgetBehavior":"inherit"
}
```

This object contains, at the root level, the keys that correspond to each `view_widget.xml` element `id`.

In the example above, two input combos use the IDs `device_label` and `title`. Those keys appear in the `settings` object. The `variables` key is always a list that contains the selected variables.

You can also access the widget endpoint like this:

```javascript
const url = ubidots.getWidget().getSettings()["function_url"]
```


# Plugins deployment

Deploying a plugin means making it accessible to end-users after the development phase. While the deployment process can differ across the different plugin types, there's a consistent step: uploading the plugin's source files to the Ubidots *Developer console*.\
\
At a high level, the process looks like this:

<figure><img src="/files/ZaKDdxNfe8Hy7n4qwzV8" alt=""><figcaption></figcaption></figure>

## Developer console

To access the Developer console, open the Ubidots account where the plugin will be used. Then go to *Devices* ⟶ *Plugins*. There, you will see the following icon:

<figure><img src="/files/WZbXSi95kaNxGOHiesNd" alt=""><figcaption></figcaption></figure>

The following screenshot shows what the Developer console looks like:

<figure><img src="/files/0H6mHP9RsFThGbSWjGiB" alt=""><figcaption></figcaption></figure>

From here, you can:

* Create new plugins
* Visualize and edit all previously created plugins

{% hint style="info" %}
In this context, creating a plugin means setting up a repository where the plugin's source code can be uploaded and hosted.
{% endhint %}

## Create a new plugin

Click the *add new plugin* button. A modal slides in from the right side of the screen and asks for the plugin's name and description. Fill in those fields, then click the check mark icon to continue.

<figure><img src="/files/uqpPGnmMUAj1Q66DQNxm" alt="" width="563"><figcaption></figcaption></figure>

When the modal collapses, you will see the plugin you just created:

<figure><img src="/files/b0hbGUDubuQtkgyxb5Yr" alt=""><figcaption></figcaption></figure>

Click it to display its options.

## Plugin's general information tab

<figure><img src="/files/Uwvkov1IJN9juAyfHlLx" alt=""><figcaption></figcaption></figure>

From here you can:

* Set an icon for your plugin
* Edit the plugin's name and description
* Set the README for the plugin

### Guidelines for setting a proper plugin icon

#### Image requirements

* The icon must be in *png* format.
* The icon must be a 512x512 pixels square image.

{% hint style="info" %}
Using a format other than *png* causes an error when you try to upload the image.
{% endhint %}

{% hint style="info" %}
Using a different image size, or an image without a square aspect ratio, might cause resizing. This can alter its appearance compared to the original image.
{% endhint %}

#### Using Ubidots style in the plugins icons

If you want to match the style of Ubidots native plugin icons, use the following setting:

* **Border radius:** 86 px

## Version tab

<figure><img src="/files/JyRkxAQJHMriOMb3X3B5" alt="" width="563"><figcaption></figcaption></figure>

Click the *Add new plugin version* icon, as shown above. A dialog opens for you to upload the ZIP file that contains the plugin files mentioned in the *Plugins development* section:

<figure><img src="/files/IIMZxVrQLRjPkPTTPaOn" alt="" width="563"><figcaption></figcaption></figure>

After you upload the ZIP file, the plugin's version page looks like this:

<figure><img src="/files/7y2rc0yjGjBC53l6PfPe" alt="" width="563"><figcaption></figcaption></figure>

From here you can:

* View the plugin's version
* View the plugin's price
* View the plugin's status (*Draft*, *Released*, *Published*)
* View the number of users using the plugin
* Release the plugin (paper airplane icon)

Click the *release* button and accept the pop-up:

<figure><img src="/files/dn8tJlkqjyXU3NatjnR7" alt="" width="563"><figcaption></figcaption></figure>

The plugin's status changes from *Draft* to *Released.*

For Cron and Webhook plugins, the deployment process ends here. For widget plugins, a few steps remain.

To learn how to use your new Cron or Webhook plugin, go to [Cron and Webhook](/plugins/using-the-plugins/cron-and-webhook).

To continue deploying a widget plugin, go to [Widget](/plugins/plugins-deployment/widget).


# Cron and webhook

This page depicts the process of deploying Cron and Webhook plugins

If you followed all the steps in:

{% content-ref url="/pages/Cb4Pyn7KR79XeItpp04E" %}
[Plugins deployment](/plugins/plugins-deployment)
{% endcontent-ref %}

Your Cron or Webhook plugin is already deployed and ready to use. To learn how to use it, go to:

{% content-ref url="/pages/uZkgFujhEiWxZAGHQVaH" %}
[Cron and webhook](/plugins/using-the-plugins/cron-and-webhook)
{% endcontent-ref %}


# Widget

After releasing the plugin, go to *Devices* ⟶ *Plugins* in Ubidots, then click the *create new plugin* button:

<figure><img src="/files/7rcyuVe9Rtp8A5KNFmBq" alt=""><figcaption></figcaption></figure>

The plugins drawer will pop up from the right side of the screen. There, search for the widget plugin you created.

<figure><img src="/files/aXpmVl3JMrt4gk3kLQAr" alt="" width="563"><figcaption></figcaption></figure>

When you click the plugin, the readme you set up during the deployment phase appears here:

<figure><img src="/files/hECS4U8Vb1RSiqBo55Z6" alt="" width="563"><figcaption></figcaption></figure>

Click the *next* button to proceed with plugin creation. Fill in the plugin's required fields, then click *next* again:

<figure><img src="/files/Dt59gtDO9tevJVjpqTLY" alt="" width="563"><figcaption></figcaption></figure>

Once done, the plugin appears in your created plugins section:

<figure><img src="/files/x0tDon7Sh1oXvgNklBBK" alt=""><figcaption></figcaption></figure>

That’s it for deploying a Widget plugin. Next, learn how to use the widget in its dedicated section:

{% content-ref url="/pages/eUzX6ZdXgr7JNIGL4jgo" %}
[Widget](/plugins/using-the-plugins/widget)
{% endcontent-ref %}


# Device

After releasing the plugin, go to *Devices* ⟶ *Plugins* in Ubidots, then click the *create new plugin* button:

<figure><img src="/files/VwtTsjXh1HFGvlaT4FQc" alt=""><figcaption></figcaption></figure>

The plugins drawer will pop up from the right side of the screen. There, search for the device plugin you recently uploaded to the developer console:

<figure><img src="/files/jWYYcKo4kaeGomx5aGKD" alt="" width="563"><figcaption></figcaption></figure>

When you click the plugin, the readme you set up during the deployment phase appears here:

<figure><img src="/files/gT26RWJ1Eg3Mzymixqtv" alt="" width="563"><figcaption></figcaption></figure>

Click the *next* button to proceed with plugin creation. Fill in the plugin's required fields, then click *next* again:

<figure><img src="/files/Ay19KVpQWruJwdpsE5gw" alt="" width="563"><figcaption></figcaption></figure>

Once done, the plugin appears in your created plugins section:

<figure><img src="/files/YlYn3EkgKtWkRTuTrEJm" alt=""><figcaption></figcaption></figure>

That’s it for deploying a Device plugin. Next, learn how to use the device in its dedicated section:

{% content-ref url="/pages/kki7vejYw2OIfL8tg93m" %}
[Device](/plugins/using-the-plugins/device)
{% endcontent-ref %}


# Using the plugins

This section covers the use of plugins once they are deployed


# Cron and webhook

This page shows the process of using a Cron or Webhook plugin.

Go to *Devices* ⟶ *Plugins* in Ubidots, then click the *create new plugin* button:

<figure><img src="/files/YVgAY1bPTXeayAqlujUs" alt=""><figcaption></figcaption></figure>

The plugins drawer will pop up on the right side of the screen. Search for the Cron or Webhook plugin you recently created.

<figure><img src="/files/Q5r5T9G6TzKFdm9DKXVn" alt="" width="563"><figcaption></figcaption></figure>

When you click the plugin, the readme that you set up during the deployment phase appears here:

<figure><img src="/files/iPsd54aMVixxHxqyatRD" alt="" width="563"><figcaption></figcaption></figure>

Click the *next* button to proceed with the plugin creation. Fill in the required fields, then click the *next* button:

<figure><img src="/files/JcgwHAiwzW13zCKpu1hQ" alt="" width="563"><figcaption></figcaption></figure>

Once done, the plugin is displayed in your created plugins section and is ready to execute its logic.

<figure><img src="/files/ALVLXccLCmX9IBdd54Xu" alt=""><figcaption></figcaption></figure>


# Widget

This page depicts the process of using a Widget plugin.

Go to the dashboard where you want to create the widget and open the Widgets drawer. The widget you just created appears under the *Plugins* section:

<figure><img src="/files/0uynN84pSWP9uPcu3nuM" alt="" width="563"><figcaption></figcaption></figure>

After you click the widget, its properties — the elements set up in the *view\_widget.xml* file — appear here. Fill in the widget's required input parameters:

<figure><img src="/files/YXtJyfwCaV8hcPqKekoh" alt="" width="563"><figcaption></figcaption></figure>

That's it! Your widget is created on the dashboard:

<figure><img src="/files/GGKRw5RqFfnx6ZIcUrxD" alt=""><figcaption></figcaption></figure>


# Device

Go to *Devices* ⟶ *Devices* in Ubidots. Hover over *Create a device*, then click *Create a device*:

<figure><img src="/files/Z7XD4Qg6UP2qOM9AijYg" alt=""><figcaption></figcaption></figure>

Your newly created Device plugin appears in the drawer:

<figure><img src="/files/dSeBCdJqFx2zCdb0QKuV" alt="" width="563"><figcaption></figcaption></figure>




---

[Next Page](/llms-full.txt/1)

