Managing State in Node-RED Using the Context API
Learn how to use Node-RED's Context API to maintain state across flows, implement persistent storage, and avoid common memory pitfalls in your automation logic.
16 Sept 2025, 09:36 UTC

Solving the Stateless Message Problem
By default, Node-RED is stateless. Each message (msg) that passes through a flow is independent; once a message leaves a node, that node "forgets" everything about it. This creates a problem when you need to remember a value from a previous event—such as comparing a current temperature reading to the last one or tracking whether a security system is armed.
The solution is the Context API. Context allows you to store data in a memory space that persists across different message cycles, enabling you to maintain state across a single node, a specific flow tab, or the entire Node-RED instance.
Understanding Context Scopes
Node-RED provides three distinct levels of scope. Choosing the correct one prevents data leakage and reduces the risk of accidental overwrites.
- Node Context: Local to a single node. Use this for internal counters or timers that no other part of the system needs to see.
- Flow Context: Shared among all nodes on a single workspace tab. Use this for state shared between related nodes (e.g., a sensor node and a logic node on the same page).
- Global Context: Shared across the entire Node-RED instance. Use this for system-wide settings, API keys, or shared device statuses.
Implementation Example: A State-Based Toggle
To implement state, you use the .get() and .set() methods within a Function node. In this example, we create a toggle that remembers if a light is "ON" or "OFF" regardless of how many messages are sent.
// Get the current state from flow context.
// If it doesn't exist yet, default to 'OFF'
let currentState = flow.get('lightState') || 'OFF';
// Determine the new state
let newState = (currentState === 'OFF') ? 'ON' : 'OFF';
// Save the new state back to flow context for the next message
flow.set('lightState', newState);
// Pass the state to the next node
msg.payload = {
state: newState,
previous: currentState
};
return msg;Verification Steps
- Deploy the Function node with the code above.
- Trigger the node multiple times using an Inject node.
- Open the Context Data sidebar in the Node-RED editor (right-hand panel).
- Click the refresh icon next to the Flow section to verify that
lightStateis updating and persisting between triggers.
Persistent Storage vs. Memory
By default, context is stored in RAM. If you restart Node-RED or the host machine, all context data is wiped. For critical state (like a user's preferred temperature setting), you must enable persistent storage in settings.js. This file lives in your Node-RED user directory (commonly ~/.node-red/settings.js) and editing it requires write access to that directory plus a Node-RED restart to take effect.
To enable filesystem persistence, locate the contextStorage object in your settings.js file and configure it as follows:
contextStorage: {
default: {
module: 'localfilesystem'
}
}After restarting Node-RED, you can confirm persistence by setting a flow variable, restarting the service again, and checking the Context Data sidebar to see whether the value survived the reboot.
Risk Warning: Persistent storage writes to the disk. If you are running Node-RED on an SD card (like a Raspberry Pi) and updating a global variable every second, you may significantly shorten the lifespan of your storage media due to excessive write cycles.
Engineering Constraints and Common Pitfalls
Race Conditions in Global Scope
When multiple flows access and modify the same global variable simultaneously, you may encounter race conditions. Because Node-RED is single-threaded but handles asynchronous events, a variable might be changed by Flow A after Flow B has read it but before Flow B has updated it. For complex state machines, consider using a dedicated state-management node or a database.
Memory Exhaustion
Storing large JSON objects or long arrays in memory context can lead to Heap Out-of-Memory errors, especially on resource-constrained hardware. Always clear out old data using flow.set('variable', undefined) or global.set('variable', undefined) when the data is no longer required.
Performance Overhead
While memory access is nearly instantaneous, persistent storage introduces I/O latency. Avoid placing .get() or .set() calls inside high-frequency loops (e.g., processing 100 messages per second) if you have enabled localfilesystem storage.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.