Nothing you drag onto the canvas is running yet — until you click one button
Part 1 got Node-RED installed and locked down. This part teaches you to read the editor before you ever touch it live, then hands you a flow with zero real-world consequences to build that reading into muscle memory. You will drag three nodes — Inject, Change, Debug — wire them together, and click Deploy exactly once, with nothing behind that click but a string changing in a sidebar. No light turns on. No notification fires. That is the entire point: everything in this part is rehearsed on a flow that cannot touch your house, so that by the time a real Home Assistant node enters the picture, Deploy is not a leap of faith.
Everything on the canvas is a draft until you click Deploy
Open the Node-RED editor from the add-on's OPEN WEB UI button and the most common mistake is assuming that whatever is visible on the canvas is already running. It is not. Dragging a node in from the Palette, wiring it to another node, typing a new value into a property dialog — none of that reaches the running system. It changes only the editor's model of your flows, sitting in your browser. The runtime — the actual Node-RED process, the one holding your existing automations — hears about none of it until you select a scope and click Deploy.
That gap matters the moment Node-RED already controls something real. If it is already running hallway lighting, nighttime notifications, or climate equipment from Part 1's install, picking the wrong Deploy scope by habit can restart nodes that have nothing to do with the change you just made. This part exists so that does not happen to you on your first afternoon with the editor.
It is the difference between editing a document and hitting send on the email. You can drag paragraphs around, delete a sentence, retype the subject line — none of that lands in anyone's inbox until you click Send. The Node-RED canvas is the draft. Deploy is Send. Until you click it, the automation quietly running your hallway lights has not heard about a single one of your edits, no matter how finished they look on screen.
Your only job in this section and the next is to identify each part of the editor, open a node's Help, and understand what the three Deploy options actually restart — not to import a flow, not to touch the Home Assistant server configuration, not to install a Palette package, and not to click the main Deploy button yet. The screenshots and field names in this part are pinned to Node-RED 5.0.2, running under add-on 22.0.1; if a menu on your screen looks different, that is very likely a version gap rather than something you did wrong — core editor behavior and node options do shift between builds, and it is worth checking your own add-on's version before assuming the instructions are broken.
| Action | Where it happens | Does it change the runtime? |
|---|---|---|
| Switch workspace tabs, zoom, or pan | Editor view | No |
| Add nodes, change properties, connect wires | Undeployed flow model | Not yet |
| Open Information or Help, or search the Palette | Editor UI | No — though installing a package through Manage palette is a separate, real system change |
| Click Deploy | Editor sends the complete flow set and a deployment type to the runtime | Yes. Full, Modified Flows, or Modified Nodes decides how much restarts |
| Change add-on options and restart | The Home Assistant add-on layer | Yes — and its scope is wider than deploying a flow ever is |
flowchart LR
subgraph EDIT["The editor, in your browser tab"]
direction LR
A["Drag nodes, wire them,
edit a node's properties"] --> B["Undeployed flow model"]
end
subgraph RUN["The runtime, the Node-RED process"]
direction LR
C["Nodes start, subscribe,
and begin passing messages"]
end
B -->|"Deploy"| C
The four areas, and what each one actually promises
The editor divides into four areas: the Palette on the left, the central workspace, the sidebar on the right, and the header with the Deploy control along the top. Find all four before you drag anything. On a narrow browser window the Palette or sidebar can collapse to a thin strip — look for its toggle before deciding a feature is missing.
The workspace organizes flows into tabs, not permissions
The workspace holds your flows as tabs. Nodes carry input and output ports, and wires connect an upstream output to a downstream input; messages travel along those wires and only along those wires. Placing a node to the visual left or right of another improves readability, nothing more — the actual execution path is the wiring, and a node with no incoming wire can still act on its own, in response to its own event source. An unwired node sitting quietly on the canvas is not proof that it does nothing.
Splitting flows into tabs named, say, Entry Lighting and Ambient Notifications makes them easier to read. It does not isolate credentials, network access, or Home Assistant permissions between them. Config nodes — the Home Assistant server connection chief among them — are commonly shared across many flows, so a change to one config node can ripple into tabs you were not even looking at. When you do eventually edit a config node, check its list of users, not just the tab in front of you.
A readable home-automation flow reads left to right as source, then condition, then action or observation, and names its nodes for what they do rather than what type they are — "night only," "confirm light is off," "test output" tell you more at a glance than the node's default label ever will. Comment nodes are the place to write down assumptions and fail-safe behavior. None of that layout work is test evidence on its own, though; a tidy diagram still has to be watched after deployment to prove every branch behaves.
The Palette lists what is registered, not what is configured
The Palette catalogs available nodes by category, with a search box at the top. Node-RED's own core set includes Inject, Debug, Complete, Catch, Status, Link, Comment, Change, Switch, Template, Delay, Trigger, and Function. The Home Assistant add-on adds several third-party nodes on top of that, including HA WebSocket 0.80.3. Seeing a node in this list only means its package is registered with the runtime — it says nothing about whether your environment, your devices, or your permissions are actually set up to use it well.
| Node | Purpose in this part | Safety reminder |
|---|---|---|
| Inject | Manually generate a test message | Section 4 explicitly turns off every automatic trigger and fires it with one button press |
| Change | Set, change, delete, or move message properties | Confirm the data type and the exact target property; do not put secrets into msg |
| Debug | Send selected message content to the Debug sidebar | Can expose a sensitive payload — disable or remove it once you are done troubleshooting |
| Action | Not used in this part — asks Home Assistant to do something, later | Has physical side effects. Older material calls it Call Service; that name is mentioned only so you recognize it, and is not used here |
The Palette is a hardware store's shelf, not its receipt. Walking past the shelf and reading a box tells you the tool exists in the store. It does not tell you the tool is charged, calibrated, or the right one for the job you are about to do — you still read the box, or in Node-RED's case, the node's Help tab, before you plug anything in.
A theme changes appearance only. Add-on 22.0.1 ships @node-red-contrib-themes/theme-collection 5.0.1, defaulting to theme: default from a fixed list of names — it recolors the editor, and touches nothing about authentication, permissions, or data isolation. Changing it needs an add-on restart, not Deploy; Deploy only ever handles flows.
Manage palette is a supply-chain operation, not a search result. Typing into the Palette's search box installs nothing. Installing a package through Manage palette does — it adds third-party server code to your running environment, the same as the add-on's npm_packages startup option does. Either route deserves a look at the package's name, its maintenance status, a pinned version, and what you would do to recover if it turned out to be a mistake. A familiar-looking icon is not a reason to install something.
The sidebar: Information, Debug, and Help
The sidebar on the right is a switchable panel; the three tabs worth knowing now are Information, for details on whatever node is selected, Debug, for watching runtime messages, and Help, for the documentation bundled with the node's installed version. Select a node in the Palette or on the canvas and read its Help before configuring it from guesswork — for Home Assistant nodes specifically, that also means checking the pinned HA WebSocket 0.80.3 documentation, especially around the Action node's target, data, and output settings once you reach that chapter.
The Debug sidebar is also a potential leak surface. In a home environment its output can include occupancy status, room names, notification text, and device names. During the exercise ahead you will restrict Debug's output to just msg.payload for exactly this reason — not because the rest of the message is dangerous in the abstract, but because there is no reason to widen what shows on screen before you have a reason to need it.
Full, Modified Flows, Modified Nodes — and what each one actually stops
Every Deploy click sends your complete flow set to the runtime along with a deployment type. In Node-RED 5.0.2's editor these show up as three named options: Full, Modified Flows, and Modified Nodes, corresponding to internal types full, flows, and nodes. They are runtime restart strategies, not grades of save quality — every one of them submits the same full flow configuration, and the only difference is how much of the running system the runtime tears down and rebuilds to apply it.
| UI name | 5.0.2 description | Runtime impact | Home scenario |
|---|---|---|---|
| Full | Deploys everything in the workspace | Stops and restarts every node | Unrelated timers, event subscriptions, connections, and lighting flows may all restart — the widest possible impact |
| Modified Flows | Only deploys flows that contain a changed node | Restarts every node on any flow with a change in it | A running node on the same tab that you never touched can restart too — check for long waits or device controls sharing that tab |
| Modified Nodes | Only deploys nodes that changed | The stop list is exactly diff.changed and diff.removed | Usually the least disruptive — but a config node change can mark every node that uses it as changed, so dependencies still need a check |
flowchart TD X["You press Deploy"] --> F["Full"] X --> MF["Modified Flows"] X --> MN["Modified Nodes"] F --> F1["Every node on every tab
stops, then restarts"] MF --> MF1["Every node on any flow
with a changed node
stops and restarts"] MN --> MN1["Only nodes Node-RED marks
changed or removed
are stopped"]
Full is cutting power to the whole house to change one lightbulb. Modified Flows is cutting power to just the room that bulb is in — the room's other lamps blink off too, even though you never touched them. Modified Nodes is unscrewing only that one bulb. All three technically get the new bulb in. Only one of them does it without also dimming the lamp across the room that somebody else was reading by.
Restarting a node can clear whatever it was holding in memory, and it can re-establish subscriptions and connections from scratch. If an Inject node is configured to fire automatically at startup, a redeploy that restarts it will send that automatic message again — one more reason the exercise ahead disables every automatic trigger before it goes anywhere near Deploy. Note too that a link between two nodes does not, by itself, pull the far end into the Modified Nodes stop list; only an actual change or removal does that, and a config node's change is the one thing that can indirectly widen the list.
Five checks before you press Deploy on anything real
-
Check 1
Confirm the list of changes and why you are deploying
Do not carry unknown dirty changes into a Deploy click just because they happen to be sitting there.
-
Check 2
Confirm the scope
List which flows will stop or restart, which nodes are changed or removed, and which nodes a config node change might mark as changed even though you never touched them directly.
-
Check 3
Check for queued and automatic triggers
Inject nodes set to fire at startup, messages waiting in a Delay or Trigger node, and any Action, HTTP, file, or device output downstream all deserve a look before you restart the nodes that feed them.
-
Check 4
Prepare to monitor and recover
For anything touching real lighting, pick non-critical equipment to test on and do it at a time when you can actually see the effect.
-
Check 5
Read the result before clicking again
After deploying, read the notification, the Debug output, and any logs you need. Do not click Deploy repeatedly while waiting — wait for the result first.
Inject → Change → Debug, with nothing wired to anything real
The point of this first flow is not to control a light. It is to prove, on something that cannot go wrong, that you understand Node-RED's minimal execution model: a node receives a message, does something to it, and passes it along a wire to whatever is next. Wire up a real Action node from day one and even a result that looks correct will not tell you whether the message, the condition, the Home Assistant connection, or the Action's own configuration was the thing that actually worked.
The scenario is an imagined one — labeling data for living-room environmental monitoring — but the flow itself only ever processes sample text. Press Inject once. Change rewrites msg.payload from "Original message" to "Safe transformation complete". Debug shows only the payload. No real entity IDs, locations, endpoints, or credentials appear anywhere in it.
"Safe transformation complete" appears exactly once in the Debug sidebar, and only after you deliberately press Inject. Neither a Deploy nor an add-on restart should ever produce this message on its own — if one does, an automatic trigger is still switched on somewhere, and section 6 below walks through fixing that.Node-RED passes plain JavaScript objects between nodes, conventionally called msg. msg.payload is the field you will use most, but it is not the whole object — msg.topic commonly carries a topic string, and the runtime attaches its own fields such as _msgid along the way. Do not assume every node reads and writes only the payload; the node's own Help tab is where that gets confirmed.
| Node | Its job in this flow | What it does not do |
|---|---|---|
| Inject | Creates msg.payload = "Original message" when you press its button | No interval, no schedule, no automatic injection at startup |
| Change | Sets msg.payload to the string "Safe transformation complete" | Does not run JavaScript and does not call Home Assistant |
| Debug | Shows msg.payload in the editor's Debug sidebar | Does not write to the system console, set node status, or show the whole message |
| Wire | Defines the Inject → Change → Debug path | Does not carry a message just because the nodes sit left to right on screen |
The Change node here uses its visual rule editor, not a Function node — keeping "set this property to this value" separate from "write your own JavaScript" until you actually need the latter. Debug is used purely to observe; a good result in Debug proves the message arrived, not that some future device action finished doing whatever it was meant to do.
Why this flow avoids real entity IDs on purpose
A real entity ID ties an example to your own house and can reveal rooms and devices the moment you share a screenshot or a flow export. There is a second reason, and it matters more: wiring in an actual Home Assistant state or Action node pulls in connection handling, unknown and unavailable states, target validation, permissions, and recovery, all at once. Later chapters introduce each of those one at a time. For now, the placeholder convention to keep in mind for anything you do try on your own is names like light.living_room or sensor.YOUR_SENSOR — obviously not real, and obviously meant to be swapped for your own.
-
Step 1
Create a separate workspace tab
Add a new flow tab named "First flow." Do not practice inside an existing home-automation tab — a dedicated tab keeps Modified Flows scoped to only this exercise.
-
Step 2
Add Inject, and disable every automatic trigger
Drag an Inject node in. Set its Payload type to
stringwith valueOriginal message. Leaverepeatandcrontabempty, and confirmonceisfalse— all three, because an automatic Inject fires on startup or after a redeploy, which would break this exercise's success definition outright. Leavetopicas an empty string. Name it "Send sample message manually."payload type: stringpayload value: Original messageonce: false,repeat: (empty),crontab: (empty) -
Step 3
Add Change, with exactly one rule
The one rule: set
msg.payloadto the stringSafe transformation complete. Name it "Rewrite message content." No environment variables, no flow or global context, no real household data anywhere in the rule.set msg.payload to string "Safe transformation complete" -
Step 4
Add Debug, and restrict its output
Set Debug's output property to
msg.payload. Enable the sidebar output, and disable both the system console and node status. Name it "Inspect transformation result." Restricting the field is what keeps this from becoming a wider data exposure than the exercise needs. -
Step 5
Wire them in order
Inject output → Change input, then Change output → Debug input. Confirm no branch anywhere leads to an Action, HTTP Request, File, MQTT, or device node.
-
Step 6
Review the diff before deploying
Confirm the dirty changes are exactly the new tab and its three nodes — nothing else. Confirm Inject still has no automatic trigger and Debug still outputs only the payload.
-
Step 7
Deploy once, with Modified Flows
Select Modified Flows, confirm only the new flow is listed, then click Deploy. If the editor reports an unknown, invalid, or unused configuration, stop and fix it rather than deploying anyway.
-
Step 8
Press Inject once, and read the result
Open the Debug sidebar first, then press the button on Inject's left edge exactly once. Expect
msg.payloadto read"Safe transformation complete". Disable Debug once you have confirmed it, so its output does not keep accumulating unattended.
flowchart LR I["Inject node
sends msg.payload =
'Original message'"] --> C["Change node
sets msg.payload to
'Safe transformation complete'"] --> D["Debug node
shows msg.payload only"]
The flow ships as a downloadable reference too: the guide site's examples folder holds it as 01-first-flow.json, containing exactly one tab, one manual Inject, one Change, and one Debug — once false, no credentials, no real IDs. If you do import it rather than build it by hand, read the JSON first, use Import's new-flow option so it lands isolated, confirm the preview shows exactly three nodes, and then run the same pre-deploy checklist as above. Building it by hand the first time is still worth the extra few minutes, because it is what actually teaches you what each field does.
Controllable
The flow only ever starts when a person presses Inject. Nothing about it runs on a timer or at startup.
Predictable
Whatever string Inject happens to hold, the fixed Change rule always sets the payload to the same specified value.
Observable
Debug shows only the payload, so the input and the processed result are each easy to check on their own.
No external side effects
No Action, HTTP, MQTT, File, Exec, serial, or notification node anywhere in it. Output goes to the editor sidebar and nowhere else.
"No side effects" has a narrow meaning here worth being precise about: the runtime still builds a message object, still executes nodes, and still generates data for the Debug UI — it simply never acts on a Home Assistant entity, an external service, a file, or a physical device. Debug output can itself carry sensitive data in a real flow, which is exactly why this one only ever carries synthetic strings.
What to look at, not just whether it looks like it worked
After you press Inject once, the message picks up at least a payload. Change receives it, rewrites that payload, and Debug receives the result along the same logical path. The Debug sidebar typically shows the property name, the value, a data-type indicator, and the source node — the exact layout depends on your installed version, so treat any claim based on an unpinned screenshot from elsewhere with some caution.
| Observation point | Expected result | What a mismatch may mean |
|---|---|---|
| Trigger timing | Only after you press Inject | Output appearing right after Deploy, with no press, means Inject's once setting or a schedule is still active |
| Output property | msg.payload | The whole object showing up means Debug is set to output the complete message, not just the payload |
| Value | "Safe transformation complete" | Anything else means the Change rule, the wiring, or the deployment state needs another look |
| Type | string | A number, boolean, or object here means the typed value in Change or Inject was set to the wrong type |
| Count | One output per press | More than one usually means a duplicate wire, a second Inject node, or a second flow doing the same thing |
Fields like _msgid exist to prove msg is an object with more than one field, not to serve as a stable identifier — do not lean on a specific _msgid value, and do not treat any runtime identifier as a permanent business ID across flows. Chapter 5 goes further into payloads, topics, objects, and arrays; this part only needs you comfortable reading the one field.
Observation is not the same thing as device completion
A future, real flow might read "Sensor event → Condition → Action → Debug." A Debug node placed after an Action node only proves a message reached that point in the wiring — it does not prove the light actually reached the state you asked for. A flow you can trust needs to observe the Home Assistant state itself, plus timeouts and error paths, not just a Debug row that looks satisfied. This exercise sidesteps that whole question on purpose, so you learn to read msg before you also have to reason about whether a device obeyed it.
Completion checklist
- The workspace has a separate exercise tab holding exactly three nodes — Inject, Change, Debug — and two wires.
- Inject uses
"Original message"as a string;repeatandcrontabare empty, and no automatic startup injection is enabled. - Change has exactly one rule:
msg.payloadset to the string"Safe transformation complete". - Debug outputs only
msg.payloadto the sidebar — not the console, not node status. - The pre-deploy diff contained only this exercise, and no editor warning was ignored to get there.
- Each Inject press produces the expected string exactly once; a Deploy or an add-on restart on its own produces nothing.
- Debug is disabled, or at least cleared, once you are done. Anything exported from this flow carries no secrets, real IDs, or household data.
To pause the exercise rather than finish it, disable the tab or the Debug node, then deploy that change through the same checklist as everything else — an undeployed edit is not the same thing as a stopped runtime, and it is easy to assume otherwise.
The failures that actually happen, and what they usually mean
flowchart TD A["You press Inject.
Nothing shows in Debug"] --> B{"Are Inject, Change,
and Debug wired in order?"} B -->|"no"| B1["Reconnect the wires:
Inject to Change to Debug"] B -->|"yes"| C{"Is Debug enabled, with
its sidebar tab open?"} C -->|"no"| C1["Enable Debug and open
the Debug tab in the sidebar"] C -->|"yes"| D{"Did the last Deploy
finish with no warning?"} D -->|"no"| D1["Fix the warning first.
Do not ignore it"] D -->|"yes"| D2["Check Debug's output field
is set to msg.payload,
not the complete message"]
| Symptom | Likely cause | What to do |
|---|---|---|
| You cannot find the Palette | The left panel is collapsed, or the browser is narrow or zoomed | Check the panel toggle and window width first. Do not jump to Manage palette and install a similarly named package |
| The Deploy button is grayed out | There are no undeployed changes, or you lack flows.write permission | Check your login and editor status. Do not try to work around it by changing the login method or reaching a different port directly |
| Deploy warns of an unknown, invalid, or unused config | Something in the diff references a node or config that is not resolving cleanly | Stop. Select each affected node and read its Information and Help. Do not deploy anyway just to see "Success" |
| Other automations restarted after you deployed | The scope was wider than intended, or a config node change marked nodes as changed | Note which scope you used. Full restarts everything; Modified Flows restarts a whole flow; Modified Nodes restarts only changed and removed nodes — but a shared config node can still widen that last one |
| Pressing Inject produces no Debug output | A wire is missing, Debug is disabled, or the deploy did not actually succeed | Confirm all three nodes are connected, Debug is enabled with its sidebar tab open, and the last Deploy finished clean |
| The output still reads "Original message" | A wire bypasses Change, or the Change rule targets the wrong property or type | Check whether Change actually sits between Inject and Debug, that its rule targets msg.payload, and that the value is typed as a string |
| A message appears right after Deploy, with no press | Inject's once, a repeat interval, or a schedule is still active | Open Inject and disable all three before deploying again. This exercise depends on there being no automatic trigger at all |
| One press produces two or more Debug rows | A duplicate wire, a second Inject or Debug node, or another tab doing the same thing | Check for duplicates. The source-node name shown in Debug can help you find the extra one |
| Debug shows the whole object instead of one value | Debug's output field is set to the complete message rather than msg.payload | Change the output field to msg.payload, and clear the sidebar of anything from an earlier flow before sharing a screenshot |
| Import reports an unknown node | The JSON references a package that is not installed | Do not install an unfamiliar package on the strength of an import error. This site's reference JSON uses only 5.0.2's core Inject, Change, and Debug nodes — verify the file and your own runtime version |
| Deploy lists changes from a flow you did not touch | Someone else's draft, or leftover dirty state from an earlier session | Stop. Identify the owner and purpose of every change, then separate or revert whatever is outside this task. Do not reach for Full just to bypass the review |
| Deploy has no effect after changing the add-on theme | A theme is add-on configuration, not flow configuration | Restart the add-on. Deploy only ever applies to flows |
Does dragging a node onto the canvas start it?
Does Modified Nodes really leave every other node completely alone?
Why does two quick presses of Inject give different _msgid values?
Should I build the flow by hand or import the JSON?
01-first-flow.json, still confirm the preview shows exactly three core nodes with no automatic Inject, and still run the pre-deploy checklist before clicking Deploy.Why restrict Debug to msg.payload instead of showing the whole message?
msg is still an object with more inside it; later chapters look at its other fields once there is a reason to.Which Deploy scope should I actually use for this exercise?
Does seeing a node in the Palette mean it is safe to use?
Why does Deploy have no effect after I change the Add-on theme?
What should I do if the Deploy list includes someone else's draft changes?
Why does the first flow not control a Home Assistant light directly?
The Import preview shows the correct three nodes. Can I deploy immediately?
Where to go from here
You have sent a message through three wires and watched exactly what left each one.
Part 3 opens up msg itself: what a payload looks like once it is a number, an object, or an array rather than a plain string, what msg.topic is actually for, and how the Switch and Change nodes read those values to send a message down one branch instead of another. The Inject-Change-Debug habits from this part — disable automatic triggers, restrict Debug's output, check the diff before you deploy — carry forward unchanged into every flow after this one.
Part 2 of the WoowTech Complete Node-RED Guide series on the Apporo blog.
Adapted from the WoowTech Complete Node-RED Guide, produced by WoowTech and released under CC BY 4.0. This adaptation is published by Apporo under the same licence.
Light · Air · Water · Control · apporo