Decode the payload
A device sends 00018d460000300040e12402. Your billing system wants
index: 18.061. A decoder is what happens in between.
Decoding is worth doing early: rules filter on decoded fields, and the custom output editor can only offer you fields it has seen a decoder produce.
Where a decoder lives
A decoder attaches to a device profile's technology, not to a device. Define it once for the model and every device on that profile decodes the same way.
Where: Profiles → Decoders to write one, then attach it on the profile's technology. Or Flow → Decoders, which is the same thing from the pipeline view.
Pick a kind
Prefer grammar when it fits. It cannot loop forever, cannot throw in surprising ways, and reads as documentation of the payload format.
The two output groups
A decoder returns readings in two groups, and the distinction carries all the way to your sink:
{
"telemetry": { "index": 18.061, "unit": "m3", "battery": 100 },
"meta": { "seqNumber": 18, "rssi": -103.41 }
}
telemetry is what the device measured. meta is about the transmission.
Put things in the right group — some output shapes deliver only one of them, and
a reading filed under meta will go missing from a sink expecting telemetry.
The JavaScript sandbox
JavaScript decoders run in an isolated V8 sandbox:
- No network, no filesystem. A decoder cannot call out.
- Memory and time limited. A runaway script fails that one message; the pipeline keeps running.
- TypeScript syntax is accepted; types are stripped before execution.
When decoding fails
Decoding is best-effort. A decoder that throws, a profile with no decoder, or a callback carrying no payload at all does not fail the message — it continues with whatever it had.
That is deliberate: losing a reading is worse than delivering it undecoded. But it means a sink can see two different shapes for the same device.
Check payload.decodeError on a message that looks wrong. It is the
difference between "the decoder is broken" and "this callback never had a payload
to decode".
DATA_UPLINK, DATA_BIDIR and SERVICE_DATA_ADVANCED contain a payload
frame. Status, acknowledge, repeater and error callbacks have nothing to decode
and arrive as they came — that is not a decoder fault. Filter them out with a
rule if your sink should not see them.Decryption
If the profile defines encryption, the payload is decrypted before decoding, using the key on the device's binding. Keys are encrypted at rest and never shown again — leave the field blank when editing to keep the stored value.
Test it
Send a real uplink, then open Flow → Routing for a pair using that source. The preview renders with your decoder's actual field names and last-seen values, using the same code that performs delivery — so what you see is what a sink receives.
Until a decoder has run once there is nothing to preview, and the editor says so. Send one message and the fields appear.