Writing Payload Decoders
Create custom JavaScript decoders with templates and testing
Payload decoders transform raw device data (binary, hex, or custom formats) into structured JSON that the platform can store and visualize. This guide covers writing and testing custom decoders.
Prerequisites
- ADMIN or ENGINEER role
- A device profile to attach the decoder to
- Understanding of your device's payload format
When Do You Need a Decoder?
| Payload Format | Decoder Needed? |
|---|---|
JSON object ({"temp": 23.5}) | No — parsed automatically |
Hex-encoded string ("0x1A2B3C") | Yes — decode bytes to fields |
| Raw binary bytes | Yes — parse buffer to fields |
Custom string format ("T:23.5;H:60") | Yes — parse string to fields |
Steps
Navigate to Device Profile
Go to Device Profiles, open the profile you want to configure, and select the Payload Formatter tab.
Select Codec Type
Set the Codec Type to CUSTOM. This enables the code editor where you write your decoder function.
Other options:
NONE— No decoding (payload must be valid JSON)CAYENNE_LPP— Built-in Cayenne Low Power Payload decoder
Write Your Decoder Function
The decoder receives the raw payload and must return a JSON object with your telemetry fields.
Function signature:
function decode(payload) {
// payload is a Buffer (array of bytes)
// Return an object with your telemetry fields
return {
field1: value1,
field2: value2,
};
}Example — Decode a 4-byte payload:
A device sends 4 bytes: temperature (2 bytes, signed, big-endian) and humidity (2 bytes, unsigned):
function decode(payload) {
// Read temperature as signed 16-bit integer (divide by 100 for decimal)
const temperature = payload.readInt16BE(0) / 100;
// Read humidity as unsigned 16-bit integer (divide by 100 for decimal)
const humidity = payload.readUInt16BE(2) / 100;
return {
temperature,
humidity,
};
}Example — Parse a custom string format:
Device sends "T:23.50|H:60.00|B:3.7":
function decode(payload) {
const str = payload.toString("utf-8");
const parts = str.split("|");
const result = {};
parts.forEach((part) => {
const [key, value] = part.split(":");
switch (key) {
case "T": result.temperature = parseFloat(value); break;
case "H": result.humidity = parseFloat(value); break;
case "B": result.battery = parseFloat(value); break;
}
});
return result;
}Example — Decode hex-encoded GPS data:
Device sends hex string with latitude (4 bytes float) and longitude (4 bytes float):
function decode(payload) {
const lat = payload.readFloatBE(0);
const lng = payload.readFloatBE(4);
return {
latitude: parseFloat(lat.toFixed(6)),
longitude: parseFloat(lng.toFixed(6)),
};
}Test with Sample Payload
Use the built-in Test panel below the code editor:
- Enter a sample hex payload (e.g.,
09 1A 17 70for temp=23.30, humidity=60.00) - Click Test Decoder
- Verify the decoded output matches your expected fields
{
"temperature": 23.30,
"humidity": 60.00
}Always test with real payloads captured from your device. Edge cases (negative values, maximum ranges, missing bytes) are common sources of bugs.
Save and Apply
Click Save. The decoder immediately applies to all devices using this profile. From now on, every incoming payload from these devices passes through your decoder before being stored.
- If the decoder throws an error, the raw payload is stored as-is and an error is logged
- If the decoder returns an empty object, no telemetry fields are created for that message
Common Patterns
Bit Field Extraction
function decode(payload) {
const flags = payload[0];
return {
motorRunning: !!(flags & 0x01),
doorOpen: !!(flags & 0x02),
alarmActive: !!(flags & 0x04),
batteryLow: !!(flags & 0x08),
};
}Multi-Sensor Payload
function decode(payload) {
const sensorCount = payload[0];
const result = {};
for (let i = 0; i < sensorCount; i++) {
const offset = 1 + i * 4;
const sensorId = payload[offset];
const value = payload.readInt16BE(offset + 1) / 100;
result[`sensor_${sensorId}`] = value;
}
return result;
}Tips
- Keep decoders simple: Extract fields and return them. Avoid complex logic or external calls.
- Handle errors gracefully: Wrap risky operations in try/catch to prevent the decoder from crashing on unexpected payloads
- Use meaningful field names:
temperatureis better thantorfield1— these names appear in dashboards and alarm rules - Document the payload format: Add comments in your decoder explaining the byte layout