Node-RED Node Development
This skill provides patterns, APIs, and tools for developing custom Node-RED nodes.
When to Use This Skill
- Creating new Node-RED nodes from scratch
- Implementing node message handling and lifecycle
- Building edit dialogs with HTML/jQuery
- Creating configuration nodes for shared state
- Setting up node status indicators
- Packaging nodes for npm distribution
Quick Start
To scaffold a new node, run the initialization script:
# Regular node
python scripts/init_node.py my-node --path ./nodes
# Configuration node
python scripts/init_node.py my-config --config --path ./nodes
Node Architecture
Every Node-RED node consists of two files:
JavaScript File (.js)
Defines runtime behavior:
module.exports = function(RED) {
function MyNode(config) {
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function(msg, send, done) {
send = send || function() { node.send.apply(node, arguments) };
try {
// Process message
send(msg);
done();
} catch (err) {
done(err);
}
});
node.on('close', function(removed, done) {
// Cleanup resources
done();
});
}
RED.nodes.registerType("my-node", MyNode);
}
HTML File (.html)
Defines editor appearance with three <script> sections:
- Node registration -
RED.nodes.registerType() - Edit template -
data-template-name - Help text -
data-help-name
Key Patterns
Message Handling
node.on('input', function(msg, send, done) {
// Node-RED 1.0+ compatibility
send = send || function() { node.send.apply(node, arguments) };
// Process and send
msg.payload = transform(msg.payload);
send(msg);
// Signal completion (triggers done/error)
done();
});
Error Handling
try {
// risky operation
} catch (err) {
if (done) {
done(err); // Triggers Catch nodes
} else {
node.error(err, msg); // Node-RED 0.x
}
}
Status Updates
node.status({ fill: "green", shape: "dot", text: "connected" });
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.status({}); // Clear status
Status properties:
fill:"red","green","yellow","blue","grey"shape:"ring"(outline),"dot"(filled)text: Short string (under 20 characters)
Configuration Nodes
For shared connections/state:
// In config node
RED.nodes.registerType("my-server", MyServerNode, {
credentials: {
username: { type: "text" },
password: { type: "password" }
}
});
// In client node - reference config
this.server = RED.nodes.getNode(config.server);
if (this.server) {
var connection = this.server.getConnection();
}
Cleanup (close event)
node.on('close', function(removed, done) {
if (removed) {
// Node disabled or deleted
} else {
// Flow redeploying
}
// Async cleanup with 15-second timeout
disconnect(done);
});
HTML Edit Dialog
Input Naming Convention
- Regular nodes:
id="node-input-<property>" - Config nodes:
id="node-config-input-<property>"
Form Structure
<script type="text/html" data-template-name="my-node">
<div class="form-row">
<label for="node-input-name">
<i class="fa fa-tag"></i> Name
</label>
<input type="text" id="node-input-name">
</div>
</script>
Editor Hooks
RED.nodes.registerType('my-node', {
oneditprepare: function() {
// Dialog opening - initialize widgets
},
oneditsave: function() {
// User clicked Done - return false to cancel
},
oneditcancel: function() {
// User clicked Cancel
}
});
Packaging
package.json Structure
{
"name": "@scope/node-red-my-nodes",
"version": "1.0.0",
"keywords": ["node-red"],
"node-red": {
"version": ">=2.0.0",
"nodes": {
"my-node": "nodes/my-node.js"
}
}
}
Important: Since January 2022, all new packages must use scoped names.
Local Testing
cd ~/.node-red
npm install /path/to/your/node
# Restart Node-RED
Reference Documentation
For detailed API documentation, consult the references directory:
references/node-js-api.md- JavaScript API (send, receive, close, status, logging)references/node-html-api.md- HTML API (registration, edit dialog, help text)references/config-nodes.md- Configuration node patternsreferences/status-and-context.md- Status indicators and context storagereferences/packaging.md- npm packaging and publishing
To search for specific API patterns:
grep -r "pattern" references/
Common Mistakes to Avoid
- Forgetting
RED.nodes.createNode()- Must be called first in constructor - Not handling Node-RED 0.x compatibility - Use
send = send || ...pattern - Blocking in input handler - Use async patterns for I/O
- Not cleaning up in close - Always handle the
closeevent - Wrong input ID prefix -
node-input-vsnode-config-input- - Mismatched type names - JS and HTML
registerTypemust match - Missing credentials in both files - Define in both JS and HTML
- Exceeding 15-second close timeout - Runtime will force-kill