Node-RED + ChirpStack: Build LoRaWAN Flows over MQTT

Wire ChirpStack into Node-RED over MQTT: subscribe to the uplink topic, read msg.payload.object in a function node, then route to a DB, dashboard or downlink.

LoRaWAN
Architecture

The data flow

Node-RED turns ChirpStack uplinks into low-code flows: subscribe to the MQTT event topic, read the decoded values from msg.payload.object in a function node, then fan out to a database, a dashboard or a downlink. This guide shows the full path with a small importable flow.

Sensor / controller

Measures or controls in the field and sends LoRaWAN uplinks.

LoRaWAN gateway

Receives the radio packets and forwards them to the server.

ChirpStack

Network server: manages sessions and decodes the payload.

ThingsBoard / Grafana

Dashboards, alarms, rules and reports.

What Node-RED adds on top of ChirpStack

ChirpStack already terminates the LoRaWAN session and, with a codec on the device profile, hands you decoded JSON. Node-RED is the low-code glue layer after that: it lets you wire an uplink to a database insert, a dashboard gauge, an alert or a downlink without writing a service. The connection between the two is plain MQTT, so there is no special coupling and no vendor lock-in.

Step 1: Publish events from ChirpStack

In the ChirpStack application under Integrations, enable the MQTT integration with the marshaler set to JSON. Every uplink is then published to:

application/{ApplicationID}/device/{DevEUI}/event/up

The message body is the same v4 event you would forward anywhere else. With a working codec on the device profile, the decoded values sit in the object field:

{
  "deviceInfo": {
    "applicationId": "00372e31-0620-4671-8270-237632a4e227",
    "applicationName": "cold-chain",
    "deviceProfileName": "EM300-TH",
    "deviceName": "coldroom-01",
    "devEui": "24e124136b502217"
  },
  "fPort": 85,
  "fCnt": 128,
  "data": "AXVkA2etAARoSw==",
  "object": { "battery": 100, "temperature": 17.3, "humidity": 57.5 },
  "rxInfo": [{ "gatewayId": "24e124fffef54092", "rssi": -51, "snr": 13.8 }],
  "txInfo": { "frequency": 868100000 }
}

Step 2: Subscribe with an mqtt in node

Add an mqtt in node, point its broker config at the ChirpStack MQTT broker and subscribe to:

application/+/device/+/event/up

The two + wildcards match any ApplicationID and any DevEUI, so a single node receives every device. Set the node's Output to a parsed JSON object so the next node gets msg.payload as an object rather than a string.

Step 3: Read the decoded values in a function node

Wire the mqtt in node into a function node. The decoded sensor values are in msg.payload.object; the device identity is in msg.payload.deviceInfo. Shape the message for whatever comes next:

// mqtt in -> function: ChirpStack v4 uplink already parsed to an object
var p = msg.payload;
var o = p.object || {};                 // decoded values from the codec

// Stop early if no codec ran (only raw Base64 in p.data)
if (!p.object) {
    node.warn("No decoded object - is a codec set on the device profile?");
    return null;
}

msg.devEui = p.deviceInfo.devEui;        // stable, unique key
msg.topic  = p.deviceInfo.deviceName;    // human label for dashboards

msg.payload = {
    devEui: p.deviceInfo.devEui,
    name: p.deviceInfo.deviceName,
    profile: p.deviceInfo.deviceProfileName,
    ts: new Date(p.time || Date.now()).toISOString(),
    temperature: o.temperature,
    humidity: o.humidity,
    battery: o.battery,
    rssi: (p.rxInfo && p.rxInfo[0]) ? p.rxInfo[0].rssi : null
};

return msg;

From here you fan out: an influxdb out or postgres node for storage, a ui_gauge node for a dashboard, or a switch node that fires an alert when temperature crosses a threshold.

A minimal importable flow

This is a self-contained flow with the mqtt in node, the function above and a debug node. Paste it into Menu, Import in Node-RED and set your broker credentials:

[
  {
    "id": "mqtt_up",
    "type": "mqtt in",
    "name": "ChirpStack up",
    "topic": "application/+/device/+/event/up",
    "qos": "0",
    "datatype": "json",
    "broker": "cs_broker",
    "wires": [["fn_shape"]]
  },
  {
    "id": "fn_shape",
    "type": "function",
    "name": "shape uplink",
    "func": "var p=msg.payload;var o=p.object||{};if(!p.object){node.warn('no codec object');return null;}msg.devEui=p.deviceInfo.devEui;msg.payload={devEui:p.deviceInfo.devEui,name:p.deviceInfo.deviceName,temperature:o.temperature,humidity:o.humidity,battery:o.battery};return msg;",
    "outputs": 1,
    "wires": [["dbg_out"]]
  },
  {
    "id": "dbg_out",
    "type": "debug",
    "name": "decoded",
    "active": true,
    "complete": "payload",
    "wires": []
  },
  {
    "id": "cs_broker",
    "type": "mqtt-broker",
    "name": "ChirpStack broker",
    "broker": "localhost",
    "port": "1883"
  }
]

Sending a downlink back to the device

To control a device (a relay, a valve, a thermostat) publish a JSON command to the downlink topic with an mqtt out node. Build the command in a function node:

// function -> mqtt out: enqueue a ChirpStack downlink
var appId  = "00372e31-0620-4671-8270-237632a4e227";
var devEui = "24e124136b502217";

msg.topic = "application/" + appId + "/device/" + devEui + "/command/down";
msg.payload = {
    devEui: devEui,        // must match the DevEUI in the topic
    confirmed: false,
    fPort: 85,             // must be greater than 0
    data: "AQ=="           // Base64 command; ChirpStack encrypts before sending
};
return msg;

Set the mqtt out node's output to JSON (or JSON.stringify the payload yourself). If a codec with an encodeDownlink function sits on the device profile, you can send an object field with readable values instead of data. For Class A devices the command is delivered in the RX window after the next uplink; for Class C devices almost immediately.

Pitfalls from the field

  • Custom topic template: The application/... event and command topics come from the integration.mqtt block in chirpstack.toml. The default has no region prefix, but a customized deployment can change the template, so if your subscription sees no traffic, check the configured event_topic against what you subscribed to. (The eu868/... prefix you may have seen elsewhere applies to the separate Gateway Bridge gateway/... topics, not these application topics.)
  • No codec, no object: If msg.payload.object is empty you only have msg.payload.data (Base64). Put a codec on the device profile, see the ChirpStack payload decoder guide.
  • Datatype on mqtt in: Leave it on a parsed JSON object, otherwise msg.payload arrives as a string and .object is undefined.
  • fPort greater than 0: Downlinks with fPort 0 are rejected.
  • DevEUI as the key: Key your storage and dashboards on devEui, not the display name, so renaming a device does not split its history.

When Node-RED is and is not the right layer

Node-RED is ideal for prototyping, light routing and bridging odd protocols. For long-term storage and proper dashboards, send the shaped data on to a time-series store and Grafana, or to ThingsBoard, see ChirpStack to ThingsBoard. We run the broker, the flows and the backups for you as Node-RED managed hosting alongside ChirpStack hosting, so the flows you build stay yours.

Frequently asked questions

A plain mqtt in node is enough. ChirpStack publishes the event as JSON, so you parse it and read msg.payload.object directly. The node-red-contrib-chirpstack nodes add convenience (event filtering, downlink helpers) but are optional.
In msg.payload.object, provided a payload codec runs on the device profile. Without a codec you only get msg.payload.data as a Base64 string and must decode it in the function node.
application/+/device/+/event/up for every uplink across every application. The two plus signs are wildcards for ApplicationID and DevEUI, so one mqtt in node catches all devices.
Yes. Publish a JSON command to application/{ApplicationID}/device/{DevEUI}/command/down with devEui, fPort greater than 0 and either a Base64 data field or an object field if a codec is configured.
Yes, both run as managed hosting on European infrastructure, including the broker, the flows and backups. You keep ownership of the flows and the data.

Let's discuss your infrastructure. Digital and on-site.

Whether it's IoT platform development, hardware selection, managed hosting for ChirpStack, ThingsBoard, Grafana or NetBird VPN, or migration from a self-hosted setup - we'll find the right solution for your use case. Book a free 30-minute consultation, no commitment required.

Timo Wevelsiep

Your contact

Timo Wevelsiep

Founder, merkaio

15 minutes, no commitment, directly with Timo.

By submitting, you agree to our Privacy Policy.

merkaio is an independent integrator and is not affiliated with Milesight.