Skip to content

Cookie preferences

We use cookies for our own analytics, to see how our campaigns perform and, if you allow it, to load content from other services such as the Google site search. We never sell your data. Necessary cookies keep the site working and cannot be switched off. See our Cookie Policy for details and our Privacy Policy for how we handle personal data.

Security, load balancing and remembering your cookie choice.

Show us which pages people read and how they find the site, so we can improve it (Google Analytics).

Show us which of our ad campaigns bring visitors to the site and remember the campaign or partner link you arrived from (Google Ads, partner program). We do not use these cookies to build advertising profiles.

Load the site search from Google when you use it. Google sets its own cookies and shows ads in the search results.

© 2026 The ThingsBoard Authors
Try for free

ThingsBoard Cloud

Choose your data region

Your data stays in the region you choose, for residency and compliance. No credit card required.

Rather run it yourself? Install on your own servers

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.


Create a free Creator account on the ThingsBoard Creator Portal to start publishing your items.


When a user clicks Install on your device in IoT Hub, the installation wizard performs these actions in order:

  1. Displays instructions from your markdown files (prerequisites, wiring, firmware setup).
  2. Collects user input via forms you define (device name, Wi-Fi credentials, Device EUI, etc.).
  3. Creates ThingsBoard entities from your templates (device profile, device, dashboard, rule chain, integration, gateway).
  4. Resolves ${variable} placeholders in subsequent files using form values, created entity IDs, and platform transport settings.
  5. 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.

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:

  1. Edit device-info.json — set your device’s name, vendor, description, hardwareType, connectivity, and useCases.
  2. Replace the entity templates (device-profile.json, device.json, dashboard.json) with exports from your own ThingsBoard instance. Keep the ${variable} placeholders.
  3. Rewrite prerequisites.md and post-install.md for your hardware.
  4. Swap in your device photo under images/.
  5. 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.

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)

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.md

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 (without platform integrations)

Install Method Recommended Folder
CHIRPSTACK chirpstack/

ThingsBoard platform 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/
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/

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"},
...
]
}

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"}
]
}
}
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
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)
  • Every key in installSteps must exactly match a value in installMethods.
  • All file and template paths are relative to the ZIP root, not to device-info.json.
  • The name on DEVICE_PROFILE and DASHBOARD steps 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 DEVICE step name should use ${deviceName} so the device is named from the form input.

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 categories, contact us or email iothub@thingsboard.io to request a new hardware type.

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.

Tag your device with all applicable IoT use cases. These are shared across the whole marketplace (widgets, 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.

Choose one or more methods that describe how the device connects to ThingsBoard. Each method you list must have a matching entry in installSteps.

The last two columns of each table show compatibility with installations that run releases before ThingsBoard 4.4, when Community Edition (CE) and Professional Edition (PE) were separate. From 4.4 they are merged into one platform, so the PE or 4.4+ column also covers every ThingsBoard 4.4 and later installation.

Direct transports — device opens a transport connection straight to the platform.

Value Description CE before 4.4 PE or 4.4+
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 before 4.4 PE or 4.4+
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 (without platform integrations)

Value Description CE before 4.4 PE or 4.4+
CHIRPSTACK ChirpStack LoRaWAN integration yes yes

ThingsBoard platform integrations — uses the platform’s Integrations subsystem. PE or 4.4+

Value Description CE before 4.4 PE or 4.4+
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 platform integration 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

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.

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"}

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 or 4.4+ no
DOWNLINK_CONVERTER Downlink converter PE or 4.4+ no
INTEGRATION Integration PE or 4.4+ 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.

[
{
"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
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.

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.

ThingsBoard 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 integration is the canonical path because it produces the right shape and naming on the first try.

For platform 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 the ThingsBoard 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 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):

  1. Configure a working integration of the same type in ThingsBoard, then click Export to IoT Hub on the integration detail page. Save the ZIP and unpack it.
  2. Delete the old integration-template.json and integration-form.json. Drop the export’s integration.json into the package directory unchanged.
  3. Replace uplink_data_converter.json with the export’s uplink.json. Replace downlink_data_converter.json with the export’s downlink.json (if the package supports downlink).
  4. Merge form.json. Keep the device-side entries (like deviceName) at the top, then append the export’s entries. Use group fields to preserve the visual layout.
  5. Update device-info.json. The INTEGRATION step now references a single template (no form field). Converter step template paths point at uplink.json and downlink.json (no _data_converter suffix).
  6. 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 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.

{
"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"
}
}
}

Always reference the created device profile via ${deviceProfile.id}:

{
"name": "${deviceName}",
"type": "My Sensor Type",
"label": "${deviceName}",
"deviceProfileId": {
"id": "${deviceProfile.id}",
"entityType": "DEVICE_PROFILE"
}
}

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"
}
}
}
}
}
}

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}"
}

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"
}
{
"credentialsType": "MQTT_BASIC",
"credentialsValue": {
"clientId": "${deviceName}",
"userName": "${mqttUsername}",
"password": "${mqttPassword}"
}
}
{
"credentialsType": "ACCESS_TOKEN",
"credentialsValue": "${customToken}"
}
{
"credentialsType": "X509_CERTIFICATE",
"credentialsValue": "${deviceCertificate}"
}

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"
}

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:

version: '3.4'
services:
tb-gateway:
image: thingsboard/tb-gateway:3.8-stable
container_name: tb-gateway
restart: always
# Ports bindings - required by some connectors
ports:
- "5000:5000" # Comment if you don't use REST connector and change if you use another port
# Uncomment and modify the following ports based on connector usage:
# - "1052:1052" # BACnet connector
# - "5026:5026" # Modbus TCP connector (Modbus Slave)
# - "50000:50000/tcp" # Socket connector with type TCP
# - "50000:50000/udp" # Socket connector with type UDP
# Necessary mapping for Linux
extra_hosts:
- "host.docker.internal:host-gateway"
# Environment variables
environment:
- TB_GW_HOST=${mqtt.host}
- TB_GW_PORT=${mqtt.port}
- TB_GW_SECURITY_TYPE=accessToken
- TB_GW_ACCESS_TOKEN=${gateway.token}
volumes:
- tb-gw-config:/thingsboard_gateway/config
- tb-gw-logs:/thingsboard_gateway/logs
- tb-gw-extensions:/thingsboard_gateway/extensions
# Volumes declaration for configurations, extensions and configuration
volumes:
tb-gw-config:
name: tb-gw-config
tb-gw-logs:
name: tb-gw-logs
tb-gw-extensions:
name: tb-gw-extensions
  • 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, and accessToken.
  • Your gateway uses MQTT Basic credentials and needs to pass clientId, userName, password.
version: '3.4'
services:
tb-gateway:
image: thingsboard/tb-gateway:3.8-stable
container_name: tb-gateway
# Environment variables
environment:
- TB_GW_HOST=${mqtt.host}
- TB_GW_PORT=${mqtt.port}
- TB_GW_SECURITY_TYPE=usernamePassword
- TB_GW_CLIENT_ID=${mqttClientId}
- TB_GW_USERNAME=${mqttUsername}
- TB_GW_PASSWORD=${mqttPassword}

When a dockerCompose field is present on the GATEWAY step:

  1. After the gateway is created, the template file is read from the ZIP.
  2. All ${variable} placeholders are resolved.
  3. ${gateway.downloadButton} in post-install markdown serves this custom file as a direct download.
  4. 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.

## Launch Gateway
Download the configuration file and start the gateway:
${gateway.downloadButton}
Then run in the same directory:
```bash
docker compose up -d
```
The gateway will connect to **${mqtt.host}:${mqtt.port}**.

Instruction files use standard Markdown with two extensions: ${variable} placeholders and image-gallery helpers.

## 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:
![Wiring Diagram](images/wiring-diagram.png)
> **Note:** Make sure the USB cable supports data transfer, not just charging.

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 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}
```
Terminal window
curl -o docker-compose.yml "${gateway.dockerComposeUrl}"
docker compose up -d

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.md

Supported formats: .png, .jpg, .jpeg, .gif, .svg.

Use standard Markdown with paths relative to the ZIP root:

![Wiring Diagram](images/wiring-diagram.png)

The wizard resolves these paths to inline base64 data URIs from the ZIP — no external hosting needed.

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).

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.

  • 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, not screenshot1.png.

Screenshots are a different thing from the images inside the ZIP described above. Those illustrate the install instructions; screenshots are the gallery shown on your device’s detail page in IoT Hub, and they live on the listing rather than in the package. They are optional, but they let a user see the device and its dashboard before deciding to install.

Upload them in the Screenshots step of the new-item and new-version wizards, or later from the Screenshots tab of the version drawer. The gallery leads with the preview image and then follows your screenshots in the order you arrange them, so the picture users clicked in from is still the first thing they see. Any image in it opens full screen when clicked.

Requirements.

  • At least 1280 px wide; 1600×900 is recommended
  • 16:9 is the aspect ratio the gallery frame is built for — other ratios are letterboxed, not cropped
  • PNG or JPG
  • Up to 2 MB per file
  • Up to 8 screenshots per listing

Best practices.

  • Order them so the one that best explains the device comes first — it is the slide right after the preview image
  • Cover both sides of the package: the hardware itself and the dashboard it installs
  • Use real telemetry, not an empty dashboard

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:

  • description in device-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 |

Variables are resolved at the time of step execution and are available in all subsequent markdown files, entity templates, and form help text.

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
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 or 4.4+ . Auto-substituted into the integration template.
${uplinkConverter.name} UPLINK_CONVERTER Uplink converter name
${downlinkConverter.id} DOWNLINK_CONVERTER Downlink converter UUID PE or 4.4+ . Auto-substituted into the integration template.
${downlinkConverter.name} DOWNLINK_CONVERTER Downlink converter name
${integration.id} INTEGRATION Integration UUID PE or 4.4+
${integration.name} INTEGRATION Integration name PE or 4.4+
${integration.httpEndpoint} INTEGRATION The webhook URL the integration listens on PE or 4.4+ . Reference it in post-install.md to display the URL the customer registers with their LoRaWAN provider, MQTT broker, etc.

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

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}

Before uploading your ZIP, verify each item below.

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.

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
  • 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 temp to temperature” 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.

## 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.

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.

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
Published Your version passed review and is now live on IoT Hub
Rejected Your version did not pass review — see the reviewer comment for details

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.

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:

  1. Fix the reported issues in your local package.
  2. Return to Items → Manage Versions for your item.
  3. Click + Upload new version and complete the wizard with the corrected package.