Skip to content
Stand with Ukraine flag

Widget API Reference

Structured reference for the ThingsBoard widget API. Use this page to look up specific APIs after completing one of the widget development tutorials.

Every widget has access to self.ctx — the widget context object. This is your gateway to data, settings, services, and platform APIs.

PropertyTypeDescription
ctx.dataDatasourceData[]Array of data objects, one per data key. Updated on each data refresh.
ctx.datasourcesDatasource[]Configured datasources with entity info and data keys.
ctx.settingsobjectWidget settings defined by the settings schema.
ctx.unitsstringUnits configured on the widget.
ctx.decimalsnumberDecimal precision configured on the widget.
ctx.defaultSubscriptionIWidgetSubscriptionThe active data subscription. Contains timewindow config, alarm data, and more.
ctx.widgetWidgetThe widget configuration object (config, layout, datasources).
ctx.dashboardWidgetDashboardWidgetDashboard-level widget wrapper (title, visibility, styles).
ctx.widgetConfigWidgetConfigShorthand for ctx.widget.config — title, colors, fonts, margins.
ctx.$containerjQueryjQuery reference to the widget’s root DOM element.
ctx.$scopeobjectAngular scope for template bindings. Set properties here to use in HTML templates.
ctx.$scope.$injectorInjectorAngular dependency injector for accessing platform services.
ctx.$scope.fbFormBuilderAngular Reactive Forms builder for creating form controls.
ctx.servicesMapMap<string, string>Maps service names to injection tokens. Use with $injector.get().
ctx.controlApiRpcApiRPC control API for sending device commands (Control widgets only).
ctx.subscriptionApiSubscriptionApiAPI for creating and managing custom data subscriptions.
ctx.timewindowFunctionsTimewindowFunctionsFunctions to manipulate the widget’s time window.
ctx.stateControllerStateControllerDashboard state controller for navigation and URL state.
ctx.dashboardDashboardCtxDashboard context (edit mode, white-labeling, etc.).
ctx.detectChanges()functionTriggers Angular change detection. Call after modifying $scope properties.

Define these on self in the JavaScript tab:

Called once when the widget initializes. Use for:

  • Reading settings (self.ctx.settings)
  • Injecting services (self.ctx.$scope.$injector.get(...))
  • Setting up initial scope variables
  • Creating form controls
  • Registering event listeners
self.onInit = function() {
let $injector = self.ctx.$scope.$injector;
let utils = $injector.get(self.ctx.servicesMap.get('utils'));
let settings = utils.deepClone(self.ctx.settings) || {};
self.ctx.$scope.myValue = settings.defaultValue || 0;
};

Called whenever the subscription receives new data. For Latest Values widgets this fires when a new data point arrives; for Time-Series widgets it fires on each aggregation interval.

self.onDataUpdated = function() {
let data = self.ctx.data;
if (data.length > 0 && data[0].data.length > 0) {
self.ctx.$scope.value = data[0].data[0][1];
}
self.ctx.detectChanges();
};

Called when the widget container changes size (browser resize, dashboard layout change). Use for resizing charts or recalculating layouts.

self.onResize = function() {
if (myChart) {
myChart.resize();
}
};

Called when the dashboard enters or exits edit mode. Useful for hiding controls during editing.

Called when the dashboard switches between desktop and mobile layout.

Called when the widget is removed. Clean up timers, chart instances, and event listeners.

self.onDestroy = function() {
if (myChart) {
myChart.clear();
}
};

Returns an object that configures widget behavior in the platform. Called once during initialization.

self.typeParameters = function() {
return {
maxDatasources: 1,
singleEntity: true,
maxDataKeys: 4,
dataKeysOptional: false,
datasourcesOptional: false,
hasDataPageLink: false,
ignoreDataUpdateOnIntervalTick: true,
embedTitlePanel: false,
defaultDataKeysFunction: function() {
return [{ name: 'temperature', label: 'Temperature', type: 'timeseries' }];
}
};
};
ParameterTypeDefaultDescription
maxDatasourcesnumber-1 (unlimited)Maximum number of datasources the user can configure
singleEntitybooleanfalseRestrict to a single entity
maxDataKeysnumber-1 (unlimited)Maximum number of data keys per datasource
dataKeysOptionalbooleanfalseAllow widget without data keys
datasourcesOptionalbooleanfalseAllow widget without any datasource
hasDataPageLinkbooleanfalseEnable server-side pagination
warnOnPageDataOverflowbooleantrueShow warning when data exceeds page size
ignoreDataUpdateOnIntervalTickbooleanfalseSkip onDataUpdated calls triggered by interval timer (not new data)
embedTitlePanelbooleanfalseMake widgetTitlePanel template available in HTML
embedActionsPanelbooleanfalseMake widgetActionsPanel template available in HTML
hasAdditionalLatestDataKeysbooleanfalseAllow additional latest-value data keys alongside time-series keys
previewWidthstring’100%‘Widget preview width in the editor
previewHeightstring’100%‘Widget preview height in the editor
defaultDataKeysFunctionfunctionnullReturns default data keys for new widget instances
dataKeySettingsFunctionfunctionnullReturns per-data-key settings schema

Declares the clickable action sources your widget exposes. Users configure actions (navigate to dashboard state, open URL, etc.) for each source in the widget settings.

self.actionSources = function() {
return {
'rowClick': {
name: 'widget-action.row-click',
multiple: false
},
'actionCellButton': {
name: 'widget-action.action-cell-button',
multiple: true,
hasShowCondition: true
}
};
};
PropertyDescription
nameDisplay name shown in the widget actions dialog
multipletrue allows multiple actions; false allows only one
hasShowConditiontrue enables a show/hide condition per action

To trigger an action from your code:

let descriptors = self.ctx.$scope.ctx.actionsApi
.getActionDescriptors('rowClick');
if (descriptors.length) {
self.ctx.$scope.ctx.actionsApi.handleWidgetAction(
null, // event index
descriptors[0], // action descriptor
entityId, // { entityType, id }
entityName // string
);
}

Each entry in self.ctx.data represents one data key:

{
dataKey: {
name: 'temperature', // key name
label: 'Temperature', // display label
type: 'timeseries', // 'timeseries' | 'attribute' | 'entityField'
units: '°C', // configured units (may be empty)
decimals: 1 // configured decimal precision
},
data: [
[1710000000000, 24.5], // [timestamp_ms, value]
[1710000060000, 25.1] // for time-series: multiple points
]
}

For Latest Values widgets, data typically has one entry (the most recent). For Time-Series widgets, data contains all points in the time window.

{
type: 'entity', // 'entity' | 'function'
entity: {
id: { entityType: 'DEVICE', id: '...' },
name: 'My Device'
},
entityId: '...',
entityName: 'My Device',
entityType: 'DEVICE',
dataKeys: [ /* DataKey objects */ ]
}

For Alarm widgets, alarms are on the subscription:

let alarms = self.ctx.defaultSubscription.alarms;
// alarms.data = [ AlarmInfo, AlarmInfo, ... ]
// alarms.totalElements = 42
// alarms.totalPages = 5

Each AlarmInfo object:

{
id: { id: '...', entityType: 'ALARM' },
type: 'High Temperature',
severity: 'CRITICAL', // CRITICAL | MAJOR | MINOR | WARNING | INDETERMINATE
status: 'ACTIVE_UNACK', // ACTIVE_UNACK | ACTIVE_ACK | CLEARED_UNACK | CLEARED_ACK
createdTime: 1710000000000,
startTs: 1710000000000,
endTs: 1710000060000,
ackTs: 0, // 0 = not acknowledged
clearTs: 0, // 0 = not cleared
originator: { entityType: 'DEVICE', id: '...' },
originatorName: 'My Device',
details: { /* custom JSON */ }
}

self.ctx.defaultSubscription holds the active subscription:

PropertyTypeDescription
dataDatasourceData[]Same as self.ctx.data
datasourcesDatasource[]Same as self.ctx.datasources
timeWindowConfigobjectCurrent time window configuration
legendDataobjectLegend entries and visibility state
alarmsobjectAlarm page data (alarm widgets only)
update()functionForce a subscription data refresh

The settings schema defines the configuration form users see in the widget settings dialog. Use the new-style flat array format — a JSON array of field objects.

{
"id": "fieldId",
"name": "Display Name",
"group": "Group Name",
"type": "text",
"default": "default value",
"required": false,
"condition": "return model.otherField == true;"
}
PropertyDescription
idThe settings key — accessed in JavaScript as self.ctx.settings.fieldId
nameLabel shown in the settings dialog
groupGroups fields under a collapsible header
typeField type (see table below)
defaultDefault value for new widget instances
requiredWhether the field must be filled
conditionJavaScript expression — field is shown only when this returns true. Access other fields via model.fieldId.
TypeRendersValue type
textSingle-line text inputstring
numberNumber inputnumber
passwordPassword input (masked)string
textareaMulti-line text inputstring
switchToggle switchboolean
selectDropdown selectstring
radiosRadio button groupstring
datetimeDate/time pickernumber (ms timestamp)
colorColor pickerstring (hex/rgb)
color_settingsAdvanced color picker with constant/range/function modesobject
fontFont picker (family, size, weight, style, line-height)object
unitsUnits selectorstring
iconIcon pickerstring (icon name)
cssSizeCSS size input (value + unit suffix)string
imageImage uploaderstring (URL/data URI)
jsonJSON editorobject
htmlHTML editorstring
cssCSS editorstring
javascriptJavaScript editorstring
markdownMarkdown editorstring
fieldsetCollapsible group container — wraps child fields
arrayRepeatable list of field groups — each item shares the same sub-fieldsarray
htmlSectionStatic HTML block rendered inside the formstring

Accepts a rows property to control the visible height:

{ "id": "note", "name": "Note", "type": "textarea", "rows": 4 }

Accepts a direction property ("row" or "column") to control button layout. Defaults to "row".

{ "id": "alignment", "name": "Alignment", "type": "radios", "direction": "column" }

Each option in the group is defined by an items array (same shape as select options).

OptionValuesDescription
dateTimeType"date" | "time" | "datetime"Controls which parts of the picker are shown. Defaults to "datetime".
allowClearbooleanShows a clear (×) button to reset the value.
{
"id": "deadline",
"name": "Deadline",
"type": "datetime",
"dateTimeType": "date",
"allowClear": true
}

The color_settings type renders an advanced color picker with three modes:

  • Constant — a single static color
  • Range — define value ranges with {from, to, color} entries
  • Function — a JavaScript function that receives value and returns a color string

Default structure:

{
"type": "constant",
"color": "#000",
"rangeList": {
"range": [
{ "from": 0, "to": 20, "color": "#2196f3" },
{ "from": 20, "to": 30, "color": "#4caf50" },
{ "from": 30, "to": null, "color": "#f44336" }
]
},
"colorFunction": "if (value > 30) return 'red';\nreturn 'green';"
}

Renders a font picker. The value is an object with the following shape:

{
"size": 12,
"sizeUnit": "px",
"family": "Roboto",
"weight": "normal",
"style": "normal",
"lineHeight": "1"
}

Use the full object as the default value:

{
"id": "labelFont",
"name": "Label font",
"type": "font",
"default": { "size": 14, "sizeUnit": "px", "family": "Roboto", "weight": "500", "style": "normal", "lineHeight": "1.2" }
}

Renders a dynamic list where users can add, remove, and reorder items. Each item is defined by the same set of sub-fields, specified in an items array inside the field definition. Access the value in JavaScript as a standard array of objects.

{
"id": "thresholds",
"name": "Thresholds",
"type": "array",
"items": [
{ "id": "label", "name": "Label", "type": "text" },
{ "id": "value", "name": "Value", "type": "number", "default": 0 },
{ "id": "color", "name": "Color", "type": "color", "default": "#f44336" }
]
}

In JavaScript, self.ctx.settings.thresholds will be an array of { label, value, color } objects.

Use the condition property to show/hide fields dynamically:

[
{
"id": "displayIcon",
"type": "switch",
"default": true
},
{
"id": "icon",
"type": "icon",
"condition": "return model.displayIcon == true;"
}
]

When displayIcon is off, the icon field is hidden from the settings dialog.

Create additional data subscriptions at runtime using self.ctx.subscriptionApi.createSubscription().

let options = {
type: 'timeseries', // or 'latest', 'alarm'
datasources: [{
type: 'entity',
dataKeys: [{ name: 'temperature', type: 'timeseries' }],
entityFilter: {
type: 'singleEntity',
singleEntity: { entityType: 'DEVICE', id: deviceId }
}
}],
callbacks: {
onDataUpdated: () => { self.onDataUpdated(); }
}
};
self.ctx.subscriptionApi.createSubscription(options, true)
.subscribe(subscription => {
// Replace default subscription
self.ctx.defaultSubscription = subscription;
self.ctx.data = subscription.data;
self.ctx.defaultSubscription.update();
});
Filter typeDescriptionKey properties
singleEntityOne specific entitysingleEntity: { entityType, id }
entityNameEntities matching a name patternentityType, entityNameFilter
deviceTypeDevices of a specific typedeviceType, deviceNameFilter
entityListExplicit list of entity IDsentityType, entityList: [id1, id2]

For the full list of entity filter types, refer to the ThingsBoard TypeScript source.

When replacing self.ctx.defaultSubscription, remember to:

  1. Unsubscribe from the previous custom subscription
  2. Copy timeWindowConfig from the old subscription
  3. Call subscription.update() after assignment
  4. If embedding a chart widget, toggle its visibility to force re-initialization
if (self.ctx.$scope.customSubscription) {
self.ctx.$scope.customSubscription.unsubscribe();
}
self.ctx.$scope.customSubscription =
self.ctx.subscriptionApi.createSubscription(options, true)
.subscribe(subscription => {
subscription.timeWindowConfig =
self.ctx.defaultSubscription.timeWindowConfig;
self.ctx.defaultSubscription = subscription;
self.ctx.data = subscription.data;
self.ctx.defaultSubscription.update();
});

Access services via the Angular injector:

let $injector = self.ctx.$scope.$injector;
let service = $injector.get(self.ctx.servicesMap.get('serviceName'));
Service nameDescription
utilsUtility functions (deepClone, etc.)
entityRelationServiceQuery and manage entity relations
attributeServiceRead/write entity attributes
deviceServiceDevice CRUD operations
assetServiceAsset CRUD operations
customerServiceCustomer CRUD operations
userServiceUser CRUD operations
alarmServiceAlarm operations (ack, clear, query)
entityServiceGeneric entity operations
subscriptionApiCreate custom data subscriptions
broadcastServiceInter-widget communication events
translatei18n translation service
entityGroupServiceEntity group operations PE only

Read attributes:

let attributeService = $injector.get(
self.ctx.servicesMap.get('attributeService'));
attributeService.getEntityAttributes(
{ entityType: 'DEVICE', id: deviceId },
'SERVER_SCOPE',
['firmware', 'model']
).subscribe(attributes => {
// attributes = [{ key, lastUpdateTs, value }, ...]
});

Save attributes:

attributeService.saveEntityAttributes(
{ entityType: 'DEVICE', id: deviceId },
'SERVER_SCOPE',
[{ key: 'firmware', value: '2.0' }]
).subscribe(() => {
// saved
});

Query relations:

let relationService = $injector.get(
self.ctx.servicesMap.get('entityRelationService'));
let query = {
filters: [{
relationType: 'Contains',
entityTypes: []
}],
parameters: {
rootId: entityId,
rootType: 'ASSET',
direction: 'FROM',
maxLevel: 1
}
};
relationService.findInfoByQuery(query)
.subscribe(relations => {
// relations = [{ from, to, fromName, toName, type }, ...]
});

Control the widget’s time window programmatically:

// Set a fixed time window
self.ctx.timewindowFunctions.onUpdateTimewindow(
startTimestamp, endTimestamp
);

Manage dashboard navigation state:

let controller = self.ctx.$scope.ctx.stateController;
// Read current state parameters
let params = controller.getStateParams();
// Update state
controller.updateState(stateValue, params);
// Navigate to previous state
controller.navigatePrevState(1);

Send and receive events between widgets on the same dashboard:

let broadcastService = $injector.get(
self.ctx.servicesMap.get('broadcastService'));
// Listen for events
broadcastService.on('my-event', (event, data) => {
// handle event
self.ctx.detectChanges();
});

Events are typically sent via widget actions configured in the dashboard. A common use case is the dark mode toggle:

broadcastService.on('toggle-dark-mode', () => {
// Switch chart colors, update styles, etc.
});