or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdcli.mdfunction-nodes.mdhttp-services.mdindex.mdnode-development.mdruntime.mdutilities.md

node-development.mddocs/

0

# Node Development

1

2

APIs and patterns for creating custom Node-RED node types. This covers the node registration system, lifecycle management, and integration with the Node-RED runtime for building reusable node modules.

3

4

## Capabilities

5

6

### Node Registration

7

8

Core API for registering new node types with the Node-RED runtime.

9

10

```javascript { .api }

11

/**

12

* Node registration function (available in node modules)

13

* @param type - Node type identifier

14

* @param constructor - Node constructor function

15

*/

16

module.exports = function(RED) {

17

RED.nodes.registerType(type: string, constructor: NodeConstructor): void;

18

};

19

20

/**

21

* Node constructor interface

22

*/

23

interface NodeConstructor {

24

(this: NodeInstance, config: NodeConfig): void;

25

}

26

27

/**

28

* Get node instance by ID

29

* @param id - Node instance ID

30

* @returns Node instance or null

31

*/

32

RED.nodes.getNode(id: string): NodeInstance | null;

33

34

/**

35

* Create and initialize node instance

36

* @param node - Node instance object

37

* @param config - Node configuration from editor

38

*/

39

RED.nodes.createNode(node: NodeInstance, config: NodeConfig): void;

40

```

41

42

**Basic Node Example:**

43

44

```javascript

45

module.exports = function(RED) {

46

function MyCustomNode(config) {

47

// Create the node instance

48

RED.nodes.createNode(this, config);

49

50

// Store configuration

51

this.customProperty = config.customProperty;

52

53

// Set up message handler

54

this.on('input', function(msg, send, done) {

55

// Process the message

56

msg.payload = this.customProperty + ": " + msg.payload;

57

58

// Send the message

59

send(msg);

60

61

// Signal completion

62

done();

63

});

64

65

// Clean up on close

66

this.on('close', function() {

67

// Cleanup code here

68

});

69

}

70

71

// Register the node type

72

RED.nodes.registerType("my-custom-node", MyCustomNode);

73

};

74

```

75

76

### Node Instance Interface

77

78

Methods and properties available on node instances during execution.

79

80

```javascript { .api }

81

/**

82

* Node instance interface

83

*/

84

interface NodeInstance extends EventEmitter {

85

id: string;

86

type: string;

87

name?: string;

88

send(msg: NodeMessage | NodeMessage[]): void;

89

error(err: string | Error, msg?: NodeMessage): void;

90

warn(warning: string | object): void;

91

log(info: string | object): void;

92

status(status: NodeStatus): void;

93

on(event: 'input', handler: InputHandler): void;

94

on(event: 'close', handler: CloseHandler): void;

95

context(): ContextStore;

96

}

97

98

/**

99

* Input message handler

100

*/

101

interface InputHandler {

102

(msg: NodeMessage, send: SendFunction, done: DoneFunction): void;

103

}

104

105

/**

106

* Send function for output messages

107

*/

108

interface SendFunction {

109

(msg: NodeMessage | NodeMessage[]): void;

110

}

111

112

/**

113

* Done function to signal message processing completion

114

*/

115

interface DoneFunction {

116

(err?: Error): void;

117

}

118

119

/**

120

* Close handler for cleanup

121

*/

122

interface CloseHandler {

123

(removed: boolean, done: () => void): void;

124

}

125

```

126

127

**Advanced Node Example:**

128

129

```javascript

130

module.exports = function(RED) {

131

function AdvancedNode(config) {

132

RED.nodes.createNode(this, config);

133

134

const node = this;

135

let processing = false;

136

137

// Configuration

138

node.timeout = parseInt(config.timeout) || 5000;

139

node.retries = parseInt(config.retries) || 3;

140

141

// Status update

142

node.status({ fill: "green", shape: "ring", text: "ready" });

143

144

node.on('input', function(msg, send, done) {

145

// Prevent concurrent processing

146

if (processing) {

147

node.warn("Already processing, dropping message");

148

done();

149

return;

150

}

151

152

processing = true;

153

node.status({ fill: "blue", shape: "dot", text: "processing" });

154

155

// Simulate async operation with timeout

156

const timeout = setTimeout(() => {

157

processing = false;

158

node.status({ fill: "red", shape: "ring", text: "timeout" });

159

done(new Error("Processing timeout"));

160

}, node.timeout);

161

162

// Async processing

163

processMessage(msg).then(result => {

164

clearTimeout(timeout);

165

processing = false;

166

167

msg.payload = result;

168

node.status({ fill: "green", shape: "dot", text: "success" });

169

170

send(msg);

171

done();

172

}).catch(err => {

173

clearTimeout(timeout);

174

processing = false;

175

176

node.status({ fill: "red", shape: "ring", text: "error" });

177

done(err);

178

});

179

});

180

181

node.on('close', function(removed, done) {

182

// Clean up resources

183

processing = false;

184

node.status({});

185

186

if (removed) {

187

node.log("Node removed from flow");

188

}

189

190

done();

191

});

192

193

async function processMessage(msg) {

194

// Custom processing logic

195

return new Promise((resolve, reject) => {

196

setTimeout(() => {

197

resolve("Processed: " + msg.payload);

198

}, 1000);

199

});

200

}

201

}

202

203

RED.nodes.registerType("advanced-node", AdvancedNode);

204

};

205

```

206

207

### Configuration Nodes

208

209

Special nodes that provide shared configuration across multiple node instances.

210

211

```javascript { .api }

212

/**

213

* Configuration node constructor

214

*/

215

interface ConfigNodeConstructor {

216

(this: ConfigNodeInstance, config: NodeConfig): void;

217

}

218

219

/**

220

* Configuration node instance

221

*/

222

interface ConfigNodeInstance {

223

id: string;

224

type: string;

225

name?: string;

226

users: string[];

227

[key: string]: any;

228

}

229

```

230

231

**Configuration Node Example:**

232

233

```javascript

234

module.exports = function(RED) {

235

// Configuration node for API credentials

236

function ApiConfigNode(config) {

237

RED.nodes.createNode(this, config);

238

239

this.host = config.host;

240

this.port = config.port;

241

// Store credentials securely

242

this.username = this.credentials.username;

243

this.password = this.credentials.password;

244

245

// Create reusable client

246

this.client = createApiClient({

247

host: this.host,

248

port: this.port,

249

username: this.username,

250

password: this.password

251

});

252

}

253

254

// Register config node

255

RED.nodes.registerType("api-config", ApiConfigNode, {

256

credentials: {

257

username: { type: "text" },

258

password: { type: "password" }

259

}

260

});

261

262

// Node that uses the config

263

function ApiRequestNode(config) {

264

RED.nodes.createNode(this, config);

265

266

// Get config node instance

267

this.apiConfig = RED.nodes.getNode(config.apiConfig);

268

269

if (!this.apiConfig) {

270

this.error("API configuration not found");

271

return;

272

}

273

274

this.on('input', function(msg, send, done) {

275

// Use shared client from config node

276

this.apiConfig.client.request(msg.payload)

277

.then(response => {

278

msg.payload = response;

279

send(msg);

280

done();

281

})

282

.catch(err => done(err));

283

});

284

}

285

286

RED.nodes.registerType("api-request", ApiRequestNode);

287

};

288

```

289

290

### Context and Storage

291

292

Access to Node-RED's context storage system from custom nodes.

293

294

```javascript { .api }

295

/**

296

* Get node context storage

297

* @returns Context store for this node

298

*/

299

node.context(): ContextStore;

300

301

/**

302

* Get flow context storage

303

* @returns Context store for the current flow

304

*/

305

RED.util.getContext('flow', flowId): ContextStore;

306

307

/**

308

* Get global context storage

309

* @returns Global context store

310

*/

311

RED.util.getContext('global'): ContextStore;

312

```

313

314

**Context Usage in Nodes:**

315

316

```javascript

317

function StatefulNode(config) {

318

RED.nodes.createNode(this, config);

319

320

const node = this;

321

322

node.on('input', function(msg, send, done) {

323

const context = node.context();

324

325

// Get stored state

326

let counter = context.get("counter") || 0;

327

counter++;

328

329

// Update state

330

context.set("counter", counter);

331

332

// Add state to message

333

msg.counter = counter;

334

msg.payload = `Message #${counter}: ${msg.payload}`;

335

336

send(msg);

337

done();

338

});

339

}

340

```

341

342

### Message Handling Patterns

343

344

Common patterns for handling messages in custom nodes.

345

346

```javascript { .api }

347

/**

348

* Multiple output node pattern

349

*/

350

function SplitterNode(config) {

351

RED.nodes.createNode(this, config);

352

353

this.on('input', function(msg, send, done) {

354

const outputs = [];

355

356

// Prepare outputs array (one per output terminal)

357

for (let i = 0; i < config.outputs; i++) {

358

outputs[i] = null;

359

}

360

361

// Route message based on payload type

362

if (typeof msg.payload === 'string') {

363

outputs[0] = msg; // String output

364

} else if (typeof msg.payload === 'number') {

365

outputs[1] = msg; // Number output

366

} else {

367

outputs[2] = msg; // Other output

368

}

369

370

send(outputs);

371

done();

372

});

373

}

374

375

/**

376

* Batch processing node pattern

377

*/

378

function BatchNode(config) {

379

RED.nodes.createNode(this, config);

380

381

const node = this;

382

const batchSize = config.batchSize || 10;

383

let batch = [];

384

385

node.on('input', function(msg, send, done) {

386

batch.push(msg);

387

388

if (batch.length >= batchSize) {

389

// Send batch

390

const batchMsg = {

391

payload: batch.map(m => m.payload),

392

_msgid: RED.util.generateId()

393

};

394

395

send(batchMsg);

396

batch = []; // Reset batch

397

}

398

399

done();

400

});

401

402

// Timer to flush partial batches

403

const flushTimer = setInterval(() => {

404

if (batch.length > 0) {

405

const batchMsg = {

406

payload: batch.map(m => m.payload),

407

_msgid: RED.util.generateId()

408

};

409

410

node.send(batchMsg);

411

batch = [];

412

}

413

}, 30000); // Flush every 30 seconds

414

415

node.on('close', function() {

416

clearInterval(flushTimer);

417

});

418

}

419

```

420

421

### Node Module Structure

422

423

Structure and metadata for Node-RED node modules.

424

425

```javascript { .api }

426

/**

427

* Node module package.json requirements

428

*/

429

interface NodeModulePackage {

430

name: string;

431

version: string;

432

description: string;

433

"node-red": {

434

version?: string;

435

nodes: {

436

[nodeType: string]: string; // Path to node JS file

437

};

438

};

439

keywords: string[]; // Should include "node-red"

440

}

441

```

442

443

**Example package.json:**

444

445

```json

446

{

447

"name": "node-red-contrib-my-nodes",

448

"version": "1.0.0",

449

"description": "Custom nodes for Node-RED",

450

"node-red": {

451

"nodes": {

452

"my-custom-node": "nodes/my-custom-node.js",

453

"api-config": "nodes/api-config.js"

454

}

455

},

456

"keywords": [

457

"node-red",

458

"api",

459

"custom"

460

],

461

"files": [

462

"nodes/"

463

]

464

}

465

```

466

467

## Types

468

469

```javascript { .api }

470

interface NodeConfig {

471

id: string;

472

type: string;

473

name?: string;

474

[key: string]: any;

475

}

476

477

interface NodeMessage {

478

_msgid: string;

479

topic?: string;

480

payload: any;

481

[key: string]: any;

482

}

483

484

interface NodeStatus {

485

fill?: 'red' | 'green' | 'yellow' | 'blue' | 'grey';

486

shape?: 'ring' | 'dot';

487

text?: string;

488

}

489

490

interface ContextStore {

491

get(key: string | string[], store?: string, callback?: Function): any;

492

set(key: string | string[], value: any, store?: string, callback?: Function): void;

493

keys(store?: string, callback?: Function): string[];

494

}

495

```