
Nodered
- 18 installs
- Updated January 28, 2026
- szkocot/skills
Helps with ai & agent building tasks.
About
nodered is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- nodered
- AI & Agent Building
- AI-coding skill
Nodered by the numbers
- 18 all-time installs (skills.sh)
- Ranked #10,710 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/szkocot/skills --skill noderedAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| Last updated | January 28, 2026 |
| Repository | szkocot/skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Node-RED
Node-RED is a flow-based programming tool for event-driven applications, enabling visual wiring of nodes to create automation workflows.
Core Concepts
Message Object
Messages flow between nodes via msg object:
{
payload: "data", // Primary data
topic: "category", // Message type/category
_msgid: "abc123" // Auto-generated ID
}Node Types
- Input: Inject, HTTP In, MQTT In - initiate flows
- Processing: Function, Change, Switch - transform data
- Output: Debug, HTTP Response, MQTT Out - send results
- Utility: Delay, Trigger, Split/Join - control flow
Flow JSON Structure
Flows stored in flows.json:
[
{
"id": "node-uuid",
"type": "inject",
"name": "Timer",
"repeat": "5",
"payload": "hello",
"payloadType": "str",
"x": 100,
"y": 100,
"wires": [["next-node-id"]]
},
{
"id": "next-node-id",
"type": "debug",
"name": "Output",
"active": true,
"complete": "payload",
"x": 300,
"y": 100,
"wires": []
}
]Key properties:
id: Unique UUIDtype: Node type namex, y: Editor positionwires: Array of arrays mapping outputs to next node inputs
Function Node
Execute JavaScript with full context access:
// Access message
const data = msg.payload;
// Modify message
msg.payload = data.toUpperCase();
msg.timestamp = Date.now();
// Send to output
return msg;
// Multiple outputs
return [msg1, msg2];
// Send nothing
return null;Context Storage
// Node context (this node only)
context.set("key", value);
const val = context.get("key");
// Flow context (all nodes in flow)
flow.set("counter", 0);
const count = flow.get("counter");
// Global context (all flows)
global.set("config", {});
const cfg = global.get("config");Async Operations
// Using async/await
const result = await someAsyncFunction();
msg.payload = result;
return msg;
// Using node.send() for async
someAsyncFunction().then(result => {
msg.payload = result;
node.send(msg);
});
return null; // Don't return msg hereCommon Nodes
Change Node
Modify message properties without code:
- Set:
msg.payloadto value - Change: Replace text in property
- Move: Rename property
- Delete: Remove property
Switch Node
Route messages based on conditions:
==,!=,<,>,<=,>=contains,matches regexis null,is not nullis of type(string, number, etc.)
Template Node
Mustache templating:
Hello {{payload.name}}!
Temperature: {{payload.temp}}°CIntegrations
MQTT
Subscribe (MQTT In):
Topic: home/sensors/#
QoS: 0/1/2
Output: parsed JSON or stringPublish (MQTT Out):
Topic: home/commands/light
Retain: true/false
QoS: 0/1/2HTTP
Create endpoint (HTTP In → HTTP Response):
Method: GET/POST/PUT/DELETE
URL: /api/dataMake request (HTTP Request):
Method: GET
URL: https://api.example.com/data
Return: parsed JSONWebSocket
Real-time bidirectional communication:
- WebSocket In: Listen for messages
- WebSocket Out: Send to clients
Debugging
Debug Node
- Output to sidebar panel
- Show complete msg or specific property
- Add to status bar
Console Logging
// In function node
node.warn("Warning message"); // Yellow, shows in debug
node.error("Error message"); // Red, triggers catch
console.log(msg); // Terminal onlyStatus Indicators
node.status({
fill: "green", // green, yellow, red, grey
shape: "dot", // dot, ring
text: "connected"
});
node.status({}); // Clear statusError Handling
Catch Node
Catches errors from nodes in same flow:
[Any Node] → error → [Catch Node] → [Handle Error]Try/Catch in Function
try {
const result = JSON.parse(msg.payload);
msg.payload = result;
return msg;
} catch (e) {
node.error("Parse failed: " + e.message, msg);
return null;
}Configuration (settings.js)
Location: ~/.node-red/settings.js
module.exports = {
// Server
uiPort: 1880,
httpAdminRoot: '/admin',
httpNodeRoot: '/api',
// Flows
flowFile: 'flows.json',
credentialSecret: "your-secret-key",
// Security
adminAuth: {
type: "credentials",
users: [{
username: "admin",
password: "$2b$08$hash...", // bcrypt hash
permissions: "*"
}]
},
// Logging
logging: {
console: { level: "info" }
},
// Context storage
contextStorage: {
default: { module: "memory" },
persistent: { module: "localfilesystem" }
},
// Function node globals
functionGlobalContext: {
moment: require('moment'),
_: require('lodash')
}
};Home Assistant Integration
Use node-red-contrib-home-assistant-websocket:
Call Service Node
Domain: light
Service: turn_on
Entity: light.living_room
Data: {"brightness": 255}Entity Node
Monitor state changes:
Entity: sensor.temperature
Output on: state changeGet Entities Node
Query current state:
Search: entity_id contains "light"
Output: array of entitiesReference
- Custom nodes - Full node development guide
- Flow patterns - Common automation patterns
- Home Assistant - HA integration details
Custom Node Development
Complete guide for creating Node-RED custom nodes.
Table of Contents
Package Structure
node-red-contrib-mynode/
├── package.json
├── mynode.js # Node implementation
└── mynode.html # Editor UI and helppackage.json
{
"name": "node-red-contrib-mynode",
"version": "1.0.0",
"description": "Custom node for Node-RED",
"keywords": ["node-red"],
"node-red": {
"nodes": {
"mynode": "mynode.js"
}
},
"dependencies": {},
"devDependencies": {
"node-red-node-test-helper": "^0.3.0"
}
}Naming conventions:
node-red-contrib-*for community nodesnode-red-node-*for official nodes
JavaScript Implementation
Basic Node
module.exports = function(RED) {
function MyNode(config) {
RED.nodes.createNode(this, config);
const node = this;
// Store config properties
node.property = config.property;
// Handle input messages
node.on('input', function(msg, send, done) {
// Process message
msg.payload = processData(msg.payload);
// Send output (use send function for Node-RED 1.0+)
send(msg);
// Signal completion
if (done) done();
});
// Cleanup on close
node.on('close', function(done) {
// Cleanup resources
done();
});
}
RED.nodes.registerType("mynode", MyNode);
};Multiple Outputs
node.on('input', function(msg, send, done) {
const msg1 = { payload: "output 1" };
const msg2 = { payload: "output 2" };
// Send to specific outputs [output1, output2]
send([msg1, msg2]);
// Send to first output only
send([msg1, null]);
// Send multiple messages to one output
send([[msg1, msg2], null]);
done();
});Status Indicators
// Show status
node.status({
fill: "green", // green, yellow, red, grey, blue
shape: "dot", // dot (filled), ring (outline)
text: "connected"
});
// Clear status
node.status({});Error Handling
node.on('input', function(msg, send, done) {
try {
// Process
msg.payload = riskyOperation(msg.payload);
send(msg);
done();
} catch (err) {
// Report error (triggers Catch node)
done(err);
// Or: node.error(err, msg);
}
});Async Operations
node.on('input', async function(msg, send, done) {
try {
const result = await asyncOperation();
msg.payload = result;
send(msg);
done();
} catch (err) {
done(err);
}
});Context Access
// Node context (per node instance)
const nodeContext = node.context();
nodeContext.set("key", value);
const val = nodeContext.get("key");
// Flow context (shared in flow)
const flowContext = node.context().flow;
flowContext.set("key", value);
// Global context (shared everywhere)
const globalContext = node.context().global;
globalContext.set("key", value);
// Async context operations
await nodeContext.set("key", value);
const val = await nodeContext.get("key");Using Config Nodes
function MyNode(config) {
RED.nodes.createNode(this, config);
// Get reference to config node
this.server = RED.nodes.getNode(config.server);
if (this.server) {
// Use config node properties
const host = this.server.host;
const token = this.server.credentials.token;
}
}HTML Definition
Node Registration
<script type="text/javascript">
RED.nodes.registerType('mynode', {
category: 'function', // Palette category
color: '#a6bbcf', // Node color
defaults: { // Editable properties
name: { value: "" },
property: { value: "payload", required: true },
operation: { value: "default" }
},
inputs: 1, // Number of inputs (0 or 1)
outputs: 1, // Number of outputs
icon: "font-awesome/fa-cog", // Node icon
label: function() { // Display label
return this.name || "my node";
},
paletteLabel: "my node", // Palette label
labelStyle: function() { // Label styling
return this.name ? "node_label_italic" : "";
},
inputLabels: "input", // Input port label
outputLabels: ["output"], // Output port labels
oneditprepare: function() { // Called when edit dialog opens
// Initialize UI elements
},
oneditsave: function() { // Called when saving
// Validate/transform before save
},
oneditcancel: function() { // Called on cancel
// Cleanup
}
});
</script>Edit Dialog Template
<script type="text/html" data-template-name="mynode">
<div class="form-row">
<label for="node-input-name">
<i class="fa fa-tag"></i> Name
</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-property">
<i class="fa fa-ellipsis-h"></i> Property
</label>
<input type="text" id="node-input-property">
</div>
<div class="form-row">
<label for="node-input-operation">
<i class="fa fa-wrench"></i> Operation
</label>
<select id="node-input-operation">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
</div>
</script>Help Documentation
<script type="text/html" data-help-name="mynode">
<p>Brief description of what this node does.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">string | buffer</span></dt>
<dd>Description of expected input</dd>
<dt class="optional">topic <span class="property-type">string</span></dt>
<dd>Optional topic description</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Description of output</dd>
</dl>
<h3>Details</h3>
<p>Detailed explanation of node behavior.</p>
<h3>References</h3>
<ul>
<li><a href="https://example.com">External docs</a></li>
</ul>
</script>TypedInput Widget
For property inputs that support multiple types:
<div class="form-row">
<label for="node-input-payload">Payload</label>
<input type="text" id="node-input-payload">
<input type="hidden" id="node-input-payloadType">
</div>
<script>
oneditprepare: function() {
$("#node-input-payload").typedInput({
type: "msg",
types: ["msg", "flow", "global", "str", "num", "bool", "json"],
typeField: "#node-input-payloadType"
});
}
</script>Configuration Nodes
Shared configuration across multiple nodes:
JavaScript
module.exports = function(RED) {
function MyConfigNode(config) {
RED.nodes.createNode(this, config);
this.host = config.host;
this.port = config.port;
// Access credentials
this.username = this.credentials.username;
this.password = this.credentials.password;
}
RED.nodes.registerType("myconfig", MyConfigNode, {
credentials: {
username: { type: "text" },
password: { type: "password" }
}
});
};HTML
<script type="text/javascript">
RED.nodes.registerType('myconfig', {
category: 'config',
defaults: {
host: { value: "localhost", required: true },
port: { value: 1883, required: true, validate: RED.validators.number() }
},
credentials: {
username: { type: "text" },
password: { type: "password" }
},
label: function() {
return this.host + ":" + this.port;
}
});
</script>
<script type="text/html" data-template-name="myconfig">
<div class="form-row">
<label for="node-config-input-host">Host</label>
<input type="text" id="node-config-input-host">
</div>
<div class="form-row">
<label for="node-config-input-port">Port</label>
<input type="text" id="node-config-input-port">
</div>
<div class="form-row">
<label for="node-config-input-username">Username</label>
<input type="text" id="node-config-input-username">
</div>
<div class="form-row">
<label for="node-config-input-password">Password</label>
<input type="password" id="node-config-input-password">
</div>
</script>Using Config Node
<div class="form-row">
<label for="node-input-server">Server</label>
<input type="text" id="node-input-server">
</div>
<script>
defaults: {
server: { value: "", type: "myconfig" }
}
</script>Credentials
Credentials are stored encrypted, separate from flows.
Definition
RED.nodes.registerType("mynode", MyNode, {
credentials: {
apiKey: { type: "text" }, // Shown in UI
apiSecret: { type: "password" } // Hidden in UI
}
});Access
function MyNode(config) {
RED.nodes.createNode(this, config);
// Access credentials
const apiKey = this.credentials.apiKey;
const apiSecret = this.credentials.apiSecret;
}Testing
Using node-red-node-test-helper:
const helper = require("node-red-node-test-helper");
const myNode = require("../mynode.js");
describe('mynode Node', function() {
afterEach(function() {
helper.unload();
});
it('should be loaded', function(done) {
const flow = [{ id: "n1", type: "mynode", name: "test" }];
helper.load(myNode, flow, function() {
const n1 = helper.getNode("n1");
n1.should.have.property('name', 'test');
done();
});
});
it('should transform payload', function(done) {
const flow = [
{ id: "n1", type: "mynode", wires: [["n2"]] },
{ id: "n2", type: "helper" }
];
helper.load(myNode, flow, function() {
const n1 = helper.getNode("n1");
const n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', 'expected');
done();
});
n1.receive({ payload: "input" });
});
});
});Run tests:
npm testFlow Patterns
Common Node-RED automation patterns and examples.
Table of Contents
- Message Routing
- Data Transformation
- Rate Limiting
- State Management
- Error Handling
- HTTP API Patterns
- MQTT Patterns
- Scheduling
Message Routing
Switch-based Routing
Route messages based on property values:
[
{
"id": "switch1",
"type": "switch",
"property": "payload.type",
"rules": [
{"t": "eq", "v": "temperature"},
{"t": "eq", "v": "humidity"},
{"t": "else"}
],
"outputs": 3
}
]Topic-based Routing
// Function node for dynamic routing
const routes = {
"sensors/temp": 0,
"sensors/humidity": 1,
"alerts": 2
};
const output = routes[msg.topic];
if (output !== undefined) {
return [Array(3).fill(null).map((_, i) => i === output ? msg : null)];
}
return null;Parallel Processing
Split work and rejoin:
[Split] → [Process 1] → [Join]
→ [Process 2] →
→ [Process 3] →Split node settings:
- Split on: Fixed length / String / Array
- Join mode: Automatic / Manual
Data Transformation
JSON Processing
// Parse JSON string
msg.payload = JSON.parse(msg.payload);
// Extract nested data
msg.payload = msg.payload.data.results;
// Transform array
msg.payload = msg.payload.map(item => ({
id: item.id,
value: item.reading * 1.8 + 32 // Celsius to Fahrenheit
}));
return msg;Change Node Transformations
{
"rules": [
{"t": "set", "p": "payload.timestamp", "pt": "msg", "to": "", "tot": "date"},
{"t": "move", "p": "payload.temp", "pt": "msg", "to": "payload.temperature", "tot": "msg"},
{"t": "delete", "p": "payload.raw", "pt": "msg"},
{"t": "set", "p": "payload.unit", "pt": "msg", "to": "celsius", "tot": "str"}
]
}Aggregation
Collect messages and aggregate:
// Initialize on first message
let data = flow.get("buffer") || [];
// Add to buffer
data.push(msg.payload);
// Check if complete
if (data.length >= 10) {
const avg = data.reduce((a, b) => a + b, 0) / data.length;
flow.set("buffer", []);
msg.payload = { average: avg, count: data.length };
return msg;
}
flow.set("buffer", data);
return null; // Don't send yetRate Limiting
Delay Node
- Fixed delay: Wait N seconds
- Rate limit: Max N messages per time period
- Drop intermediate: Only pass latest
Debounce Pattern
Only process after activity stops:
// Function node with context
const DEBOUNCE_MS = 1000;
const timerId = context.get("timer");
if (timerId) {
clearTimeout(timerId);
}
const newTimer = setTimeout(() => {
node.send(msg);
context.set("timer", null);
}, DEBOUNCE_MS);
context.set("timer", newTimer);
return null;Throttle Pattern
Limit to one message per interval:
const THROTTLE_MS = 5000;
const lastSent = context.get("lastSent") || 0;
const now = Date.now();
if (now - lastSent >= THROTTLE_MS) {
context.set("lastSent", now);
return msg;
}
return null;State Management
State Machine
const states = {
idle: { start: "running", reset: "idle" },
running: { stop: "stopped", error: "error" },
stopped: { start: "running", reset: "idle" },
error: { reset: "idle" }
};
const currentState = flow.get("state") || "idle";
const action = msg.payload.action;
const nextState = states[currentState]?.[action];
if (nextState) {
flow.set("state", nextState);
msg.payload = { previous: currentState, current: nextState };
return msg;
}
node.warn(`Invalid transition: ${currentState} + ${action}`);
return null;Persistent Context
Configure in settings.js:
contextStorage: {
default: { module: "memory" },
file: { module: "localfilesystem" }
}Use persistent storage:
// Store persistently
flow.set("config", data, "file");
// Retrieve
const config = flow.get("config", "file");Error Handling
Catch-all Pattern
[Any Nodes] → (error) → [Catch] → [Log Error] → [Notify]Catch node configuration:
- Catch errors from: All nodes / Selected nodes
- Include: node.error() / node.error(msg)
Retry Pattern
const MAX_RETRIES = 3;
const retries = msg._retries || 0;
try {
// Attempt operation
msg.payload = await riskyOperation(msg.payload);
return [msg, null]; // Success output
} catch (err) {
if (retries < MAX_RETRIES) {
msg._retries = retries + 1;
msg._error = err.message;
return [null, msg]; // Retry output (wire back)
}
node.error("Max retries exceeded", msg);
return null;
}Circuit Breaker
const THRESHOLD = 5;
const TIMEOUT = 60000;
let failures = flow.get("failures") || 0;
let circuitOpen = flow.get("circuitOpen") || false;
let openTime = flow.get("openTime") || 0;
// Check if circuit should close
if (circuitOpen && Date.now() - openTime > TIMEOUT) {
circuitOpen = false;
failures = 0;
}
if (circuitOpen) {
node.warn("Circuit open - request blocked");
return null;
}
try {
msg.payload = await operation();
failures = 0;
flow.set("failures", failures);
return msg;
} catch (err) {
failures++;
if (failures >= THRESHOLD) {
circuitOpen = true;
flow.set("circuitOpen", true);
flow.set("openTime", Date.now());
}
flow.set("failures", failures);
throw err;
}HTTP API Patterns
REST Endpoint
[HTTP In GET /api/items] → [Function: Get Items] → [HTTP Response]
[HTTP In POST /api/items] → [Function: Create Item] → [HTTP Response]
[HTTP In GET /api/items/:id] → [Function: Get Item] → [HTTP Response]Function for GET /api/items:
const items = global.get("items") || [];
msg.payload = items;
msg.headers = { "Content-Type": "application/json" };
return msg;Function for POST /api/items:
const items = global.get("items") || [];
const newItem = {
id: Date.now(),
...msg.payload
};
items.push(newItem);
global.set("items", items);
msg.payload = newItem;
msg.statusCode = 201;
return msg;Webhook Handler
[HTTP In POST /webhook] → [Validate] → [Process] → [HTTP Response]
↓
[Queue for later]Validation function:
const secret = env.get("WEBHOOK_SECRET");
const signature = msg.req.headers["x-signature"];
if (!verifySignature(msg.payload, signature, secret)) {
msg.statusCode = 401;
msg.payload = { error: "Invalid signature" };
return [null, msg]; // Error output
}
return [msg, null]; // Success outputMQTT Patterns
Request/Response
[Inject] → [Set Topic] → [MQTT Out: request/topic]
[MQTT In: response/+] → [Process Response]With correlation:
// Request
msg.correlationId = RED.util.generateId();
msg.topic = `request/${msg.correlationId}`;
flow.set(`pending_${msg.correlationId}`, { timestamp: Date.now() });
return msg;
// Response handler
const pending = flow.get(`pending_${msg.correlationId}`);
if (pending) {
flow.set(`pending_${msg.correlationId}`, null);
msg.roundTrip = Date.now() - pending.timestamp;
return msg;
}Fan-out/Fan-in
Publish to multiple topics, collect responses:
// Fan-out
const devices = ["device1", "device2", "device3"];
const requestId = RED.util.generateId();
flow.set(`request_${requestId}`, {
expected: devices.length,
received: [],
timestamp: Date.now()
});
const messages = devices.map(d => ({
topic: `command/${d}`,
payload: { requestId, command: msg.payload }
}));
return [messages];Scheduling
Cron-based
Inject node with cron expression:
0 0 * * * - Daily at midnight
0 */6 * * * - Every 6 hours
0 9 * * 1-5 - Weekdays at 9am
*/5 * * * * - Every 5 minutesTime-window Pattern
Only process during certain hours:
const hour = new Date().getHours();
const START_HOUR = 9;
const END_HOUR = 17;
if (hour >= START_HOUR && hour < END_HOUR) {
return msg;
}
// Outside hours - queue or drop
node.warn("Outside operating hours");
return null;Sunrise/Sunset
Using node-red-contrib-sun-position:
[Sun Position] → [Switch: altitude > 0] → [Daytime flow]
→ [Nighttime flow]Home Assistant Integration
Detailed guide for integrating Node-RED with Home Assistant using node-red-contrib-home-assistant-websocket.
Table of Contents
Setup
Installation
cd ~/.node-red
npm install node-red-contrib-home-assistant-websocketOr via Node-RED: Menu → Manage palette → Install → search "home-assistant-websocket"
Configuration Node
Create a Home Assistant server config:
1. Base URL: http://homeassistant.local:8123 or http://IP:8123 2. Access Token: Long-lived access token from HA
- HA Profile → Long-Lived Access Tokens → Create Token
3. Enable global context: Optional, exposes homeassistant.homeAssistant.states
Connection Settings
Base URL: http://192.168.1.100:8123
Access Token: eyJ0eXAiOi...
Cache Autocomplete: Yes (recommended)
Enable Global Context Store: OptionalNode Reference
Events: all
Listen to all Home Assistant events:
Event Type: state_changed (or leave blank for all)
Output: Event data objectOutput message:
{
event_type: "state_changed",
payload: {
entity_id: "light.living_room",
new_state: { state: "on", attributes: {...} },
old_state: { state: "off", attributes: {...} }
}
}Events: state
Monitor specific entity state changes:
Entity: sensor.temperature
If State: (optional) Specific state to trigger on
Output properties: Choose what to includeOutput message:
{
payload: "22.5", // Entity state
data: {
entity_id: "sensor.temperature",
new_state: { state: "22.5", attributes: {...} },
old_state: { state: "22.0", attributes: {...} }
}
}Current State
Get current state of an entity:
Entity ID: light.kitchen
State Type: String/Number/BooleanOutput message:
{
payload: "on",
data: {
entity_id: "light.kitchen",
state: "on",
attributes: {
brightness: 255,
color_temp: 370,
friendly_name: "Kitchen Light"
},
last_changed: "2024-01-15T10:30:00Z",
last_updated: "2024-01-15T10:30:00Z"
}
}Call Service
Execute Home Assistant services:
Domain: light
Service: turn_on
Entity ID: light.living_room (or from msg.payload.entity_id)
Data: {"brightness": 255}Common service examples:
| Domain | Service | Data |
|---|---|---|
| light | turn_on | {"brightness": 255, "color_temp": 350} |
| light | turn_off | {} |
| switch | toggle | {} |
| climate | set_temperature | {"temperature": 22} |
| cover | set_cover_position | {"position": 50} |
| media_player | play_media | {"media_content_id": "...", "media_content_type": "music"} |
| notify | mobile_app_phone | {"message": "Hello", "title": "Alert"} |
| script | my_script | {} |
| scene | turn_on | {} |
| automation | trigger | {} |
Get Entities
Query multiple entities:
Search Type: Substring/Regex/List
Search: "light" or ["light.a", "light.b"]Output: Array of entity objects
Fire Event
Trigger custom events in Home Assistant:
Event: custom_event_name
Data: {"key": "value"}Get History
Retrieve entity history:
Entity ID: sensor.temperature
Start Date: JSONata expression or timestamp
End Date: JSONata expression or timestampRender Template
Process Jinja2 templates:
Template: {{ states('sensor.temperature') }}°CWebhook
Trigger flows from HA automations:
Webhook ID: my_webhook
Allowed Methods: POSTCall from HA automation:
action:
- service: rest_command.trigger_nodered
data:
webhook_id: my_webhook
payload: "{{ trigger.entity_id }}"Common Patterns
Motion-activated Light
[Events: state (motion sensor)]
→ [Switch: payload == "on"]
→ [Call Service: light.turn_on]
[Events: state (motion sensor)]
→ [Switch: payload == "off"]
→ [Delay: 5 min]
→ [Call Service: light.turn_off]Temperature-based Control
[Events: state (temp sensor)]
→ [Function: Check threshold]
→ [Switch: action]
→ [Call Service: climate.set_hvac_mode heat]
→ [Call Service: climate.set_hvac_mode cool]
→ [Call Service: climate.set_hvac_mode off]Function:
const temp = parseFloat(msg.payload);
const TARGET = 22;
const TOLERANCE = 1;
if (temp < TARGET - TOLERANCE) {
msg.action = "heat";
} else if (temp > TARGET + TOLERANCE) {
msg.action = "cool";
} else {
msg.action = "off";
}
return msg;Presence-based Automation
[Events: state (person.john)]
→ [Switch]
→ [payload == "home"] → [Scene: home]
→ [payload == "not_home"] → [Scene: away]Multi-condition Check
// Function node checking multiple states
const states = global.get("homeassistant.homeAssistant.states");
const isHome = states["person.john"].state === "home";
const isDark = parseFloat(states["sensor.illuminance"].state) < 100;
const isEvening = new Date().getHours() >= 18;
if (isHome && isDark && isEvening) {
return { payload: { entity_id: "light.living_room" } };
}
return null;Notification with Conditions
[Trigger]
→ [Current State: input_boolean.notifications]
→ [Switch: payload == "on"]
→ [Call Service: notify.mobile_app]Entity Attributes
Access entity attributes in function nodes:
// From state change event
const brightness = msg.data.new_state.attributes.brightness;
const friendly_name = msg.data.new_state.attributes.friendly_name;
// From global context (if enabled)
const states = global.get("homeassistant.homeAssistant.states");
const attrs = states["light.kitchen"].attributes;Common attributes by domain:
Light:
brightness(0-255)color_temp(mireds)rgb_color[r, g, b]effectsupported_features
Climate:
temperaturecurrent_temperaturehvac_actionpreset_mode
Sensor:
unit_of_measurementdevice_classstate_class
Cover:
current_positioncurrent_tilt_position
Service Calls
Dynamic Service Data
// Function node before Call Service
msg.payload = {
domain: "light",
service: "turn_on",
data: {
entity_id: "light.living_room",
brightness: Math.round(msg.payload.level * 2.55),
transition: 2
}
};
return msg;Service with Multiple Entities
msg.payload = {
data: {
entity_id: ["light.kitchen", "light.dining", "light.living_room"],
brightness: 200
}
};
return msg;Conditional Service
const hour = new Date().getHours();
const brightness = hour < 8 || hour > 20 ? 100 : 255;
msg.payload = {
data: {
entity_id: msg.entity_id,
brightness: brightness
}
};
return msg;Automation Examples
Adaptive Lighting
// Calculate color temp based on time
const hour = new Date().getHours();
let colorTemp;
if (hour < 6 || hour > 21) {
colorTemp = 500; // Warm
} else if (hour > 8 && hour < 18) {
colorTemp = 250; // Cool
} else {
// Transition
const progress = hour < 12
? (hour - 6) / 2
: (21 - hour) / 3;
colorTemp = 500 - (progress * 250);
}
msg.payload = {
data: {
entity_id: "light.living_room",
color_temp: Math.round(colorTemp)
}
};
return msg;Door Open Alert
[Events: state (binary_sensor.front_door)]
→ [Switch: payload == "on"]
→ [Delay: 5 min]
→ [Current State: binary_sensor.front_door]
→ [Switch: payload == "on"]
→ [Call Service: notify.mobile_app]
Data: {"message": "Front door has been open for 5 minutes"}Energy Monitoring
// Track daily energy usage
const energy = parseFloat(msg.payload);
const today = new Date().toDateString();
let daily = flow.get("daily_energy") || { date: today, total: 0 };
if (daily.date !== today) {
// New day - store yesterday and reset
const yesterday = daily;
daily = { date: today, total: 0 };
// Send daily report
node.send([null, {
payload: {
date: yesterday.date,
total_kwh: yesterday.total
}
}]);
}
daily.total = energy;
flow.set("daily_energy", daily);
msg.payload = daily;
return [msg, null];Vacation Mode
[Events: state (input_boolean.vacation_mode)]
→ [Switch: payload == "on"]
→ [Random Delay: 15-45 min]
→ [Call Service: light.toggle]
Entity: Random from list
→ [Link: loop back to delay]Function for random light:
const lights = [
"light.living_room",
"light.bedroom",
"light.kitchen"
];
const random = lights[Math.floor(Math.random() * lights.length)];
msg.payload = { data: { entity_id: random } };
return msg;