The Things Stack Community Integration
The Things Stack Community (TTN) integration connects ThingsBoard to a TTN LoRaWAN network server: it receives uplink messages from LoRaWAN devices, decodes them via an uplink converter, and pushes telemetry and attributes to the platform. In the reverse direction, it encodes Rule Engine messages via a downlink converter and delivers them to devices through the TTN API.
The Things Stack Community setup
Section titled “The Things Stack Community setup”Register an application
Section titled “Register an application”- Go to the TTN console, open the Applications section, and press Add application.
- Fill in the required fields:
- Application ID — e.g.
thingsboard-connection - Handler registration — identifies the region where the application will be registered (e.g.
eu1for the EU region)
- Application ID — e.g.
- Press Create application.
Register a device
Section titled “Register a device”- In the application, open the Devices page and press Register device.
- Fill in the required fields:
- Device ID — e.g.
thermostat-a - Device EUI — press Generate to create a random identifier
- AppEUI — can be filled with zeros
- AppKey — press Generate to create a random identifier
- Device ID — e.g.
- Press Register.
Generate an API access key
Section titled “Generate an API access key”An access key is required to authenticate the ThingsBoard integration with TTN.
- In the TTN console, go to API keys in your application menu.
- Press Add API key, enter a name, and select the required permissions.
- Press Generate key and copy the key immediately — it will not be shown again.
ThingsBoard integration setup
Section titled “ThingsBoard integration setup”Create an uplink converter
Section titled “Create an uplink converter”The Things Stack Community integration uses a Typed converter. ThingsBoard automatically extracts and maps standard TTN fields — eui, fPort, decoded, location, signal metadata, and more — so the decoder script only needs to handle the device payload bytes. If your device is in the built-in catalog, use the Library tab — see Library — a built-in catalog of ready-to-use decoder functions for over 100 devices.
- Go to Integrations center ⇾ Data converters.
- Click + Add data converter ⇾ Create new converter.
- Set Converter type to Uplink (default).
- Select Integration type: The Things Stack Community.
- Enter a converter name:
The Things Stack Community Uplink Converter. - The Main decoding parameters section specifies:
- A naming template for automatic device creation —
Device $eui— change this if you prefer a different naming scheme. - Leave the decoder function at its default for this tutorial.
- A naming template for automatic device creation —
In Advanced decoding parameters, all fields are pre-populated for The Things Stack Community — device profile defaults to
$applicationId, label to$deviceId, and the telemetry, attributes, and update-only key lists are pre-filled with standard TTN metadata keys. Adjust as needed; see the Advanced decoding parameters reference for field descriptions.- Click Add.
The decoder function used in this tutorial:
/*** Decodes the incoming payload and returns a structured object containing telemetry data and attributes.** @param {byte[]} input - The raw payload received as an array of bytes.* @returns {Object} output - The structured output with decoded telemetry and attributes.*/
function decodePayload(input) {// Initialize the output object with empty attributes and telemetry for clarity.var result = { attributes: {}, telemetry: {}};
// Decode serial number (SN) from the first 4 bytes of the payload.// Press '?' icon in the top right corner to learn more about built in helper functions and capabilities.result.attributes.sn = parseBytesToInt(input, 0, 4);
// Extract the timestamp from metadata (represented in milliseconds).var timestamp = metadata.ts; // ts is the timestamp parsed from the incoming message's time, or returns the current time if it cannot be parsed.
// Initialize an object to store decoded key/value telemetry data.var values = {};
// Decode battery level from the 5th byte of the payload.values.battery = parseBytesToInt(input, 4, 1);
// Decode temperature from the 6th and 7th bytes of the payload (divided by 100).values.temperature = parseBytesToInt(input, 5, 2) / 100.0;
// Decode saturation from the 8th byte of the payload.values.saturation = parseBytesToInt(input, 7, 1);
// Combine the timestamp with values and add it to the telemetry.result.telemetry = {ts: timestamp,values: values};
// Return the fully constructed output object.return result;// Same logic, less code:// return {// attributes: {// sn: parseBytesToInt(input, 0, 4)// },// telemetry: {// ts: convertDateToTimestamp(extractDateFromMetadata()),// values: {// battery: parseBytesToInt(input, 4, 1),// temperature: parseBytesToInt(input, 5, 2) / 100.0,// saturation: parseBytesToInt(input, 7, 1)// }// }// };}
var result = decodePayload(payload);// Uncomment this code block to overwrite values set in the main configuration window. Useful if you extract device/asset/customer/group names from the payload;// result.type = 'DEVICE'; // Entity type allows you to choose type of created entity. Can be 'DEVICE' or 'ASSET'.// result.name = 'Temperature Sensor'; // Device or asset name (the value must be unique)// result.profile = 'IndustrialSensorProfile'; // Device or asset profile name.// result.customer = 'MyCustomer'; // If customer is not null - created entity will be assigned to customer with such name.// result.group = 'SensorsGroup'; // If group is not null - created entity will be added to the entity group with such name.
// Return the final result object.return result;/*** Decodes the incoming payload and returns a structured object containing telemetry data and attributes.** @param {number[]} input - The raw payload received as an array of bytes.* @returns {Object} output - The structured output with decoded telemetry and attributes.*/
function decodePayload(input) {// Initialize the output object with empty attributes and telemetry for clarity.var result = { attributes: {}, telemetry: {}};
// Decode serial number (SN) from the first 4 bytes of the payload.// Press '?' icon in the top right corner to learn more about built in helper functions and capabilities.result.attributes.sn = parseBytesToInt(input, 0, 4);
// Extract the timestamp from metadata (represented in milliseconds).var timestamp = metadata.ts; // ts is the timestamp parsed from the incoming message's time, or returns the current time if it cannot be parsed.
// Initialize an object to store decoded key/value telemetry data.var values = {};
// Decode battery level from the 5th byte of the payload.values.battery = parseBytesToInt(input, 4, 1);
// Decode temperature from the 6th and 7th bytes of the payload (divided by 100).values.temperature = parseBytesToInt(input, 5, 2) / 100.0;
// Decode saturation from the 8th byte of the payload.values.saturation = parseBytesToInt(input, 7, 1);
// Combine the timestamp with values and add it to the telemetry.result.telemetry = {ts: timestamp,values: values};
// Return the fully constructed output object.return result;// Same logic, less code:// return {// attributes: {// sn: parseBytesToInt(input, 0, 4)// },// telemetry: {// ts: convertDateToTimestamp(extractDateFromMetadata()),// values: {// battery: parseBytesToInt(input, 4, 1),// temperature: parseBytesToInt(input, 5, 2) / 100.0,// saturation: parseBytesToInt(input, 7, 1)// }// }// };}
var result = decodePayload(payload);// Uncomment this code block to overwrite values set in the main configuration window. Useful if you extract device/asset/customer/group names from the payload;// result.type = 'DEVICE'; // Entity type allows you to choose type of created entity. Can be 'DEVICE' or 'ASSET'.// result.name = 'Temperature Sensor'; // Device or asset name (the value must be unique)// result.profile = 'IndustrialSensorProfile'; // Device or asset profile name.// result.customer = 'MyCustomer'; // If customer is not null - created entity will be assigned to customer with such name.// result.group = 'SensorsGroup'; // If group is not null - created entity will be added to the entity group with such name.
// Return the final result object.return result;
/*** Parse a slice of bytes from an array into an integer (big-endian).** @param {number[]} input - The array of bytes.* @param {number} offset - The starting index.* @param {number} length - The number of bytes to convert.* @returns {number} - The resulting integer.*/function parseBytesToInt(input, offset, length) {var result = 0;for (var i = offset; i < offset + length; i++) {result = (result << 8) | (input[i] & 0xFF);}return result;}The decoder receives the raw binary payload (frm_payload) from TTN as a byte array. If your TTN application uses a payload formatter, decoded_payload will already be parsed JSON — in that case, access fields directly as a JSON object instead of using parseBytesToInt.
The decoder assumes a fixed binary payload structure. It reads byte ranges using parseBytesToInt(input, offset, length) and produces:
- Attribute —
sn: serial number from bytes 0–3 - Telemetry —
battery(byte 4),temperature(bytes 5–6, divided by 100),saturation(byte 7), all timestamped withmetadata.ts
| Bytes | Field | Output type | Notes |
|---|---|---|---|
| 0–3 | sn | attribute | Device serial number |
| 4 | battery | telemetry | Battery level |
| 5–6 | temperature | telemetry | Raw value ÷ 100 |
| 7 | saturation | telemetry | Saturation level |
Example payload (hex):
00BC614E5F092950Example output:
{ "attributes": { "sn": 12345678 }, "telemetry": { "ts": 1690000000000, "values": { "battery": 95, "temperature": 23.45, "saturation": 80 } }}To adapt this converter to your device:
- Different device name — change the Device name pattern in Main decoding configuration (e.g. replace
Device $euiwith$deviceIdor a fixed name). - Different byte layout — adjust the offset and length in each
parseBytesToInt()call to match your payload structure. - Different field names — rename
battery,temperature, orsaturationto match your data model; add or remove fields as needed. - TTN payload formatter — if your TTN application uses a payload formatter, the pre-parsed result is available as
decodedin the Telemetry list. In that case you can access fields directly as JSON in the decoder script instead of usingparseBytesToInt. - Additional attributes — add more keys to
result.attributes(e.g. firmware version, hardware revision).
The decoder script controls device sensor data — the fields written to result.attributes and result.telemetry.values. The Advanced decoding parameters control TTN network metadata (signal quality, location, LoRa settings) that ThingsBoard extracts automatically. You do not need to add your sensor field names to the Telemetry or Attributes lists there.
Create the integration
Section titled “Create the integration”- Go to Integrations center ⇾ Integrations and click + Add integration.
- Basic settings:
- Set Integration type to The Things Stack Community.
- Enable integration and Allow create devices or assets are on by default.
- Click Next.
- Uplink data converter:
- Select existing — choose a previously created
The Things Stack Community Uplink Converterfrom the list. - Click Next.
- Select existing — choose a previously created
- Downlink data converter:
- Click Skip — the downlink converter is only needed for sending commands to devices and can be added later.
- Connection settings:
- Region — the region where your TTN application is registered (e.g.
eu1). - Port —
8883. - Credentials:
- Username — your TTN Application ID with
@ttnappended (e.g.my-thingsboard@ttn). - Password — the API key generated in Generate an API access key step.
- Enable SSL and Use API v3 are on by default — leave them enabled.
- Region — the region where your TTN application is registered (e.g.
- Optionally click Check connection — the wizard advances to a verification step showing Connected when successful.
- Click Add to complete the integration setup.
Connection settings
Section titled “Connection settings”Host type
| Value | Description |
|---|---|
| Region | Connect to a standard TTN cluster. Enter the Region identifier (e.g. eu1) — ThingsBoard constructs the host as {region}.cloud.thethings.network. |
| Custom | Specify a fully custom broker hostname (e.g. for a private TTN deployment). |
Port
| Parameter | Default | Description |
|---|---|---|
| Port | 8883 | MQTT broker port. 8883 is the standard TLS port used by TTN. |
Credentials
| Parameter | Description |
|---|---|
| Username | The TTN Application ID (e.g. my-thingsboard). |
| Password | The API access key generated in your TTN application. |
Enable SSL
Encrypts the MQTT connection using TLS. Enabled by default and required for connections to TTN public clusters.
Use API v3
Switches the MQTT topic structure between TTN API v2 and v3.
| Value | Topic format |
|---|---|
| Enabled | v3/{appId}/devices/{devId}/up (TTN v3 topic format) |
| Disabled | +/devices/+/up (TTN v2 topic format) |
Topic filters
Defines which MQTT topics ThingsBoard subscribes to for uplink messages. The default filter is pre-configured based on the Use API v3 setting.
| Field | Description |
|---|---|
| Topic | MQTT topic filter. + matches a single topic level. Automatically set based on the API version. |
| QoS | 0 — at most once (fire and forget); 1 — at least once (may deliver duplicates); 2 — exactly once |
Execute remotely
When enabled, ThingsBoard generates an Integration key and Integration secret that allow external services to push messages into this integration via the Integration API.
Advanced settings
| Parameter | Default | Description |
|---|---|---|
| Protocol version | MQTT 3.1.1 | MQTT protocol version used for the broker connection. |
| Max bytes in message | 32368 | Maximum payload size in bytes. Messages exceeding this limit are dropped. |
| Connection timeout (sec) | 10 | Seconds ThingsBoard waits for a broker response before marking the connection as failed. |
| Downlink topic pattern | ${applicationId}/devices/${devId}/down | MQTT topic used for publishing downlink messages to TTN devices. ${applicationId} and ${devId} are resolved from the downlink converter output metadata. |
Test uplink
Section titled “Test uplink”Use the TTN console to simulate an uplink message and confirm that ThingsBoard receives and decodes it correctly.
- In the TTN console, go to the device thermostat-a ⇾ Messaging tab ⇾ Simulate uplink.
- In the Payload field, enter the test payload:
00BC614E5F092950
- Click Simulate uplink.
This payload matches the byte layout from the uplink converter: sn = 12345678, battery = 95, temperature = 23.45, saturation = 80.
After sending, go to Entities ⇾ Devices in ThingsBoard. A new device is automatically provisioned by the integration. Open it and check the Latest Telemetry tab — you should see temperature, battery, saturation, and other decoded fields.
Additional fields such as rssi, snr, and latitude come from the TTN network metadata, stored as telemetry via the Advanced decoding parameters configured in Create an uplink converter.
Configure and test downlink
Section titled “Configure and test downlink”To send a command to a TTN device from ThingsBoard, three things must be in place: a downlink converter attached to the integration, a Rule Chain node that forwards messages to that integration, and a trigger — in this tutorial, a shared attribute change.
Create a downlink converter
Section titled “Create a downlink converter”The downlink converter encodes ThingsBoard commands into TTN-compatible messages. For the full encoder reference, see Downlink data converter. The output must contain a downlinks array with f_port, frm_payload, and priority fields.
Expected output structure:
{ "downlinks": [{ "f_port": 2, "frm_payload": "vu8=", "priority": "NORMAL" }]}The result object must follow this format:
contentType— how data is encoded:TEXT,JSON, orBINARYdata— the actual payload sent to the device. See the TTN API documentation for details.metadata— must includedevIdto identify the target device in TTN
This converter reads the powerState field from the incoming ThingsBoard message and sends it as a base64-encoded payload to device thermostat-a.
Add the downlink converter to the existing integration:
- Go to Integrations center ⇾ Integrations and open the The Things Stack Community integration.
- Click Toggle edit mode.
- In the Downlink data converter field, click Create new.
- Enter converter name.
Paste the encoder function by copying it below:
var data = {downlinks: [{f_port: 2,confirmed: false,frm_payload: btoa(msg.powerState),priority: "NORMAL"}]};var result = {contentType: "JSON",data: JSON.stringify(data),metadata: {devId: 'thermostat-a'}};return result;var data = {downlinks: [{f_port: 2,confirmed: false,frm_payload: btoa(msg.powerState),priority: "NORMAL"}]};var result = {contentType: "JSON",data: JSON.stringify(data),metadata: {devId: 'thermostat-a'}};return result;- Click Add to save the converter, then click Apply changes to update the integration.
The encoder reads msg.powerState from the incoming message — this is the shared attribute you will create in the Test the downlink section. The result is base64-encoded and sent to device thermostat-a.
To adapt this converter to your device:
- Different device — replace
thermostat-aindevIdwith your device name. - Different command field — replace
msg.powerStatewith whichever attribute or RPC parameter carries the command value. - Different port — change
f_portto match the port your device listens on.
Configure the Root Rule Chain
Section titled “Configure the Root Rule Chain”Configure the Root Rule Chain to forward attribute changes to the TTN integration.
- Open Rule Chains ⇾ Root Rule Chain and click the edit icon.
- Find the Integration Downlink node in the node panel and drag it onto the canvas.
- Set the Name, select The Things Stack Community integration, and click Add.
- Connect the Message Type Switch node to the Integration Downlink node using the Attributes Updated relation. The Attributes Updated relation fires whenever a shared attribute is created or changed — this is what triggers the downlink.
- Click Apply changes to save the rule chain.
Test the downlink
Section titled “Test the downlink”Trigger a downlink by adding a shared attribute to the ThingsBoard device provisioned during the uplink test:
- Go to Entities ⇾ Devices, open the device provisioned by the integration, and click the Attributes tab.
- Switch to Shared attributes and click + to add a new attribute.
- Enter the key
powerStateand valueon, then click Add.
Adding the attribute triggers a downlink to TTN device thermostat-a with the powerState value encoded in the payload. Go to the TTN console and open thermostat-a ⇾ Live data — the downlink event appears in the list.