Hi all, I am well aware there are a lot of threads that ask a variation of "How can I write a utility function and call it from other functions. e.g:
- RED.library.register usage
- How to create utility function in node red
- Defining and reuse a "classic" JS function
- there are many many more.
I have had some ideas floating around for some time (I've even written code for some of them) but I've always felt they lack a certain something I dont know what!
Today felt like a good day to explore some solutions (old and new)
I would appreciate your feedback / thoughts.
Please note: There are many ways this could be approached and a ton of ways it could be better, but in the spirit of achieving the "re-usable" aspect and limited time (for NR 5), please try to stay on topic ![]()
Function Node - ideas and thoughts around shared function definitions
Story
As a Node-RED user, I want to define reusable logic that can be reused across multiple function nodes, so that I can avoid code duplication and maintain consistency.
Implementing a way to define global functions would streamline this process and enhance code maintainability. Typically we suggest the user does this visually via link and link-call nodes (creating a subroutine), or by adding their functions to global context in a function node's on-start script or by adding code to the users settings.js file. However these approaches have limitations in terms of practicality/usability (if I am in a function, I typically want to stay in the function), maintainability (logic defined in a file or a random function somewhere not obvious is not ideal), reusability, and FBP familiarity / visibility.
Implementation Ideas
- Create a new configuration node type for global functions, allowing users to define and manage them in a centralized location.
- Modify the Function node to permit registration and usage of these global functions.
- Add a
RED.functions(or similar) API to facilitate the registration and calling of global functions. - A
RED.util.linkcall(or similar) API to facilitate calling link-in→logic→link-return subroutines programmatically from within function nodes.
1 - new configuration node type
- Users can create a "Global Function" config node where they can define functions.
- Function nodes can reference these global functions by name.
Pro
- Centralized management of these utility functions.
- Possibility of presenting the callable with typedoc defs (intellisense)
Con
- Somewhat additional complexity in managing config nodes
- New node type to create
- Not backwards compatible
- Chance of name collisions of function definitions?
2 - Modify Function node
Extend the Function node's setup to allow users to make the function node a place where a global function can be defined (e.g.: Function Scope "Normal" / "Global")
Pro
- Easy to use (familiar node-edit panel)
- Visual
- Could be backwards compatible (if we save the config of the node in the flow as if it were a standard function node and utilise the on-start function to register the functions in
globalcontext - anyone loading an old flow would see a disconnected function with some onstart code adding a utility function to global context)
Con
- Mode based UI to show/hide parts of the function node
- Disconnected / isolated function nodes feel odd.
- Only function nodes can be used
3 - RED.functions API
Introduce a new API within Node-RED (e.g., RED.functions) that allows users to register global functions programmatically. Something like RED.functions.register('myFunction', typedoc) then other functions can call RED.functions.myfunction
Pro
- No need to modify existing nodes or introduce new config type.
- Slightly simpler implementation.
- Could potentially provide function node type hints (intellisense)
Con
- Less user-friendly for non-developers - but then users requesting this kind of functionality are typically not beginners!
- not visual
- not backwards compatible (though a patch plugin might be possible to add this for older Node-RED versions)
4 - RED.util.linkcall API
- Introduce a new API within Node-RED (e.g.,
RED.util.linkcall) that allows users to call visually defined subroutines from a function node.
Pro
- No need to modify existing nodes or introduce new config type.
- Simpler implementation.
- leverages existing link-call nodes for reusability
- benefits from visual aspect of link-call nodes
- benefits from supporting more than just functions (core and contrib nodes can become re-usable logic too) - this was a surprising (but ultimately obvious) benefit!
Con
- Less user-friendly for non-developers - but then users requesting this kind of functionality are typically not beginners!
- not backwards compatible however a patch release on 4.x stream would be relatively simple.
- failure mode mode in NR <=3.x would be a regular node error complaining about
RED.util.linkcallbeing undefined (or similar)
- failure mode mode in NR <=3.x would be a regular node error complaining about
Decision Matrix
=5=best/easiest
=4=good/not too difficult,
=3=neutral,
=2=worse/hard
=1=complex/worst/poor
| Approach | Implementation | User Friendliness | Maintainability | Backwards Compatibility | Visual Aspect | Benefits | Score |
|---|---|---|---|---|---|---|---|
| 1 - New Config Node | 15 | ||||||
| 2 - Modify Function Node | 21 | ||||||
| 3 - RED.functions API | 20 | ||||||
| 4 - RED.util.linkcall API | 24 |
So, based on the result of that​
, I did a POC.
And, you know what - I dont hate it!
I thought I would, but no, it just works ™️and I havent had an ah but moment either - which leads me here.
The API
/**
* Utility function to call a reusable flow defined as a subroutine (link-in/link-out nodes).
*
* @param {string} target - the target of the link-in subroutine to call (can be node ID or name of link-in node)
* @param {Object} msg - the message object to pass to the subroutine
* @param {Object} [options] - call options
* @param {number} [options.timeout=5000] - the maximum time to wait for a response (default: 5000ms)
* @return {Promise<Object>} - resolves with the returned message
* @memberof @node-red/util_util
*/
function linkcall(target: string, msg: object, options?: object): Promise<object>;
Example code in a function:
// get the outside temprature, send it to an AI, return YES or NO
const msgTemp = (await RED.util.linkcall('get-temperature', msg))
const m2 = {
payload: `Outside temperature is ${msgTemp.payload}C. Should I turn heating on? (answer YES or NO only)`
}
msg.payload = (await RED.util.linkcall('ask-my-ai', m2, { timeout: 30000 })).payload
return msg
Testing: behaviour of function invocation vs existing link-call
Sequential

Nested calls
Missing/bad target
Missing link-return
Potential future improvements:
- dynamic typing - present the list of targets in the intelligence as an enum choice for the
target parameter - Support overloads to simplify calling
- e.g.
RED.util.linkcall('target-name', "a string for payload for simplicity") - e.g.
RED.util.linkcall('target-name')// simple fire and forget (no params, dont timeout, dont care about reply msg)
- e.g.





