| name | nodered-fundamentals |
| description | Core vocabulary of Node-RED: nodes, flows (tabs), wires, messages, context, config nodes, design principles, and basic error handling concepts. |
| tools | ["get-flows","get-flow-nodes","get-flow-diagram","get-config-nodes","get-node-detail","get-palette-nodes","get-node-type-detail","create-flow","update-flow","delete-flow","create-node","update-node","delete-node","connect-nodes","disconnect-nodes","get-context","delete-context","inject-message","search-nodes","export-flow","import-flow","read-debug-messages","install-node","uninstall-node","refresh-staging","deploy","get-staging-status"] |
Node-RED Fundamentals
Core vocabulary, design principles, and basic error handling for working with Node-RED via the MCP tools. Read this BEFORE building any flows.
What is Node-RED
Node-RED is a flow-based, event-driven visual programming platform. You wire together "nodes" on a canvas to process messages. Each message flows through a chain of nodes, each node performing a specific operation (transform, filter, route, store, etc.).
- Event-driven: Nodes wake up when a message arrives — there is no polling.
- JSON-native: Messages are JavaScript objects with a
payload property.
- Browser-based editor: The UI runs in a browser, but the runtime and flows execute server-side.
- Admin API: The MCP tools communicate with Node-RED through its REST/WebSocket Admin API. All changes to flows (create, update, delete, deploy) go through this API.
Node
A node is the fundamental building block. Each node has a type that determines what it does.
Key fields on every node:
| Field | Description |
|---|
id | Unique UUID (generated by the runtime or MCP tools) |
type | Palette type (e.g., "inject", "function", "debug", "http in") |
name | User-visible label (set via name property in create-node/update-node) |
z | The flow (tab) ID this node belongs to |
x, y | Canvas position (pixels, not meaningful for behavior) |
wires | Array of output connections (see Wire section below) |
MCP tools for nodes:
create-node — add a new node to a flow
update-node — merge properties onto an existing node (never wires)
delete-node — remove a node and clean up its wires
get-flow-nodes — list nodes in a flow with their current state
get-node-detail — get the full node object including large text fields (func code, template markup)
search-nodes — find nodes across all flows by name, type, or property value
Flow (Tab)
"Flow" has two meanings in Node-RED — always disambiguate:
- Flow tab (also called "flow" or "sheet"): A named tab in the editor that groups related nodes. Every node belongs to exactly one flow tab (its
z field).
- Message flow (also called "wired flow"): The path messages take through connected nodes — inject → function → debug.
Staging and Deploy
🚨 CRITICAL CONCEPT — Edits are NOT live until you deploy.
The MCP tools use a staging workspace model, identical to the Node-RED editor:
- All write operations stage locally:
create-node, connect-nodes, update-node, delete-node, etc. modify an in-memory copy of the flows. Nothing is sent to the Node-RED runtime until you explicitly deploy.
- If you forget to deploy, your changes are LOST. They only exist in staging memory.
- All read operations read from staging:
get-flows, get-flow-nodes, get-flow-diagram, etc. return data from the staged copy, reflecting all pending changes.
- Deploy pushes to runtime: Call
deploy to send all staged changes to Node-RED. Three deploy types:
"nodes" (default) — restart only modified nodes (least disruptive)
"flows" — restart only modified flow tabs
"full" — restart everything
- Check pending changes: Use
get-staging-status to see what's pending before deploying.
- Staging guard:
inject-message refuses to inject if there are undeployed changes — deploy first, then test.
- Batch then deploy: Create several nodes + wire them, then call
deploy once. Don't deploy after every single operation — batch your edits.
Rule: After ANY write operation, check the staging.deployed field in the response. If false, you have pending changes. Call deploy before testing with inject-message.
Workflow: Edit → Read (verify with diagram) → Deploy → Test (inject + debug)
In MCP contexts, "flow" typically means the tab. Use flowId to specify which tab to operate on.
MCP tools for flows:
get-flows — list all tabs/subflows with their node counts and types
create-flow — create a new empty tab
update-flow — change tab label, disabled state, info, or env vars
delete-flow — remove a tab and all its nodes
export-flow — export a tab's JSON for backup or migration
import-flow — import flow JSON into the instance
Wire
A wire connects one node's output port to another node's input. Wires are stored in the wires array on the source node.
wires array structure:
"wires": [["targetNodeId1", "targetNodeId2"], []]
- Index 0 → output port 0's wire targets
- Index 1 → output port 1's wire targets (empty if unused)
- Index 2 → output port 2's wire targets (empty if unused)
Ports are 0-indexed. The first output port is index 0.
MCP tools for wires:
connect-nodes — add a wire from one node to another (single or batch)
disconnect-nodes — remove a wire (single, clear-port, or batch)
- Never set
wires in update-node properties — wiring is managed exclusively through connect-nodes/disconnect-nodes
Message
A message (often called msg) is the JavaScript object that flows between nodes. Every message has at minimum:
| Property | Description |
|---|
payload | The primary data — can be any type (string, number, object, Buffer) |
topic | Optional routing/metadata string |
_msgid | Auto-generated unique message identifier |
| (custom) | Nodes can attach any additional properties (e.g., msg.headers, msg.statusCode) |
MCP tools:
inject-message — trigger an inject node (starts a message through the flow)
read-debug-messages — read output from debug nodes (requires comms WebSocket)
Context
Context is Node-RED's key-value storage system with three scopes:
| Scope | Lifetime | Access |
|---|
| Node | Tied to a single node | context.get(key) / context.set(key, val) in function nodes |
| Flow | All nodes in one flow tab | flow.get(key) / flow.set(key, val) in function nodes |
| Global | Entire Node-RED instance | global.get(key) / global.set(key, val) in function nodes |
⚠️ IMPORTANT: The Admin API does NOT support writing context values. The MCP tools can only read (get-context) and delete (delete-context) context. To write context, you must use a function node with context.set(), flow.set(), or global.set().
MCP tools:
get-context — read a context variable from any scope
delete-context — delete a context variable from any scope
Warning: Default in-memory context is lost when Node-RED restarts. For persistence, users must configure file-based context storage (not controllable via MCP).
Config Node
A config node (configuration node) is a shared resource node that holds reusable settings. Unlike regular nodes, config nodes are not wired into a flow — they are referenced by regular nodes via a property (e.g., an MQTT node references an mqtt-broker config node).
Key characteristics:
z field is "" (global, available to all flows) or a specific flowId (scoped to one tab)
- Not visible on the flow canvas by default (shown in a separate config node sidebar)
- Created automatically when you set a config-type property on a regular node via
create-node or update-node
⚠️ CREDENTIAL PRIVACY — critical to understand:
Node-RED never exposes real credential values (passwords, API keys, tokens, certificates) through its REST API. When you call get-flow-nodes or get-config-nodes:
- The
credentials field (e.g., { username: "xxx", password: "yyy" }) will be absent — never included.
- A missing
credentials field does NOT mean the node has no credentials configured. It means the values are hidden for security.
How to work with credentials despite this limitation:
- To see credential metadata (field names and which password-type fields are set): Use
get-node-detail on a config node. It returns a _credentials field like { user: "test67", has_password: true }. Password values are NEVER shown — only has_<field>: true/false.
- To update credentials: Use
update-node with a credentials object. You only need to send the fields you want to change — unspecified credential fields are preserved. Example: properties: { credentials: { password: "newpass" } } changes only the password.
- To set credentials on a new config node: Use
create-node with credentials inside properties. Example: properties: { broker: "localhost", port: 1883, credentials: { user: "test67", password: "mypass" } }.
- The tools auto-detect credential fields and nest them correctly — even if you send them at the top level, they will be moved to the
credentials sub-object.
MCP tools:
get-config-nodes — list all config nodes, filter by type (credentials hidden)
get-node-detail — get full details of a specific config node (credentials hidden)
Subflow
A subflow is a reusable group of nodes packaged as a single node. You define inputs/outputs as "subflow instance properties" and reuse the subflow like any palette node. Subflows appear in get-flows with type: "subflow".
Note: Full subflow creation/editing is not yet supported via MCP tools. You can view existing subflows with get-flows and get-flow-nodes, but creating or modifying subflow internals requires the Node-RED editor.
Palette
The palette is the registry of all installed node types. Each type has a module, version, category, and configurable properties.
MCP tools:
get-palette-nodes — list all available node types (paginated, alphabetical)
get-node-type-detail — get detailed info about a specific type including its configurable parameters
install-node — install a new node module from npm (may require Node-RED restart)
uninstall-node — remove a node module (⚠️ destroys all nodes of that type)
Design Principles
Tab Organization
- One tab per domain/concern — don't put unrelated logic in one tab. Separate HTTP APIs, MQTT ingestion, data processing, and dashboard into distinct tabs.
- Descriptive labels — use
create-flow or update-flow to set meaningful labels like "Weather API Ingest" not "Flow 1".
- info field — use the
info property on flows to document purpose, inputs/outputs, and dependencies. This is visible in the editor and helps anyone (including future you) understand the flow.
Node Naming
- Always set a descriptive
name on every node you create. Use create-node's properties.name or update-node.
- Anti-pattern: leaving nodes with default names (e.g.,
"function 3", "debug 2"). These are meaningless in get-flow-nodes output.
- Audit: use
search-nodes to find unnamed nodes: search for "name":"".
Documentation
- Flow-level: set the
info property on flow tabs via create-flow or update-flow.
- Node-level: use Comment nodes (
type: "comment") to annotate sections of a flow. They display as text labels on the canvas.
- Export: use
export-flow to share a documented flow as JSON for review.
Error Handling Basics
Node-RED provides a Catch node (type: "catch") for error handling. It catches errors thrown by nodes on the same flow tab.
Catch node scope:
- By default, a catch node catches errors from all nodes on the same tab.
- You can scope it to specific nodes by setting the
scope property to an array of node IDs.
Error object structure:
{
message: "Error description",
source: { id: "nodeId", type: "nodeType", name: "nodeName", count: 1 },
error: <original error object>
}
Best practices:
- Place a catch node on every tab, even if just wired to a debug node — otherwise errors are silently swallowed.
- In function nodes, use
node.error("message", msg) to throw catchable errors.
- Use
read-debug-messages to observe caught errors during development.
Link Nodes
Link nodes enable message passing across flow tabs without using global context or subflows.
| Type | Purpose |
|---|
link in | Receives messages from link out/call nodes (any tab) |
link out | Sends messages to all link in nodes with matching link name |
link call | Sends a message and waits for a response (request-reply pattern) |
Key behaviors:
- Link nodes connect by name, not by wires. All link in/out nodes with the same name form a virtual bus.
link call is the only built-in way to do synchronous request-reply across tabs.
- Link nodes are an alternative to subflows for cross-tab reuse.
MCP tools: Standard create-node, update-node, delete-node, connect-nodes apply to link nodes as well.
Environment Variables
Flows can define environment variables scoped to a single tab.
How to set them:
- Via MCP: use
create-flow or update-flow with the env array:
"env": [
{ "name": "API_KEY", "value": "sk-xxx", "type": "str" },
{ "name": "TIMEOUT", "value": "30", "type": "num" },
{ "name": "DEBUG", "value": "true", "type": "bool" }
]
- Types:
"str", "num", "bool" (controls how the value is parsed)
How to use them:
- In function nodes:
const apiKey = env.get("API_KEY");
- In template nodes:
{{env.API_KEY}}
- In any node property that supports env-var substitution:
${API_KEY}
Message-Flow Diagram
A simple inject → function → debug flow:
┌──────────┐ msg ┌──────────────┐ msg ┌──────────┐
│ inject │──────────▶│ function │──────────▶│ debug │
│ (start) │ │ msg.payload │ │ (output) │
└──────────┘ │ = x * 2 │ └──────────┘
└──────────────┘
1. inject node triggers → sends msg { payload: <timestamp> }
2. function node receives msg → sets msg.payload = msg.payload * 2
3. debug node receives msg → prints to debug sidebar (read via read-debug-messages)
Each node processes the message and passes it to the next node in the chain. This is the fundamental pattern: trigger → transform → output.