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.
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.
| Component | Responsibility in this flow | What it does not do |
|---|---|---|
| Inject | Creates the msg.payload string "Original message" when you press its button | Does not use an interval or schedule, and does not inject automatically at startup |
| Change | Sets msg.payload to the string "Safe transformation complete" | Does not run JavaScript or call Home Assistant |
| Debug | Displays msg.payload in the editor's Debug sidebar | Does not write to the system console, set node status, or display the complete message |
| Wire | Defines the Inject → Change → Debug message path | Does 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
- 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.
- 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.
- Add Change and configure one rule.
Configure one rule: Set
msg.payloadto stringSafe transformation complete. Use "Rewrite message content" as the node name. Do not use environment variables, flow or global context, or real household data. - 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. - 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.
- 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.
- 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.
- 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.payloadstring, 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 point | Expected result | What a mismatch may mean |
|---|---|---|
| Trigger timing | Only after you press Inject | If output appears after Deploy without a press, Inject's once setting or schedule may still be enabled |
| Output property | msg.payload | If the entire object appears, Debug is configured to output the complete message |
| Value | "Safe transformation complete" | Check the Change rule, wires, and deployment state |
| Type | string | If it is a number, boolean, or object, the typed input is set incorrectly |
| Count | One output for each press | Multiple 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/crontabare empty, and automatic injection at startup is disabled. - Change has exactly one rule, with
msg.payloadset to "Safe transformation complete" as a string. - Debug outputs only
msg.payloadto 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
- Pinned Node-RED 5.0.2 commit: runtime messaging, core common nodes, and core function nodes.
- Pinned 5.0.2 Inject and Debug source and pinned 5.0.2 Change source.
- Node-RED's official Working with messages guide and official first-flow tutorial. This chapter's configuration is checked against the pinned 5.0.2 release and its safe example.
- This site's safe 01-first-flow.json example: inspect it directly before importing; it contains no automatic Inject, credentials, or external nodes.