Rule node reference
This page details every node in the Rules editor.
The message object
Section titled “The message object”In Comparison, Lua, Go, and output templates, the message is available as message:
| Field | Contents |
|---|---|
channel |
The message’s channel. |
subtopic |
The subtopic. |
publisher |
The ID of the device (or rule/account) that sent it, from broker authentication. |
protocol |
mqtt, ui (sent from the application), rule (from Channel Publisher), schedule. |
created |
The receive time (epoch seconds). |
payload |
The parsed content: an array of records for SenML, an object for SenFlat and JSON. |
history |
true when the message is SenFlat v2 with gateway-history status (h = 1). |
timeTrusted |
false when a SenFlat v2 source has an untrusted device clock. |
Array indexes in paths and Lua start at 1: message.payload[1].v. For SenFlat v1, the metrics are at message.payload.m.<metric>.
Channel Subscriber
Section titled “Channel Subscriber”| Field | Description |
|---|---|
| Channel | The channel being watched. |
| Topic | The subtopic. Wildcards + (one level) and # (many levels). Empty = all subtopics. |
The Add Logic menu contains: Bypass (pass through), SenFlat v2 splitter → rows, Comparison, Lua code, Go code, and the list of available Custom Nodes.

Every logic node decides whether the message is forwarded. A result of nil, false, or an unmet condition = the message is not forwarded, so outputs below it do not run.
Bypass
Section titled “Bypass”Forwards the payload as is. The cheapest way to store or forward every message because it runs no code. Used by the “Store in Internal DB” template.
Comparison
Section titled “Comparison”One or more condition rows:
If message.payload[1].v >= number 24.5- Path with dot or bracket notation:
message.payload.temp_inlet,message.payload.m.door_open. - Operators:
==,~=(not equal),>,<,>=,<=. - Value types: number, text, bool.
- Conditions are chained with AND or OR (AND is evaluated first). At most 20 conditions.
- A path that does not exist, or has a different type from the one compared, makes the condition false.
- When satisfied, the payload is forwarded as is.
Lua code
Section titled “Lua code”It must have the form:
function logicFunction() -- read message, return a resultendreturn logicFunction()The result: nil or false = not forwarded; true = the payload as is; a table = a new result (a sequential array becomes a JSON array, anything else an object). The syntax is checked on save and the error is shown in the editor.
The sandbox: a fresh interpreter per execution (no state leaks between messages), only the base, table, string, and math libraries. No os, io, package, debug, load*, dofile, require. An execution time of 500 ms, and a limit on the result size.
-- Forward only when the inlet temperature is above 23.5, and build an alarmfunction logicFunction() for _, rec in ipairs(message.payload) do if rec.n and string.find(rec.n, "temp_inlet$") and rec.v and rec.v > 23.5 then return { severity = 70, cause = "Inlet temperature above limit", measurement = rec.n, value = rec.v, unit = "Cel" } end end return falseendreturn logicFunction()Go code
Section titled “Go code”Go code runs in a separate, isolated child process, with a package allowlist, a timeout, and a memory limit; on Linux, OS-level isolation is added. The shape is similar to Lua: import m "messaging" and a logicFunction() function. Availability depends on the server configuration (the administrator can disable it).
Custom Node
Section titled “Custom Node”Uses logic you saved in Custom Nodes, at a specific version.
SenFlat v2 splitter → rows
Section titled “SenFlat v2 splitter → rows”Splits one SenFlat v2 message (many sources and time points) into a list of wide rows, one row per (source, time), without Lua. The base columns: device_id, t_ms, time, time_trusted, history, q, plus every metric at that time. A message that is not SenFlat v2 is not forwarded. Full usage: Rule to PostgreSQL: many rows.
Shared options
Section titled “Shared options”Every logic node has a Data scope:
| Scope | Meaning |
|---|---|
| All data | Live data and all history (the default of older nodes). |
| Live data only | Ignores gateway history (the default of a new Comparison). |
| Live data and fresh history | History whose age does not exceed the window in minutes (5 by default). |
Lua, Go, and Custom Node also have an Evaluation unit: Batch (the code runs once and sees the whole list of rows) or Per row. Comparison always evaluates each row by itself.
Output
Section titled “Output”An output determines where a message goes, including whether it is stored. The Add Output menu contains: Internal DB (SenML), Internal DB (JSON), Channel Publisher, E-Mail, Telegram, PostgreSQL, Webhook HTTP, Alarm, and Device alarm.

Internal DB (SenML) and Internal DB (JSON)
Section titled “Internal DB (SenML) and Internal DB (JSON)”Stores to Celestian’s internal time-series database. The SenML one accepts SenML and SenFlat (units are filled from the Metric Catalog) and validates strictly; the JSON one stores any result that can be turned into JSON (at most 1 MiB). They appear in different tabs in Messages. Internal DB does not accept the list of rows from the splitter.
Channel Publisher
Section titled “Channel Publisher”Publishes the logic result to another channel (and subtopic), on behalf of the rule. The message is recorded with the rule protocol. A rule that writes to its own input channel on a topic it covers is refused on save, to prevent loops.
Creates an alarm from the logic result object. The fields come from the node template, or when empty from the key of the same name in the logic result:
| Field | Rule |
|---|---|
severity |
Required, an integer from 0 to 100. |
cause |
Required, text. |
measurement |
Required, the name that distinguishes the alarm. |
value, unit, threshold |
Optional. |
An auto-clear option (none, after N minutes without recurrence, or after N consecutive normal messages) and a Gateway history data section (the history window and retroactive alarms). Details: Alarms.
Device alarm
Section titled “Device alarm”For devices that compute alarms themselves (boolean metrics prefixed with alarm_): true opens an alarm, false closes it, with a warning severity of 40 and an alarm severity of 80 (changeable). A node behind a gateway is associated with a device through the Serial. A one-click template is available on the Rules page.
Recipients, Subject, and Content with a Go template: {{.Result}}, {{.Result.<key>}}, and {{.Message.<key>}}. Requires SMTP to be configured on the server.
Telegram
Section titled “Telegram”A bot token (stored encrypted), a chat ID, and the message text as a template.
PostgreSQL
Section titled “PostgreSQL”Writes to a table in an external PostgreSQL database. Fields: host, port, user, password (encrypted), database, table, and the column mapping (a Go template per column). Table and column names are validated and quoted, values are always parameters, and an empty result becomes NULL.
- The One row per result item switch (foreach) inserts many rows in one atomic
INSERT(at most 500 rows and 30 columns per message), with.Row,.Index, and thetsmsfunction. - The Test connection button verifies without writing.
Webhook HTTP
Section titled “Webhook HTTP”A URL, a method, headers (values encrypted), and the body as a template, with retries and backoff.
Go templates
Section titled “Go templates”Templated fields (e-mail, Telegram, webhook, PostgreSQL, alarm fields) use Go text/template syntax. The available data: .Result (the logic result), .Message (the original message), and in multi-row mode .Row and .Index. Helper functions: json, tsms, and printf.
Temperature {{printf "%.1f" (index .Result 0).v}} °C at {{.Message.subtopic}}