Chapter 4

Your first side-effect-free flow: Inject, Change, and Debug

Create a string message with a manual Inject node, use a Change node to rewrite msg.payload, and inspect the result only in the Debug sidebar. This flow contains no Home Assistant Action node, writes no files, makes no network requests, and does not run Inject automatically at startup or on a schedule.

Why this matters

The purpose of your first flow is not to control a light. It is to demonstrate that you understand Node-RED's minimal execution model: a node receives a message object, processes it, and passes it along a wire to the next node. If you connect real lighting or notifications from the start, even a result that appears to work will not tell you whether the message, condition, Home Assistant connection, or Action configuration is correct.

This chapter uses data labeling for living-room environmental monitoring as an imagined scenario, but it processes only sample text. Press Inject once, and Change rewrites msg.payload from "Original message" to "Safe transformation complete"; Debug displays only the payload. The flow contains no real entity IDs, locations, endpoints, credentials, or private content.

Success definition: The string "Safe transformation complete" appears once in the Debug sidebar only after you deliberately press Inject. Neither Deploy nor an Add-on restart should generate this exercise message automatically.

Core concepts: msg, nodes, and wires

The Node-RED runtime passes JavaScript objects between nodes. These objects are conventionally called msg. msg.payload is a commonly used data field, but it is not the entire message. msg.topic often describes the message topic, and the runtime also handles identifiers such as _msgid. Do not assume that every node reads and writes only the payload. Read a node's Help before using it.

ComponentResponsibility in this flowWhat it does not do
InjectCreates the msg.payload string "Original message" when you press its buttonDoes not use an interval or schedule, and does not inject automatically at startup
ChangeSets msg.payload to the string "Safe transformation complete"Does not run JavaScript or call Home Assistant
DebugDisplays msg.payload in the editor's Debug sidebarDoes not write to the system console, set node status, or display the complete message
WireDefines the Inject → Change → Debug message pathDoes not transmit messages merely because the nodes appear from left to right

The Change node is one of Node-RED's core function nodes, but this example uses its visual rule editor and does not require a Function node. This keeps data transformation separate from writing your own JavaScript. The Debug node is one of the core common nodes and is used only for observation. A successful Debug result does not prove that a future device action has completed.

Create Inject → Change → Debug manually

  1. Create a separate workspace tab.

    Add a new flow tab and use "First flow" as its name. Do not practice in an existing home automation tab. A separate tab limits the scope of Modified Flows to the new flow.

  2. Add Inject and disable every automatic trigger.

    Drag an Inject node from the Palette. Select string as the Payload type and use "Original message" as the value. Do not configure a repeat interval or schedule, and verify that automatic injection at startup is disabled. Use "Send sample message manually" as the node name.

  3. Add Change and configure one rule.

    Configure one rule: Set msg.payload to string Safe transformation complete. Use "Rewrite message content" as the node name. Do not use environment variables, flow or global context, or real household data.

  4. Add Debug and restrict its output.

    Configure Debug to output msg.payload. Enable the sidebar, and disable the system console and node status outputs. Use "Inspect transformation result" as the node name. Restricting the output field reduces unnecessary data exposure.

  5. Connect the wires in order.

    Connect the Inject output to the Change input, then connect the Change output to the Debug input. Verify that no branch leads to an Action, HTTP Request, File, MQTT, or device node.

  6. Review the changes before deploying.

    Confirm that the dirty changes consist only of the new tab and its three nodes. Verify that Inject has no automatic or interval schedule and that Debug outputs only the payload. Before selecting Modified Flows, make sure the new flow contains no preexisting nodes.

  7. Perform one controlled Deploy.

    Select Modified Flows, confirm that only the new flow will be affected, and then select Deploy. If the editor reports an unknown, invalid, or unused configuration, stop and correct it. Do not ignore the warning, and do not select Deploy repeatedly.

  8. Inject once manually and observe the result.

    Open the Debug sidebar and press the button on the left side of Inject once. Expect "Safe transformation complete" as the msg.payload string, then disable Debug after recording the result to prevent output from accumulating later.

Flow goals and safety guardrails

This flow simulates the future normalization of sensor data, but deliberately does not read from Home Assistant. It has four verifiable goals:

  • Controllable: The flow starts only when a person presses Inject, so you decide when the test runs.
  • Predictable: Regardless of Inject's original string, the fixed Change rule sets the payload to the specified string.
  • Observable: Debug displays only the payload, making the input and processed result easy to verify separately.
  • No external side effects: There are no Action, HTTP, MQTT, File, Exec, serial, or notification nodes. Output goes only to the editor sidebar.

"No side effects" has a limited meaning in this chapter. The runtime still creates a message, executes nodes, and generates data for the Debug UI, but it does not act on Home Assistant entities, external services, files, or physical devices. Debug output can itself expose sensitive data, so this exercise uses only synthetic strings.

Why not use real door, window, or light entity IDs?

A real entity ID ties the example to a personal environment and may reveal rooms and devices in a public export. More importantly, connecting an HA state or Action node introduces connection handling, unknown and unavailable states, target validation, permissions, and recovery. Later chapters add those concerns one layer at a time.

Node-by-node configuration and a safe importable example

Inject: allow only a manual start

Set the payload type to string and use "Original message" as its value. Leave repeat and crontab empty, and set once to false. Check all three settings because an automatic Inject node sends a message when the runtime starts or after a redeployment, which would violate this chapter's success definition. Leave topic as an empty string.

Change: specify the property and data type

The rule sets property msg.payload to "Safe transformation complete" as a string; it does not replace the entire msg object with a string. Change updates only the object's payload property, and other runtime fields may remain. To retain the original data in a future flow, create another property. This chapter overwrites the payload to keep the result simple.

Debug: inspect only the field you need

Set the output property to msg.payload. Enable the sidebar, and disable the console and status outputs. Selecting complete msg could expose the topic, identifier fields, and other data added upstream; this example does not need them. When a single output appears, expand the value and confirm that its type is string rather than relying only on the visible text.

Safe flow JSON

You can also inspect or import 01-first-flow.json from this site. The file contains only one tab, a manual Inject node, a Change node, and a Debug node. once is false, and there are no credentials, real IDs, or endpoints. Read the JSON before importing it, use Import's new flow option to isolate the content, confirm that the preview contains only three nodes, and then follow the pre-deployment checklist. Manual creation remains the primary path because it teaches you what each field does.

Inspect msg, not just "success"

After you press Inject once, the first message contains at least a payload. Change receives the object and rewrites that payload, then Debug receives the processed message along the same logical path. The Debug sidebar usually shows the property name, value, data type indicator, and source node. UI details depend on your installed version, so this guide does not make claims based on unpinned screenshots.

Observation pointExpected resultWhat a mismatch may mean
Trigger timingOnly after you press InjectIf output appears after Deploy without a press, Inject's once setting or schedule may still be enabled
Output propertymsg.payloadIf the entire object appears, Debug is configured to output the complete message
Value"Safe transformation complete"Check the Change rule, wires, and deployment state
TypestringIf it is a number, boolean, or object, the typed input is set incorrectly
CountOne output for each pressMultiple outputs may indicate duplicate wires, another Inject node, or another flow

Fields such as _msgid demonstrate that msg is an object. Do not rely on a specific identifier value, and do not treat any runtime identifier as a permanent business ID across flows. The next chapter examines payloads, topics, objects, and arrays in more detail.

Separate observation from device completion

A future flow might be "Sensor event → Condition → Action → Debug." A Debug node after Action proves only that a message reached that point. It does not necessarily prove that the light reached the expected state. A reliable design must also observe Home Assistant state, timeouts, and error paths. This chapter deliberately avoids that ambiguity.

Completion check, stopping safely, and next steps

  • The workspace contains a separate exercise tab with only three nodes—Inject, Change, and Debug—and two wires.
  • Inject uses "Original message" as a string value; repeat/crontab are empty, and automatic injection at startup is disabled.
  • Change has exactly one rule, with msg.payload set to "Safe transformation complete" as a string.
  • Debug outputs only msg.payload to the sidebar, not to the system console or node status.
  • The pre-deployment diff contains only this exercise. You confirmed the scope of Modified Flows and did not ignore any editor warning.
  • Each press of Inject produces the expected string exactly once. Deploy and restart produce no automatic output.
  • When finished, disable Debug or at least clear the sidebar. Any export contains no secrets, real IDs, private endpoints, or household data.

To pause the exercise, disable the tab or Debug, then review and deploy that change with the same checklist. Do not mistake undeployed edits for a stopped runtime. The next chapter builds on this side-effect-free foundation to compare data types and the complete msg object.

Troubleshooting

  • Pressing Inject produces no Debug output: Confirm that all three nodes are connected, Debug is enabled, and the Debug sidebar tab is open. Then verify that the controlled Deploy succeeded. Do not connect an Action node merely to test the flow.
  • The output is still "Original message": Check whether a wire bypasses Change, whether the Change rule targets msg.payload, and whether the typed value is a string. Review the deployment scope again after making corrections.
  • A message appears after Deploy without a press: Immediately check Inject's once, repeat, and schedule settings. Disable all of them before deploying again. This chapter prohibits automatic injection.
  • One press produces two or more outputs: Check for duplicate wires, another identical Inject node, a duplicate Debug node, or another tab. The source node name can help identify the cause, but do not use real environment data.
  • Debug displays the complete object: Change Debug's output from complete msg to msg.payload. Before asking for help publicly, clear existing messages so sensitive content from another flow is not included.
  • Import reports an unknown node: Do not install an unfamiliar package. This site's JSON uses only the Node-RED 5.0.2 core Inject, Change, and Debug nodes. Verify the imported file and your runtime baseline.
  • Deploy warns that other flows have changes: Stop the deployment. Separate or revert dirty changes that do not belong to this exercise. Do not use Full to send unknown changes to the runtime.

Pinned sources and examples

FAQ

Why does the first flow not control a Home Assistant light directly?
First verify msg, wires, Deploy, and Debug with a path that has no external side effects. This separates core runtime problems from Home Assistant connection or Action problems.
The Import preview shows the correct three nodes. Can I deploy immediately?
Not yet. Import only places content in the editor's undeployed state. First confirm that it is on a separate new tab with no unknown nodes or extra wires, and that the dirty changes contain only this exercise. If the Deploy review lists another flow, cancel instead of switching to Full.
Why do two quick presses of Inject produce different _msgid values?
Each trigger creates a new message object, so different tracking IDs are expected. Verify each payload and timestamp separately. Do not mistake the two messages for duplicate output from Change.
Can I import the JSON instead of creating the flow manually?
You can read and import the safe example, but still verify that the preview contains three core nodes and no automatic Inject, then review the Deploy scope. Manual creation is better for a first-time learner.
Why should Debug output only the payload rather than the complete msg?
This chapter verifies only one field. Restricting the output reduces noise and exposure of sensitive data. msg is still an object; later chapters inspect its other fields safely.
Which Deploy scope should I use?
Create a separate new tab and use Modified Flows after confirming that it is the only changed flow. If the diff includes another flow, stop and clean up the changes instead of switching to Full.