Chapter 5

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.

Remember: 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 for msg.
  • 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

  1. Create a side-effect-free input.

    Add an Inject node and first enter {"temperature":23.4,"humidity":58} as a JSON typed value. Also set topic to example/room. These are fictional values and identify no device.

  2. Inspect the complete msg first.

    Connect a Debug node and configure its output property to show the complete message rather than only msg.payload. Trigger the Inject node once, expand payload in the sidebar, and note each property's type and the _msgid added by the runtime.

  3. 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.

  4. Add a mapping without destroying the source.

    Use a Change node to map msg.payload.temperature to msg.measurement.value, and set the string °C at msg.measurement.unit. Preserve the original payload so that you can compare the structures before and after the mapping.

  5. Test a missing property and an incorrect type.

    Prepare two more Inject nodes: omit temperature from 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.

Live-object exception: An HTTP In flow may carry 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.”

PropertyExample TypePurposeFailure Handling
msg.payload.temperaturenumberFictional indoor-temperature valueSend missing or non-finite values to a validation branch
msg.payload.humiditynumberFictional relative-humidity valueStop processing values outside the reasonable test range
msg.topicstringData-source categorySend an unknown topic to the else branch
msg._msgidstringShort-term diagnostic correlationObserve 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 null are three different conditions.
  • undefined: This often means that a path does not exist. It differs from JSON's null and may not be serialized as a property in JSON.
  • Mutation: Directly assigning to msg.payload.temperature changes 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.

ValueTypeConsiderations
"off"stringA common Home Assistant state string; it is not the Boolean value false
falsebooleanCan be evaluated directly as a Boolean
0numberDo 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]arraySet 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 verify payload, 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:

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?
Do not destroy the only original value. Map the validated result to a new property with a clear purpose, then compare both values with two restricted Debug nodes. If the value is an object or array, choose an explicit deep-copy method; do not mistake a simple reference assignment for an independent copy.
Can I assign _msgid myself and use it as a device ID?
No. The runtime uses it for message tracking; it is not a persistent identity contract across restarts or systems. Create a clearly named custom property instead, and never include a real device ID in a public example.
The source sometimes sends a number and sometimes a numeric string. Can I compare them directly?
Do not rely on implicit coercion. At the data boundary, validate the permitted format, convert it explicitly to a number, and reject empty strings, NaN, and out-of-range values. Downstream nodes can then handle a single type.
Can I safely retain req/res after copying msg?
No. Node-RED's cloning utility preserves live references for 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.
Does successful JSON parsing mean that the data is safe?
No. Parsing proves only that the representation can be converted. You must still limit its size; validate its schema, types, and allowed values; and provide a Catch path for parser errors.