IoT Device Contribution Guide
Welcome to ThingsBoard IoT Hub. This guide walks you through building a device package that end users can install on their ThingsBoard instance in a single click.
A device package is a ZIP file that bundles everything the installation wizard needs: configuration forms, setup instructions, ThingsBoard entity templates (device profiles, dashboards, rule chains), and images. When a user selects your device in IoT Hub, the wizard runs through your package step-by-step to provision the device, create a dashboard, and show the user how to connect the hardware.
Before You Begin
Section titled “Before You Begin”Create a free Creator account on the ThingsBoard Creator Portal to start publishing your items.
How Users Install Your Device
Section titled “How Users Install Your Device”When a user clicks Install on your device in IoT Hub, the installation wizard performs these actions in order:
- Displays instructions from your markdown files (prerequisites, wiring, firmware setup).
- Collects user input via forms you define (device name, Wi-Fi credentials, Device EUI, etc.).
- Creates ThingsBoard entities from your templates (device profile, device, dashboard, rule chain, integration, gateway).
- Resolves
${variable}placeholders in subsequent files using form values, created entity IDs, and platform transport settings. - Shows post-install instructions with resolved variables (firmware code, verification steps, links to the created dashboard).
Your job as a creator is to describe all of this declaratively in device-info.json.
Quick Start
Section titled “Quick Start”The fastest way to start is to grab a sample package, edit a few fields, and upload. Pick the one whose connectivity matches your device:
Then:
- Edit
device-info.json— set your device’sname,vendor,description,hardwareType,connectivity, anduseCases. - Replace the entity templates (
device-profile.json,device.json,dashboard.json) with exports from your own ThingsBoard instance. Keep the${variable}placeholders. - Rewrite
prerequisites.mdandpost-install.mdfor your hardware. - Swap in your device photo under
images/. - Re-zip the contents (not the folder itself) and upload via IoT Hub → + Add new item.
See Package structure if your device supports multiple connectivity options.
The sections below are a complete reference for every part of a device package.
Package Structure
Section titled “Package Structure”Layout A — Single Install Method
Section titled “Layout A — Single Install Method”Use this layout when your device supports only one way of connecting. All files live at the ZIP root.
my-device.zip├── device-info.json (required — metadata + install steps)├── overview.md (optional — long description for the device detail page)├── images/│ ├── device-photo.jpg│ └── wiring-diagram.png├── prerequisites.md (shown before install)├── form.json (user input fields)├── device-profile.json (ThingsBoard entity template)├── device.json (ThingsBoard entity template)├── dashboard.json (ThingsBoard entity template)└── post-install.md (shown after install)Layout B — Multiple Install Methods
Section titled “Layout B — Multiple Install Methods”Use this layout when your device supports several ways of connecting (e.g. a LoRaWAN sensor that works with ChirpStack, TTN, and LORIOT). Shared files go in common/; method-specific files go in per-method subfolders.
my-device.zip├── device-info.json├── overview.md├── images/│ └── device-photo.jpg├── common/│ ├── form.json (shared fields: device name, Device EUI)│ ├── device-profile.json│ ├── device.json│ └── dashboard.json├── chirpstack/│ ├── prerequisites.md│ ├── form.json (ChirpStack-specific: server URL, API token)│ ├── converter.json│ ├── integration.json│ └── post-install.md├── ttn/│ ├── prerequisites.md│ ├── form.json (TTN-specific: region, app ID, API key)│ ├── converter.json│ ├── integration.json│ └── post-install.md└── loriot/ ├── prerequisites.md ├── form.json ├── converter.json ├── integration.json └── post-install.mdFolder Names for Install Methods
Section titled “Folder Names for Install Methods”When using Layout B, each install method has a conventional subfolder name. The folder name is referenced from the file and template paths in installSteps — you must match it exactly.
The folder name is purely a path reference — what matters is that the file and template values in installSteps correctly point to existing files in the ZIP. The conventions below avoid collisions when a single device package supports multiple install methods that share a protocol name (e.g. both DIRECT_MQTT and INTEGRATION_MQTT).
Direct transports
| Install Method | Recommended Folder |
|---|---|
DIRECT_HTTP | direct_http/ |
DIRECT_MQTT | direct_mqtt/ |
DIRECT_COAP | direct_coap/ |
DIRECT_LWM2M | direct_lwm2m/ |
DIRECT_SNMP | direct_snmp/ |
ThingsBoard IoT Gateway connectors
| Install Method | Recommended Folder |
|---|---|
GATEWAY_MQTT | gateway_mqtt/ |
GATEWAY_MODBUS | gateway_modbus/ |
GATEWAY_OPCUA | gateway_opcua/ |
GATEWAY_BACNET | gateway_bacnet/ |
GATEWAY_BLE | gateway_ble/ |
GATEWAY_CAN | gateway_can/ |
GATEWAY_FTP | gateway_ftp/ |
GATEWAY_KNX | gateway_knx/ |
GATEWAY_OCPP | gateway_ocpp/ |
GATEWAY_ODBC | gateway_odbc/ |
GATEWAY_REQUEST | gateway_request/ |
GATEWAY_REST | gateway_rest/ |
GATEWAY_SNMP | gateway_snmp/ |
GATEWAY_SOCKET | gateway_socket/ |
GATEWAY_XMPP | gateway_xmpp/ |
ChirpStack (CE)
| Install Method | Recommended Folder |
|---|---|
CHIRPSTACK | chirpstack/ |
ThingsBoard PE integrations
| Install Method | Recommended Folder |
|---|---|
INTEGRATION_APACHE_PULSAR | apache_pulsar/ |
INTEGRATION_AWS_IOT | aws_iot/ |
INTEGRATION_AWS_KINESIS | aws_kinesis/ |
INTEGRATION_AWS_SQS | aws_sqs/ |
INTEGRATION_AZURE_EVENT_HUB | azure_event_hub/ |
INTEGRATION_AZURE_IOT_HUB | azure_iot_hub/ |
INTEGRATION_AZURE_SERVICE_BUS | azure_service_bus/ |
INTEGRATION_CHIRPSTACK | chirpstack/ |
INTEGRATION_COAP | integration_coap/ |
INTEGRATION_CUSTOM | custom/ |
INTEGRATION_HTTP | integration_http/ |
INTEGRATION_IOT_CREATORS | iot_creators/ |
INTEGRATION_KAFKA | kafka/ |
INTEGRATION_KPN_THINGS | kpn_things/ |
INTEGRATION_LORIOT | loriot/ |
INTEGRATION_MQTT | integration_mqtt/ |
INTEGRATION_OPC_UA | integration_opc_ua/ |
INTEGRATION_PARTICLE | particle/ |
INTEGRATION_PUB_SUB | pub_sub/ |
INTEGRATION_RABBITMQ | rabbitmq/ |
INTEGRATION_REMOTE | remote/ |
INTEGRATION_SIGFOX | sigfox/ |
INTEGRATION_TCP | integration_tcp/ |
INTEGRATION_THINGPARK | thingpark/ |
INTEGRATION_THINGPARK_ENTERPRISE | thingpark_enterprise/ |
INTEGRATION_TTI | tti/ |
INTEGRATION_TTN | ttn/ |
INTEGRATION_TUYA | tuya/ |
INTEGRATION_UDP | integration_udp/ |
Shared Files in common/
Section titled “Shared Files in common/”Any file referenced from multiple install methods should live in common/ to avoid duplication:
"installSteps": { "CHIRPSTACK": [ {"type": "SHOW_FORM", "name": "Configuration", "file": "common/form.json"}, {"type": "DEVICE_PROFILE", "name": "My Sensor", "template": "common/device-profile.json"}, ... ], "INTEGRATION_TTN": [ {"type": "SHOW_FORM", "name": "Configuration", "file": "common/form.json"}, {"type": "DEVICE_PROFILE", "name": "My Sensor", "template": "common/device-profile.json"}, ... ]}device-info.json Manifest
Section titled “device-info.json Manifest”The device-info.json file at the ZIP root is the single source of truth for your package. The wizard reads it first and uses it to drive everything else.
{ "name": "My Temperature Sensor", "description": "Wi-Fi temperature sensor with MQTT connectivity.", "vendor": "Acme IoT", "hardwareType": "Sensor", "connectivity": ["Wi-Fi", "MQTT"], "useCases": ["Environment Monitoring", "Smart Office"], "image": "images/device-photo.jpg", "installMethods": ["DIRECT_MQTT"], "installSteps": { "DIRECT_MQTT": [ {"type": "SHOW_INSTRUCTION", "name": "Prerequisites", "file": "prerequisites.md"}, {"type": "SHOW_FORM", "name": "Configuration", "file": "form.json"}, {"type": "DEVICE_PROFILE", "name": "Acme Temperature Sensor", "template": "device-profile.json"}, {"type": "DEVICE", "name": "${deviceName}", "template": "device.json"}, {"type": "DASHBOARD", "name": "Temperature Sensor Monitor", "template": "dashboard.json"}, {"type": "SHOW_INSTRUCTION", "name": "Setup Complete", "file": "post-install.md"} ] }}Required Fields
Section titled “Required Fields”| Field | Type | Description |
|---|---|---|
name | string | Device model name as shown on the marketplace card |
description | string | One-sentence description (max 512 chars) shown on the card |
vendor | string | Manufacturer name |
hardwareType | string | One value from Hardware types |
installMethods | string[] | One or more values from Install methods |
installSteps | object | Ordered steps per install method — keys must match installMethods |
Optional Fields
Section titled “Optional Fields”| Field | Type | Description |
|---|---|---|
connectivity | string[] | Tags from Connectivity tags — used for browse filtering |
useCases | string[] | Tags from Use cases — used for browse filtering |
image | string | Path to the device photo in the ZIP (e.g. images/device.png) |
productURL | string | Manufacturer’s product page URL — backs ${product.button} (see Documentation links) |
datasheetURL | string | Datasheet URL — backs ${datasheet.button} (see Documentation links) |
Critical Rules
Section titled “Critical Rules”- Every key in
installStepsmust exactly match a value ininstallMethods. - All
fileandtemplatepaths are relative to the ZIP root, not todevice-info.json. - The
nameonDEVICE_PROFILEandDASHBOARDsteps must be specific to your device (e.g."ESP32 PICO KIT", not"Default";"ESP32 PICO KIT Monitor", not"Check and control device data dashboard"). Users see these names in their ThingsBoard instance. - The
DEVICEstep name should use${deviceName}so the device is named from the form input.
Hardware Types
Section titled “Hardware Types”Pick the one that best describes your device. Allowed values: Actuator, AI Accelerator, Analyzer, Camera, Controller, Data Logger, Development Board, Display, Gateway, Meter, PLC, Relay, Sensor, Single Board Computer, Tracker.
If your device doesn’t fit any of these, pick the closest match and contact support to request a new type.
Connectivity Tags
Section titled “Connectivity Tags”List every physical interface and protocol your device supports. The more accurate your tags, the easier users can find your device when browsing.
Wireless: Wi-Fi, Bluetooth, LoRaWAN, Zigbee, Z-Wave, Thread, NB-IoT, LTE-M, Sigfox, NFC, 2G, 3G, 4G, 5G, 6LoWPAN, DigiMesh, UWB.
Wired: Ethernet, RS485, RS232, USB, UART, SPI, I2C, CAN, 1-Wire, 4-20mA, 0-10V, M-Bus, IO-Link, IrDA, Power Line Communication, SDI-12.
Protocols: MQTT, HTTP, HTTPS, CoAP, REST, SNMP, Modbus RTU, Modbus TCP, OPC-UA, BACnet, KNX, WebSocket, TCP/IP, UDP, CANopen, DNP3, EtherCAT, Foundation Fieldbus, HART, IEC 61850, PROFINET, Wireless M-Bus.
Use Cases
Section titled “Use Cases”Tag your device with all applicable IoT use cases. These are shared across the whole marketplace (widgets, dashboards, devices), so users browsing by use case will find your device alongside matching content.
Allowed values: Air Quality Monitoring, Asset Tracking, Cold Chain, Drones, Environment Monitoring, Fleet Tracking, Health Care, Industrial Automation, Predictive Maintenance, Robotics, SCADA, Smart Building, Smart City, Smart Energy, Smart Farming, Smart Home, Smart Metering, Smart Office, Smart Retail, Solar Monitoring, Tank Level Monitoring, Waste Management.
Install Methods
Section titled “Install Methods”Choose one or more methods that describe how the device connects to ThingsBoard. Each method you list must have a matching entry in installSteps.
Direct transports — device opens a transport connection straight to the platform.
| Value | Description | CE | PE |
|---|---|---|---|
DIRECT_HTTP | Device connects directly via HTTP | yes | yes |
DIRECT_MQTT | Device connects directly via MQTT | yes | yes |
DIRECT_COAP | Device connects directly via CoAP | yes | yes |
DIRECT_LWM2M | Device connects directly via LwM2M | yes | yes |
DIRECT_SNMP | Device connects directly via SNMP | yes | yes |
ThingsBoard IoT Gateway connectors — device talks to a ThingsBoard IoT Gateway, which forwards to the platform. One value per Gateway connector.
| Value | Description | CE | PE |
|---|---|---|---|
GATEWAY_MQTT | Gateway MQTT connector | yes | yes |
GATEWAY_MODBUS | Gateway Modbus connector | yes | yes |
GATEWAY_OPCUA | Gateway OPC-UA connector | yes | yes |
GATEWAY_BACNET | Gateway BACnet connector | yes | yes |
GATEWAY_BLE | Gateway BLE connector | yes | yes |
GATEWAY_CAN | Gateway CAN connector | yes | yes |
GATEWAY_FTP | Gateway FTP connector | yes | yes |
GATEWAY_KNX | Gateway KNX connector | yes | yes |
GATEWAY_OCPP | Gateway OCPP connector | yes | yes |
GATEWAY_ODBC | Gateway ODBC connector | yes | yes |
GATEWAY_REQUEST | Gateway Request connector | yes | yes |
GATEWAY_REST | Gateway REST connector | yes | yes |
GATEWAY_SNMP | Gateway SNMP connector | yes | yes |
GATEWAY_SOCKET | Gateway Socket connector | yes | yes |
GATEWAY_XMPP | Gateway XMPP connector | yes | yes |
ChirpStack (CE-compatible)
| Value | Description | CE | PE |
|---|---|---|---|
CHIRPSTACK | ChirpStack LoRaWAN integration (CE-compatible) | yes | yes |
ThingsBoard PE integrations — uses the platform’s Integrations subsystem. PE only
| Value | Description | CE | PE |
|---|---|---|---|
INTEGRATION_APACHE_PULSAR | Apache Pulsar integration | no | yes |
INTEGRATION_AWS_IOT | AWS IoT integration | no | yes |
INTEGRATION_AWS_KINESIS | AWS Kinesis integration | no | yes |
INTEGRATION_AWS_SQS | AWS SQS integration | no | yes |
INTEGRATION_AZURE_EVENT_HUB | Azure Event Hub integration | no | yes |
INTEGRATION_AZURE_IOT_HUB | Azure IoT Hub integration | no | yes |
INTEGRATION_AZURE_SERVICE_BUS | Azure Service Bus integration | no | yes |
INTEGRATION_CHIRPSTACK | ChirpStack integration (PE) | no | yes |
INTEGRATION_COAP | CoAP integration | no | yes |
INTEGRATION_CUSTOM | Custom integration | no | yes |
INTEGRATION_HTTP | HTTP integration | no | yes |
INTEGRATION_IOT_CREATORS | IoT Creators (Deutsche Telekom) integration | no | yes |
INTEGRATION_KAFKA | Apache Kafka integration | no | yes |
INTEGRATION_KPN_THINGS | KPN Things integration | no | yes |
INTEGRATION_LORIOT | LORIOT LoRaWAN network server | no | yes |
INTEGRATION_MQTT | MQTT integration | no | yes |
INTEGRATION_OPC_UA | OPC-UA integration | no | yes |
INTEGRATION_PARTICLE | Particle integration | no | yes |
INTEGRATION_PUB_SUB | Google Pub/Sub integration | no | yes |
INTEGRATION_RABBITMQ | RabbitMQ integration | no | yes |
INTEGRATION_REMOTE | Remote integration | no | yes |
INTEGRATION_SIGFOX | Sigfox integration | no | yes |
INTEGRATION_TCP | TCP integration | no | yes |
INTEGRATION_THINGPARK | ThingPark Wireless integration | no | yes |
INTEGRATION_THINGPARK_ENTERPRISE | ThingPark Enterprise integration | no | yes |
INTEGRATION_TTI | The Things Industries integration | no | yes |
INTEGRATION_TTN | The Things Stack / The Things Network | no | yes |
INTEGRATION_TUYA | Tuya integration | no | yes |
INTEGRATION_UDP | UDP integration | no | yes |
Install Steps and Forms
Section titled “Install Steps and Forms”Steps are executed top-to-bottom. The wizard shows an instruction or form to the user, creates a ThingsBoard entity, or both. Output from earlier steps (form values, created entity IDs) becomes available as ${variable} placeholders in later steps.
SHOW_INSTRUCTION — Display Markdown
Section titled “SHOW_INSTRUCTION — Display Markdown”Shows a markdown page to the user. Use for prerequisites, setup guides, wiring instructions, and post-install verification.
{"type": "SHOW_INSTRUCTION", "name": "Prerequisites", "file": "prerequisites.md"}SHOW_FORM — Collect User Input
Section titled “SHOW_FORM — Collect User Input”Shows a dynamic form. Every field’s key becomes a ${key} variable in all subsequent steps.
{"type": "SHOW_FORM", "name": "Device Settings", "file": "form.json"}Entity Steps — Create ThingsBoard Resources
Section titled “Entity Steps — Create ThingsBoard Resources”Each entity step takes a template (a JSON file that matches the ThingsBoard REST API format for that entity) and creates the entity. The created entity’s ID, name, and other properties become available as variables (${device.id}, ${dashboard.name}, etc.).
| Step type | Creates | Reuses existing by name? |
|---|---|---|
DEVICE_PROFILE | Device profile | yes |
DEVICE | Device | no (always new) |
GATEWAY | Gateway device | no (always new) |
GATEWAY_CONNECTOR | Gateway connector configuration | appended to gateway |
DASHBOARD | Dashboard | no (always new) |
RULE_CHAIN | Rule chain | yes |
UPLINK_CONVERTER | Uplink converter PE only | no |
DOWNLINK_CONVERTER | Downlink converter PE only | no |
INTEGRATION | Integration PE only | no |
Converter / integration ordering. Any UPLINK_CONVERTER or DOWNLINK_CONVERTER step must appear before the INTEGRATION step in the same install method’s array. The validator rejects packages that put converters after the integration.
This ordering is what makes the wiring automatic: the wizard runs the converter steps first, captures their generated IDs as ${uplinkConverter.id} / ${uplinkConverter.name} and ${downlinkConverter.id} / ${downlinkConverter.name}, then resolves those placeholders in the integration template before creating the integration. The creator does not need to wire converter IDs by hand. Referencing ${uplinkConverter.id} and ${downlinkConverter.id} in the integration template is enough; the wizard fills them in at install time.
"installSteps": { "INTEGRATION_TTN": [ {"type": "SHOW_FORM", "name": "TTN Settings", "file": "ttn/form.json"}, {"type": "DEVICE_PROFILE", "name": "Acme Sensor", "template": "common/device-profile.json"}, {"type": "UPLINK_CONVERTER", "name": "Acme Decoder", "template": "ttn/uplink.json"}, {"type": "DOWNLINK_CONVERTER", "name": "Acme Encoder", "template": "ttn/downlink.json"}, {"type": "INTEGRATION", "name": "${deviceName} - TTN", "template": "ttn/integration.json"}, {"type": "DEVICE", "name": "${deviceName}", "template": "common/device.json"}, {"type": "DASHBOARD", "name": "Monitor", "template": "common/dashboard.json"}, {"type": "SHOW_INSTRUCTION", "name": "Setup Complete", "file": "ttn/post-install.md"} ]}For the recommended way to author the converter and integration files, see Integration packages below.
Optional fields on entity steps.
| Field | Applies to | Description |
|---|---|---|
serverAttributes | All entity types | Path to a JSON file with key-value pairs saved as server attributes |
sharedAttributes | All entity types | Path to a JSON file with key-value pairs saved as shared attributes |
credentials | DEVICE, GATEWAY | Path to a JSON file with device credentials (see Device credentials) |
dockerCompose | GATEWAY | Path to a YAML template for a custom docker-compose file (see Gateway Docker Compose templates) |
Form files are JSON arrays of field definitions. Every key becomes a ${key} variable after the user submits the form.
Field Types
Section titled “Field Types”[ { "key": "deviceName", "label": "Device Name", "type": "STRING", "defaultValue": "My Sensor", "required": true, "helpText": "Name for the device in ThingsBoard" }, { "key": "wifiPassword", "label": "WiFi Password", "type": "PASSWORD", "required": true }, { "key": "baudRate", "label": "Baud Rate", "type": "SELECT", "defaultValue": "9600", "options": [ {"value": "9600", "label": "9600"}, {"value": "115200", "label": "115200"} ] }, { "key": "loriotServer", "label": "Loriot server", "type": "STRING_AUTOCOMPLETE", "defaultValue": "eu1", "options": ["eu1", "us1", "as1", "eu2", "ap1"] }, { "key": "enableTls", "label": "Enable TLS", "type": "BOOLEAN", "defaultValue": true }, { "key": "port", "label": "Port", "type": "INTEGER", "defaultValue": 1883 }]| Type | Renders as | Value type |
|---|---|---|
STRING | Text input | string |
PASSWORD | Password input with visibility toggle | string |
INTEGER | Number input | integer |
BOOLEAN | Checkbox | boolean |
SELECT | Dropdown — value picked from options[].value | string |
STRING_AUTOCOMPLETE | Free-text input with autocomplete suggestions from options | string |
Field Properties
Section titled “Field Properties”| Property | Description |
|---|---|
key | Variable name (used as ${key} in later steps) |
label | Label shown next to the field |
type | One of the field types above |
required | If true, the user cannot proceed without a value |
defaultValue | Pre-filled value shown in the field |
helpText | Text shown when the user clicks the help icon |
helpImage | Image path shown alongside helpText |
options | SELECT: array of {value, label} entries. STRING_AUTOCOMPLETE: array of plain strings |
validators | Array of regex validators (see below) |
secretSupport | Boolean. If true, the install dialog renders both a plaintext input and a Secret picker so the user can choose to back the value with a ThingsBoard Secret |
secretType | TEXT or TEXT_FILE. Used by the Secret picker to filter eligible Secrets. Default: TEXT |
group | Optional section header. Fields with the same group value render under one heading on the install form |
randomGenerator | STRING/PASSWORD only: if true, shows a regenerate icon that fills the field with a random value |
randomSize | STRING/PASSWORD only: length of the generated value (default: 20) |
randomByDefault | STRING/PASSWORD only: if true, the field is pre-filled with a random value when the form opens (overrides defaultValue) |
Reserved keys. These keys are populated by step output and cannot be used as form field keys:
uplinkConverter.id, uplinkConverter.name, downlinkConverter.id, downlinkConverter.name, integration.id, integration.name, integration.httpEndpoint. The validator rejects packages that try to define them.
Validators
Section titled “Validators”Add regex validators with custom error messages:
{ "key": "devEui", "label": "Device EUI", "type": "STRING", "required": true, "validators": [ {"pattern": "^[0-9A-Fa-f]{16}$", "message": "Must be exactly 16 hex characters"} ]}Multiple validators can be applied to one field — all must pass for the form to be valid.
Integration Packages
Section titled “Integration Packages”ThingsBoard PE generates a complete integration package via the Export to IoT Hub button on the integration detail page. The exported ZIP is self-describing: drop the files into your device package and let the install wizard substitute placeholders at install time. Hand-authoring the integration JSON works, but exporting from a real PE integration is the canonical path because it produces the right shape and naming on the first try.
File Layout
Section titled “File Layout”For PE integration install methods (INTEGRATION_LORIOT, INTEGRATION_TTN, INTEGRATION_AWS_IOT, etc.), the package places three files alongside the device-side files:
| File | Purpose |
|---|---|
integration.json | Integration entity JSON, verbatim from PE’s exporter, with ${formKey}, ${secret:NAME;type:TEXT}, and ${alias.prop} placeholders |
uplink.json | Uplink converter, verbatim from the exporter |
downlink.json | Downlink converter, verbatim from the exporter |
The INTEGRATION step references just one template; the wizard’s SHOW_FORM step still drives the combined form.json:
{"type": "INTEGRATION", "name": "LORIOT Integration", "template": "integration.json"}The integration type comes from the type field at the top of integration.json. The validator rejects packages with a missing or unknown integration type.
Placeholder Conventions Inside integration.json
Section titled “Placeholder Conventions Inside integration.json”The exporter emits three kinds of placeholders. Keep them verbatim when copying the export into a package.
| Placeholder | Meaning |
|---|---|
${formKey} | Non-secret form value. The install dialog substitutes the user’s form.json input directly into the body. Examples: ${integrationName}, ${loriotServer}, ${loriotDomain} |
${secret:NAME;type:TEXT|TEXT_FILE} | Sensitive value. Preserved verbatim in the body. The install dialog creates a Secret named NAME from the user’s form input via POST /api/secret, and ThingsBoard PE resolves the placeholder when the integration starts |
${alias.prop} | Output from an earlier install step. The wizard captures step outputs and substitutes them into later steps. Examples: ${uplinkConverter.id}, ${downlinkConverter.id}, ${deviceProfile.id} |
Form keys that drive a ${secret:…} placeholder receive an 8-character alphanumeric suffix at export time (like loriotToken3KYhD5Wl) to prevent Secret name collisions across separate exports. Do not rename these keys — the placeholder in integration.json and the key field in form.json must match exactly.
Combining Device Fields With Integration Fields
Section titled “Combining Device Fields With Integration Fields”The wizard renders one form per SHOW_FORM step. Combine the device-side fields and the integration-side fields into a single form.json, and use group to keep the visual sections from the exporter intact:
[ { "key": "deviceName", "label": "Device Name", "type": "STRING", "defaultValue": "Acme LoRaWAN Sensor", "required": true }, { "key": "integrationName", "label": "Integration name", "type": "STRING", "defaultValue": "Acme LoRaWAN Sensor - LoRaWAN", "required": true, "group": "LORIOT Connection" }, { "key": "loriotServer", "label": "LORIOT server", "type": "STRING_AUTOCOMPLETE", "defaultValue": "eu1", "options": ["eu1", "us1", "as1", "eu2", "ap1"], "required": true, "group": "LORIOT Connection" }, { "key": "loriotAppId", "label": "Application ID", "type": "STRING", "secretSupport": true, "secretType": "TEXT", "required": true, "group": "LORIOT Connection" }, { "key": "loriotToken", "label": "Application Access Token", "type": "PASSWORD", "secretSupport": true, "secretType": "TEXT", "required": true, "group": "LORIOT Connection" }]Migration Recipe (Existing Pre-Export Packages)
Section titled “Migration Recipe (Existing Pre-Export Packages)”For each device package using the older split-artifacts format (integration-template.json + integration-form.json):
- Configure a working integration of the same type in PE, then click Export to IoT Hub on the integration detail page. Save the ZIP and unpack it.
- Delete the old
integration-template.jsonandintegration-form.json. Drop the export’sintegration.jsoninto the package directory unchanged. - Replace
uplink_data_converter.jsonwith the export’suplink.json. Replacedownlink_data_converter.jsonwith the export’sdownlink.json(if the package supports downlink). - Merge
form.json. Keep the device-side entries (likedeviceName) at the top, then append the export’s entries. Usegroupfields to preserve the visual layout. - Update
device-info.json. TheINTEGRATIONstep now references a single template (noformfield). Converter steptemplatepaths point atuplink.jsonanddownlink.json(no_data_convertersuffix). - Smoke-test the package end-to-end via the install dialog. Page 1 shows the combined form with grouped sections, page 2 provisions the entities, and page 3 confirms success.
Entity Templates
Section titled “Entity Templates”Entity templates are JSON files that match the ThingsBoard REST API format for that entity. Export an entity from ThingsBoard, add ${variable} placeholders for any values that must be dynamic, and save the result.
Device Profile Template
Section titled “Device Profile Template”{ "name": "Acme Temperature Sensor", "type": "DEFAULT", "transportType": "MQTT", "description": "Device profile for Acme Temperature Sensor with MQTT transport", "profileData": { "configuration": {"type": "DEFAULT"}, "transportConfiguration": { "type": "MQTT", "deviceTelemetryTopic": "v1/devices/me/telemetry", "deviceAttributesTopic": "v1/devices/me/attributes" } }}Device Template
Section titled “Device Template”Always reference the created device profile via ${deviceProfile.id}:
{ "name": "${deviceName}", "type": "My Sensor Type", "label": "${deviceName}", "deviceProfileId": { "id": "${deviceProfile.id}", "entityType": "DEVICE_PROFILE" }}Dashboard Template
Section titled “Dashboard Template”Export a dashboard from ThingsBoard (Dashboards → Export), then replace any hardcoded device UUIDs in entity aliases with ${device.id}:
{ "title": "Acme Temperature Sensor Monitor", "configuration": { "entityAliases": { "device_alias": { "alias": "Device", "filter": { "type": "singleEntity", "singleEntity": { "id": "${device.id}", "entityType": "DEVICE" } } } } }}Non-String Template Variables
Section titled “Non-String Template Variables”For boolean and integer values, use bare ${variable} (no quotes):
{ "enableTls": ${enableTls}, "port": ${mqtt.port}}For string values inside quotes, use ${variable} as normal:
{ "name": "${deviceName}", "baseUrl": "${chirpstackUrl}"}Device Credentials
Section titled “Device Credentials”By default, ThingsBoard auto-generates an access token for each new device, available as ${device.token}. This works for most use cases and requires no credentials file.
To override the default, add a credentials field to the DEVICE or GATEWAY step pointing to a credentials JSON file:
{ "type": "DEVICE", "name": "${deviceName}", "template": "device.json", "credentials": "credentials.json"}MQTT Basic Credentials
Section titled “MQTT Basic Credentials”{ "credentialsType": "MQTT_BASIC", "credentialsValue": { "clientId": "${deviceName}", "userName": "${mqttUsername}", "password": "${mqttPassword}" }}Access Token (Explicit)
Section titled “Access Token (Explicit)”{ "credentialsType": "ACCESS_TOKEN", "credentialsValue": "${customToken}"}X.509 Certificate
Section titled “X.509 Certificate”{ "credentialsType": "X509_CERTIFICATE", "credentialsValue": "${deviceCertificate}"}Gateway Docker Compose Templates
Section titled “Gateway Docker Compose Templates”By default, ${gateway.downloadButton} in post-install markdown generates a standard docker-compose.yml from the ThingsBoard backend with the gateway’s host, port, and access token pre-filled.
To provide a custom template with additional ports, volumes, environment variables, or credentials, add a dockerCompose field to the GATEWAY step:
{ "type": "GATEWAY", "name": "${deviceName} Gateway", "template": "gateway.json", "dockerCompose": "docker-compose.yml"}Template File
Section titled “Template File”The template is a standard YAML file with ${variable} placeholders. All variables (form values, transport config, entity outputs) are resolved after the gateway is created:
services: tb-gateway: image: thingsboard/tb-gateway:3.7-stable restart: always ports: - "5026:5026" # Modbus TCP - "5000:5000" # REST connector extra_hosts: - "host.docker.internal:host-gateway" environment: - host=${mqtt.host} - port=${mqtt.port} - accessToken=${gateway.token} volumes: - tb-gw-config:/thingsboard_gateway/config - tb-gw-logs:/thingsboard_gateway/logs - tb-gw-extensions:/thingsboard_gateway/extensions
volumes: tb-gw-config: tb-gw-logs: tb-gw-extensions:When to Use a Custom Template
Section titled “When to Use a Custom Template”- Your gateway needs extra ports (e.g. Modbus TCP, BACnet, Socket connector).
- You need additional volumes or bind mounts.
- You want custom environment variables beyond
host,port, andaccessToken. - Your gateway uses MQTT Basic credentials and needs to pass
clientId,userName,password.
MQTT Basic Credentials Example
Section titled “MQTT Basic Credentials Example”services: tb-gateway: image: thingsboard/tb-gateway:3.7-stable environment: - host=${mqtt.host} - port=${mqtt.port} - clientId=${mqttClientId} - username=${mqttUsername} - password=${mqttPassword}How It Works
Section titled “How It Works”When a dockerCompose field is present on the GATEWAY step:
- After the gateway is created, the template file is read from the ZIP.
- All
${variable}placeholders are resolved. ${gateway.downloadButton}in post-install markdown serves this custom file as a direct download.- No backend API call is made — the file is generated entirely in the browser.
When no dockerCompose field is set, the download button falls back to the standard ThingsBoard-generated docker-compose.yml.
Post-Install Example
Section titled “Post-Install Example”## Launch Gateway
Download the configuration file and start the gateway:
${gateway.downloadButton}
Then run in the same directory:
```bashdocker compose up -d```
The gateway will connect to **${mqtt.host}:${mqtt.port}**.Instructions and Images
Section titled “Instructions and Images”Instruction files use standard Markdown with two extensions: ${variable} placeholders and image-gallery helpers.
Headings, Lists, and Notes
Section titled “Headings, Lists, and Notes”## Prerequisites
Before you begin, make sure you have:
- **ESP32 Dev Kit** board- **USB cable** (micro-USB)- **Arduino IDE** installed
### Step 1: Connect the Board
Connect the board to your computer via USB as shown:

> **Note:** Make sure the USB cable supports data transfer, not just charging.Using Variables in Markdown
Section titled “Using Variables in Markdown”Variables are resolved before rendering. Use them to show user-provided values, created resource names, and platform settings:
## Setup Complete
Your device **${deviceName}** is ready!
Use this token in your firmware:
${device.token}
Open the [${dashboard.name}](${dashboard.url}) dashboard to see telemetry.
The device is connected to **${mqtt.host}:${mqtt.port}** via MQTT.See the full Variable reference for all available variables.
Variables Inside Code Blocks
Section titled “Variables Inside Code Blocks”Variables are resolved inside fenced code blocks, which is especially useful for firmware snippets:
```cpp#define WIFI_SSID "${wifiSsid}"#define WIFI_PASSWORD "${wifiPassword}"#define TOKEN "${device.token}"#define TB_SERVER "${mqtt.host}"#define TB_PORT ${mqtt.port}```curl -o docker-compose.yml "${gateway.dockerComposeUrl}"docker compose up -dAdding Images to the ZIP
Section titled “Adding Images to the ZIP”Place images anywhere in the ZIP — the images/ folder is recommended.
my-device.zip├── images/│ ├── device-photo.jpg│ ├── wiring-diagram.png│ └── serial-output.png├── prerequisites.md└── post-install.mdSupported formats: .png, .jpg, .jpeg, .gif, .svg.
Referencing Images in Markdown
Section titled “Referencing Images in Markdown”Use standard Markdown with paths relative to the ZIP root:
The wizard resolves these paths to inline base64 data URIs from the ZIP — no external hosting needed.
Image Gallery
Section titled “Image Gallery”Display multiple images side-by-side as clickable thumbnails using the gallery helper:
${images.gallery(images/step1-tools.png, images/step2-upload.png, images/step3-verify.png)}- Paths are comma-separated, relative to the ZIP root.
- Images render as a horizontal row of thumbnails.
- Clicking a thumbnail expands it full-size; clicking again collapses.
- Useful for multistep visual instructions (e.g. IDE menu navigation screenshots).
Images as Form Field Help
Section titled “Images as Form Field Help”Form fields can show a help image alongside the help text:
{ "key": "devEui", "label": "Device EUI", "type": "STRING", "required": true, "helpText": "16 hex characters from the device label", "helpImage": "images/device-label-eui.png"}The user clicks a help icon next to the field to expand the image.
Image Best Practices
Section titled “Image Best Practices”- Keep images under 500 KB each — the whole ZIP is stored as a single file.
- PNG for screenshots and diagrams; JPG for device photos.
- Crop tightly to the relevant area.
- Annotate with arrows or highlights to point out important UI elements.
- Use descriptive filenames:
chirpstack-api-key.png, notscreenshot1.png.
Readme Content
Section titled “Readme Content”overview.md is an optional file at the ZIP root that provides the full description for your device’s detail page on IoT Hub. It complements the short description in device-info.json:
descriptionindevice-info.json→ shown on browse cards (short, max 512 chars).overview.md→ shown on the device detail page; pre-fills the “Readme” step in the upload wizard.
Use it to describe hardware specifications, features, and any context a user needs before deciding to install. Standard Markdown is supported, including tables and images.
## ESP32 Dev Kit C V4
The ESP32 Dev Kit C V4 is a compact development board built on the ESP-WROOM-32U module,featuring a dual-core processor with Wi-Fi and Bluetooth connectivity.
### Features
- Dual-core Xtensa LX6 processor (240 MHz)- 520 KB SRAM, 4 MB Flash- Wi-Fi 802.11 b/g/n + Bluetooth 4.2 + BLE- 34 programmable GPIO pins- Multiple interfaces: SPI, I2C, UART, ADC, DAC, PWM
### Specifications
| Parameter | Value ||-----------|-------|| Operating Voltage | 3.3V || Input Voltage | 5V (USB) || Clock Speed | 80–240 MHz || Flash | 4 MB |Variable Reference
Section titled “Variable Reference”Variables are resolved at the time of step execution and are available in all subsequent markdown files, entity templates, and form help text.
From Form Fields
Section titled “From Form Fields”Every form field key is exposed as ${key}. Common examples:
| Example | Typical use |
|---|---|
${deviceName} | Device name entered by the user |
${wifiSsid} | Wi-Fi network name |
${wifiPassword} | Wi-Fi password |
${devEui} | LoRaWAN Device EUI |
${chirpstackUrl} | ChirpStack server URL |
${mqttUsername} | MQTT Basic username |
${mqttPassword} | MQTT Basic password |
From Created Entities
Section titled “From Created Entities”| Variable | Step type | Description |
|---|---|---|
${deviceProfile.id} | DEVICE_PROFILE | Created profile UUID |
${deviceProfile.name} | DEVICE_PROFILE | Profile name |
${device.id} | DEVICE | Created device UUID |
${device.name} | DEVICE | Device name |
${device.token} | DEVICE | Device access token |
${device.url} | DEVICE | Link to the device page in ThingsBoard |
${gateway.id} | GATEWAY | Gateway UUID |
${gateway.name} | GATEWAY | Gateway name |
${gateway.token} | GATEWAY | Gateway access token |
${gateway.downloadButton} | GATEWAY | Download button for docker-compose.yml |
${dashboard.id} | DASHBOARD | Dashboard UUID |
${dashboard.name} | DASHBOARD | Dashboard name |
${dashboard.url} | DASHBOARD | Link to the dashboard |
${ruleChain.id} | RULE_CHAIN | Rule chain UUID |
${uplinkConverter.id} | UPLINK_CONVERTER | Uplink converter UUID PE only . Auto-substituted into the integration template. |
${uplinkConverter.name} | UPLINK_CONVERTER | Uplink converter name |
${downlinkConverter.id} | DOWNLINK_CONVERTER | Downlink converter UUID PE only . Auto-substituted into the integration template. |
${downlinkConverter.name} | DOWNLINK_CONVERTER | Downlink converter name |
${integration.id} | INTEGRATION | Integration UUID PE only |
${integration.name} | INTEGRATION | Integration name PE only |
${integration.httpEndpoint} | INTEGRATION | The webhook URL the integration listens on
PE only
. Reference it in post-install.md to display the URL the customer registers with their LoRaWAN provider, MQTT broker, etc. |
Transport Connectivity
Section titled “Transport Connectivity”These come from the ThingsBoard platform settings of the installing instance:
| Variable | Default | Description |
|---|---|---|
${mqtt.host} | from baseUrl | MQTT broker host |
${mqtt.port} | 1883 | MQTT broker port |
${mqtts.port} | 8883 | MQTTS (TLS) port |
${http.host} | from baseUrl | HTTP API host |
${http.port} | 8080 | HTTP API port |
${coap.host} | from baseUrl | CoAP host |
${coap.port} | 5683 | CoAP port |
Documentation Links
Section titled “Documentation Links”Two placeholders render as inline buttons that link to the device’s manufacturer resources. They work in overview.md and in any SHOW_INSTRUCTION markdown.
| Variable | Source field in device-info.json | Renders as |
|---|---|---|
${product.button} | productURL | Button labeled {name} Product page |
${datasheet.button} | datasheetURL | Button labeled {name} Datasheet |
If the corresponding URL is not set, the placeholder is silently dropped — so it is safe to leave them in your markdown even when only one URL is provided.
## Device Technical Documentation
Download datasheets and manuals for the FMC130.
${product.button} ${datasheet.button}Pre-Upload Checklist
Section titled “Pre-Upload Checklist”Before uploading your ZIP, verify each item below.
device-info.json
Section titled “device-info.json”Entity Names
Section titled “Entity Names”Templates
Section titled “Templates”Instructions and Code
Section titled “Instructions and Code”Images
Section titled “Images”Multiple Install Methods
Section titled “Multiple Install Methods”Listing
Section titled “Listing”Readme
Section titled “Readme”Content Quality
Section titled “Content Quality”Writing the Changelog
Section titled “Writing the Changelog”When you upload a new version, pair the version bump with a changelog entry — a short note that tells users exactly what changed since the previous version, so they can decide whether, and how, to upgrade.
Document What’s New
Section titled “Document What’s New”Summarize what changed since the previous version: new features, bug fixes, breaking changes, and any migration notes users need to know.
Group your entry under these headings, and include only the ones that apply:
| Heading | What goes here |
|---|---|
| New features | New capabilities, configuration options, or behavior added in this version |
| Bug fixes | Defects corrected since the previous version — describe the symptom users saw, not the internal cause |
| Breaking changes | Anything that changes existing behavior in a way that can disrupt an installed copy — renamed keys, removed options, changed defaults |
| Migration notes | The concrete steps an existing user must take to move from the previous version to this one |
Write for the User
Section titled “Write for the User”- Lead with the user impact. Describe what the user can now do, or what no longer breaks — not how you implemented it.
- Be specific. Name the exact keys, fields, settings, or outputs that changed. “Renamed the output key from
temptotemperature” is actionable; “improved naming” is not. - One change per bullet. Keep each item to a single, scannable line.
- Flag breaking changes loudly. If an upgrade can disrupt an installed copy, say so explicitly and pair it with a migration note.
Pair every entry with a semantic version bump — patch (1.0.1) for fixes, minor (1.1.0) for backward-compatible features, major (2.0.0) for breaking changes.
Changelog Example
Section titled “Changelog Example”## 2.0.0
### Breaking changes- Renamed the output key from `temp` to `temperature` to match the ThingsBoard telemetry convention.
### Migration notes- Update any dashboards, alarm rules, or downstream calculated fields that read the `temp` key to read `temperature` instead.
### New features- Added an optional `humidity` argument; when present, the formula now also computes a `dewPoint` output.
### Bug fixes- Fixed missing output when the input telemetry arrived as a string instead of a number.After You Submit
Section titled “After You Submit”Once you complete the upload wizard and click Submit, your version enters the IoT Hub review queue. The ThingsBoard team checks every submission before it goes live.
Tracking Your Submission
Section titled “Tracking Your Submission”To see the current status of your submission, open the Creator Portal and go to Items. Find your item in the list and click the Manage Versions icon in its row. The Versions page lists every version you have uploaded with a Status column that updates in real time.
| Status | Meaning |
|---|---|
| Pending Review | Your version is in the review queue and has not been evaluated yet |
| Approved | Your version passed review and is now live on IoT Hub |
| Rejected | Your version did not pass review — see the reviewer comment for details |
What Reviewers Check
Section titled “What Reviewers Check”Reviewers verify that the submission meets the same criteria as the Pre-Upload Checklist: the package installs cleanly, entity templates are correct, instructions are complete and accurate, images are present, and the listing and readme give users enough context to evaluate and use the device.
If Your Submission Is Rejected
Section titled “If Your Submission Is Rejected”The Status column will show Rejected. Open the version details to read the reviewer’s comment, which explains specifically what needs to be fixed.
To resubmit:
- Fix the reported issues in your local package.
- Return to Items → Manage Versions for your item.
- Click + Upload new version and complete the wizard with the corrected package.
See Also
Section titled “See Also”- IoT Device Library — how end users discover and install a device package
Was this helpful?