msg, payload, topic, Objects, and Data Types
Every wire carries a JavaScript message object called msg; payload is only one conventional property within it, not the entire message. You will use Debug to inspect data structures one level at a time, safely read and write nested properties, and recognize the boundaries of copying, parsing, and live objects.
Why This Chapter Matters
Home automation errors often occur not because a node failed to run, but because one node sent the string "21.5" while the next performed a numeric comparison. Or the state may actually reside at msg.data.new_state.state while the flow checks only msg.payload. Inspect the message first so that you do not mistake the string "false" for the Boolean value false, or inadvertently affect another branch by modifying an object.
This chapter uses the Node-RED 5.0.2 bundled with Add-on 22.0.1 as its baseline. The core runtime's Node.js implementation, message-cloning utility, and parser-node source code define the behavior described here. Interface labels may vary by locale, so the instructions use only stable node and field names. The examples use fictional indoor sensor data, connect to no real devices, and perform no Home Assistant Action.
msg is the container; msg.payload is one value inside it. A flow can carry a message with no payload, or one that also contains topic, parts, error information, and custom properties.Core Concepts
- Message object: Node-RED nodes receive and send JavaScript objects. Their properties can contain primitives, objects, arrays, or Buffers; each node is responsible only for the properties documented for it.
payload: The primary data property that many core nodes process by default. It is neither a fixed schema nor a synonym formsg.topic: A conventional category or source label. You can use it to distinguish “living-room temperature” from “study temperature,” but the flow must define its meaning explicitly. Do not assume that every node creates or preserves it._msgid: A tracking property generated by the runtime when a message has no ID. It is useful for correlating a message as it moves through a flow, but must not serve as a long-lived business key, device ID, or security token.- Structure and type: Structure describes property paths and container relationships; type describes the kind of value stored there. A condition can still fail when the path is correct but the type is wrong.
For example, {"payload":{"temperature":23.4},"topic":"example/room"} is a simplified view of a msg: msg.payload is an object, msg.payload.temperature is a number, and msg.topic is a string. The Debug sidebar is an inspection tool. Never paste a complete message containing a home name, location, event details, or credentials into a public issue.
How to Inspect Messages Safely
- Create a side-effect-free input.
Add an Inject node and first enter
{"temperature":23.4,"humidity":58}as a JSON typed value. Also settopictoexample/room. These are fictional values and identify no device. - Inspect the complete
msgfirst.Connect a Debug node and configure its output property to show the complete message rather than only
msg.payload. Trigger the Inject node once, expandpayloadin the sidebar, and note each property's type and the_msgidadded by the runtime. - Then narrow the view to the exact path.
Duplicate the Debug node and configure it to show only
msg.payload.temperature. Confirm that its type is number. If it is a string, correct the typed value at the source node rather than guessing at conversions in every downstream node. - Add a mapping without destroying the source.
Use a Change node to map
msg.payload.temperaturetomsg.measurement.value, and set the string°Catmsg.measurement.unit. Preserve the originalpayloadso that you can compare the structures before and after the mapping. - Test a missing property and an incorrect type.
Prepare two more Inject nodes: omit
temperaturefrom one and make it a string in the other. Connect them only to Debug or Switch, not to Action. Confirm that your flow distinguishes valid input, a missing value, and an incorrect type.
Change only one condition at a time and trigger each Inject node manually. This method may be slow, but it creates a reproducible message contract and makes differences easy to identify when you later replace the fictional input with a Home Assistant state event.
msg Structure, Tracking, and Copying
When the Node-RED 5.0.2 runtime encounters a message without an _msgid on a node's receive/send path, it generates one. When a message fans out, the runtime creates send events: the first route can reuse the message, while subsequent routes are cloned as needed. Treat each wire as a contract for delivering message values; never rely on branch execution order or incidental shared references.
“Assign msg.payload to another property” and “create a deep copy” are different operations. Objects and arrays are reference types, so shallow aliases may both reflect later direct changes to nested content. The Node-RED Change editor offers a deep-copy option when setting a value, and the core cloning utility also makes deep copies of ordinary message data. Whichever method you use, compare the source and destination properties in Debug. In particular, do not modify an object in a Function node if you intend to reuse it after sending it.
// Tutorial structure; this does not ask you to switch to a Function node
msg.payload = { temperature: 23.4 };
msg.topic = "example/room";
// Let the runtime manage msg._msgid
return msg;
When you need your own correlation key, use a clearly named property from a controlled source, such as msg.correlation, and restrict its length and allowed characters. Do not overwrite _msgid to imitate another message. Searching Debug output by ID is suitable only for runtime tracing; never treat that ID as permanent across reinjection, Split operations, or processing by other nodes.
msg.req and msg.res. Node-RED's cloneMessage deliberately preserves references to these two live objects instead of deep-copying them. Do not place them in context, serialize them, pass them through unnecessary branches, or expose them in Debug output. The HTTP response lifecycle is covered in Chapter 19.payload, topic, and Explicit Message Contracts
A well-designed flow defines its contract at the boundary: which properties are required, which types are accepted, and where the output is written. For example, a room-comfort flow might require a number at msg.payload.temperature, a number at msg.payload.humidity, and a string at msg.topic, then output a summary object in msg.payload. That is far more precise than saying only that the flow “receives a payload.”
| Property | Example Type | Purpose | Failure Handling |
|---|---|---|---|
msg.payload.temperature | number | Fictional indoor-temperature value | Send missing or non-finite values to a validation branch |
msg.payload.humidity | number | Fictional relative-humidity value | Stop processing values outside the reasonable test range |
msg.topic | string | Data-source category | Send an unknown topic to the else branch |
msg._msgid | string | Short-term diagnostic correlation | Observe it only; do not set it yourself |
The value of topic comes from consistency. A Switch node can route different rooms to separate statistics branches based on msg.topic, and a Trigger node can maintain separate timers by topic. Those behaviors become unpredictable if upstream nodes sometimes use an entity ID and sometimes a display name. Choose a stable, non-sensitive, verifiable category and normalize it centrally in a Change node.
Do not treat entity, device, area, tag, zone, or webhook IDs as harmless text. They can reveal the layout of a home; replace them with an explicit example placeholder before exporting a flow. Credentials, authorization headers, and access tokens must never appear in message examples or Debug output.
Objects, Arrays, Missing Values, and Mutation
A node's typed-property field usually lets you enter a path such as payload.temperature and separately choose msg, flow, or global as its source. Do not confuse the literal string "payload.temperature" with the msg property option: the former is text, while the latter reads the value at that path.
- Object: Use named properties, such as
payload.temperature, to represent data. First confirm that the parent exists and is an object. - Array: Indexes start at 0. An empty array, a missing index, and an element whose value is
nullare three different conditions. undefined: This often means that a path does not exist. It differs from JSON'snulland may not be serialized as a property in JSON.- Mutation: Directly assigning to
msg.payload.temperaturechanges the current message. If you need the original data for later comparison or tracing, map the derived value to a new, clearly named property instead.
For home-automation data, keep source events separate from derived results. For example, preserve msg.payload and place calculated results in msg.analysis. Before sorting an array or passing it to Split or Join, enforce a maximum element count. Without size limits, untrusted input can cause sequence nodes to retain large numbers of messages for long periods.
In a flow with multiple branches, take particular care not to assume that “one branch deletes a property first while another happens to see it.” Treat the input to every wire as an independent contract. When isolation is required, use an explicit mapping or a deep copy, then verify both paths simultaneously with two Debug nodes.
Typed Values and Parser Boundaries
The small type selector next to a field in nodes such as Inject, Change, and Switch determines how the input is interpreted. Common options include string, number, Boolean, JSON, timestamp, and msg/flow/global property; some nodes also support environment variables or JSONata. Available options vary by node, so rely on the editor in front of you. Entering 23 and selecting string still produces a string: identical-looking values are not necessarily the same type.
| Value | Type | Considerations |
|---|---|---|
"off" | string | A common Home Assistant state string; it is not the Boolean value false |
false | boolean | Can be evaluated directly as a Boolean |
0 | number | Do not use it interchangeably with the string "0" |
{"value":23.4} | object (after JSON parsing) | Limit its size and validate its properties first |
[1,2,3] | array | Set an element limit before passing it to Split |
Node-RED 5.0.2 core registers CSV, HTML, JSON, XML, and YAML parser nodes. A parser converts one representation into another data structure; successful parsing does not make the content trustworthy or validate its schema. The JSON node can convert between JSON strings and JavaScript values. The HTML parser can emit multiple messages with msg.parts. CSV, XML, and YAML each have their own structural options. Never equate “parsed successfully” with “safe to use.”
When processing a response from an external weather service, first limit its size, then use the appropriate parser. Next, use a Switch node to check required properties and types, and only then map the data to your internal structure. HTML selectors, CSV fields, and YAML structures can all change when their source changes. Keep a Catch path and sanitize any content included in error output.
Troubleshooting
- Switch displays 23 but does not match a numeric condition: Expand the value in Debug and inspect its type badge. If it is a string, return to Inject, the parser, or Change and use a number instead of weakening the rule to use loose comparison.
- A nested property displays undefined: Configure Debug to show the complete
msg, then verifypayload, each parent object, and the property spelling one level at a time. Also test an input whose parent is null. - Changing one branch makes another branch's result unstable: Stop relying on branch order. Map the source to a new property or use a deep copy, then compare the paths with two Debug nodes. Do not use a delay to “fix” a data race.
- A parser reports an error or its output structure changes: Save a small, de-identified test string, confirm the parser mode and output property, and use a Catch node to handle errors. Never publish a complete external response or its headers.
- Debug shows req, res, or authorization data: Immediately disable complete-message Debug output and delete any affected sidebar or log exports. Do not copy or retain live objects. Rotate exposed credentials under your incident-response procedure instead of posting them in a discussion forum.
Pinned Sources
This chapter uses the pinned Node-RED 5.0.2 commit to verify message-runtime behavior, cloning, and core parsers, with the official user documentation providing additional context:
- Node-RED 5.0.2 pinned commit 61bd08d:
@node-red/runtime/lib/nodes/Node.js,@node-red/util/lib/util.js, and@node-red/nodes/core/parsers. - Official Node-RED documentation: Working with messages.
- Official Node-RED documentation: Understanding message structure.
If another document describes different parser options or message behavior, check its Node-RED version first. Then use a test message containing no sensitive data to verify the type, copying behavior, and failure path.
FAQ
Should I overwrite the original payload when a downstream node still needs to compare it?
Can I assign _msgid myself and use it as a device ID?
The source sometimes sends a number and sometimes a numeric string. Can I compare them directly?
NaN, and out-of-range values. Downstream nodes can then handle a single type.Can I safely retain req/res after copying msg?
msg.req and msg.res; they are not ordinary serializable data. Do not put them in context or Debug output, and minimize their use within the HTTP flow's lifecycle.