Node-RED context is the built-in key-value storage that lets a flow remember values between messages, such as the last temperature a sensor reported or a counter of alarms today. This explainer covers the three context scopes, how to make context survive restarts, how to use several stores at once and a clean pattern for keeping the latest MQTT value for every device.
Why Flows Need State
Messages in Node-RED are independent. When an mqtt in node emits a reading, the next node knows only what is inside that message. Many real tasks need more than that:
- Comparing a value to the previous one to detect a change or a trend.
- Answering an HTTP request with the most recent reading, even though no MQTT message is arriving right now.
- Counting events per shift, or remembering when an alarm started.
- Combining values from two topics that arrive at different times.
MQTT itself offers one form of memory through retained messages, where the broker stores the last message on a topic and hands it to new subscribers. That is great for state shared between clients, and you should use it. Context is the complementary tool for state that lives inside Node-RED. If you are unsure which side a responsibility belongs on, our explainer on what an MQTT broker does draws the line.
The Three Node-RED Context Scopes
| Scope | Visible to | Typical use | Access in a function |
|---|---|---|---|
| Node | Only the node that set it | Deadband filters, counters inside one function | context.get() / context.set() |
| Flow | All nodes on the same tab (or inside the same subflow instance) | Latest values used by several nodes on one tab | flow.get() / flow.set() |
| Global | Every node in the Node-RED instance | Shared configuration, lookup tables | global.get() / global.set() |
Pick the narrowest scope that works. A value in global context can be changed by any node on any tab, which makes bugs hard to trace. Node context is private, so nothing else can break it.
You do not have to write code to use context. The Change node can set, read, move and delete flow. and global. properties, and the Inject, Switch and Template nodes can read them. The Context Data tab in the editor sidebar shows current values for each scope and lets you refresh or delete entries while debugging.
Subflows have one extra twist: flow context inside a subflow belongs to that instance. A subflow can reach its parent flow's context with the $parent. prefix, such as flow.get("$parent.lastTemp").
Memory Versus Persistent Context
By default all context is stored in memory. It is fast, but it disappears when Node-RED restarts, when the container is recreated or when the host reboots. For anything you cannot easily rebuild, switch on file-backed storage.
Open settings.js (in ~/.node-red/ on a standard install, or /data/ in the Docker image) and find the contextStorage section:
// settings.js
module.exports = {
// ...
contextStorage: {
default: { module: "memory" },
file: { module: "localfilesystem" }
},
// ...
};
Restart Node-RED. You now have two stores: default, which stays in memory, and file, which writes JSON files under the context/ folder of your user directory. Nodes choose a store explicitly:
flow.set("lastReading", msg.payload, "file"); // persisted
flow.set("scratch", 42); // memory (default store)
const last = flow.get("lastReading", "file");
In the Change node and other editors, a small store selector appears next to flow. and global. fields once you have more than one store.
If you prefer everything persistent, set default: { module: "localfilesystem" } instead. Keep in mind how the file store works: values are cached in memory and written to disk on an interval, every 30 seconds by default, controlled by the flushInterval option. A hard crash can lose the last few seconds of changes. That is fine for latest values and counters, but context is not a database. For historical data, write to a time-series store such as InfluxDB, as shown in our Node-RED InfluxDB guide.
Also note that only JSON-serialisable values persist reliably. Functions, timers and class instances will not come back intact after a restart, so store plain objects, numbers and strings.
Sync and Async Access
With the memory and localfilesystem stores, get and set work synchronously as shown above. Some third-party stores, such as Redis-based modules, are asynchronous and require callbacks:
flow.get("count", "redis", (err, count) => {
if (err) { node.error(err, msg); return; }
count = (count || 0) + 1;
flow.set("count", count, "redis", (err) => {
if (err) { node.error(err, msg); return; }
msg.payload = count;
node.send(msg);
node.done();
});
});
Writing code with callbacks from the start costs little and means you can change the store later without rewriting every function. Be aware that a get followed by a set is not atomic. With very high message rates on an async store, two messages can read the same old count. Keep counters in node context where only one node writes them, or batch updates.
You can also read and write several keys at once by passing arrays: flow.get(["a", "b"]) returns an array of values.
Pattern: Keep the Latest MQTT Value per Device
A common Node-RED context job is a live "last known value" table: one entry per device, updated on every message, readable from anywhere in the flow. Publish some test data to the ConnectMQ public MQTT test broker, replacing c8d1 with your own random string:
mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-ctx-c8d1 \
-t "connectmq/demo/c8d1/boiler/tank1/telemetry" -m '{"level": 71.5, "temp": 63.2}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-ctx-c8d1 \
-t "connectmq/demo/c8d1/boiler/pump2/telemetry" -m '{"rpm": 1450, "temp": 41.0}'
Subscribe in Node-RED with an mqtt in node on connectmq/demo/c8d1/+/+/telemetry, output set to parsed JSON, and wire it to this function:
// Function "store latest"
const parts = msg.topic.split("/");
const deviceId = parts[3] + "/" + parts[4]; // e.g. "boiler/tank1"
const latest = flow.get("latest", "file") || {};
latest[deviceId] = {
values: msg.payload,
ts: Date.now()
};
flow.set("latest", latest, "file");
node.status({ text: Object.keys(latest).length + " devices" });
return null;
Now any node on the tab can read the table. An http in node on GET /latest followed by this function and an http response node gives you a tiny API:
// Function "serve latest"
const latest = flow.get("latest", "file") || {};
const staleMs = 5 * 60 * 1000;
const now = Date.now();
msg.payload = Object.entries(latest).map(([id, v]) => ({
device: id,
values: v.values,
ageSeconds: Math.round((now - v.ts) / 1000),
stale: now - v.ts > staleMs
}));
return msg;
The stale flag is the important detail. A latest-value table that never expires happily shows a reading from a device that died yesterday. Always store a timestamp with the value.
Node-RED Context Best Practices Checklist
- Use the narrowest scope: node, then flow, then global.
- Enable a file store and choose it explicitly for values you cannot rebuild.
- Store timestamps alongside values so you can detect stale data.
- Avoid huge objects; large context entries slow every flush and every sidebar refresh.
- Prefix global keys by purpose, for example
cfg_for configuration andcache_for lookup data. - Do not store secrets in context. Credentials belong in configuration nodes and environment variables.
- Use retained MQTT messages for state other clients need, and context for state only Node-RED needs.
- Back up the
context/folder along with your flows. Running Node-RED in containers? Our Node-RED Docker guide shows how to keep/dataon a persistent volume.
The official context documentation covers the store API and configuration options in full. For the code side of these patterns, see our Node-RED function node guide, and for the bigger picture of connecting flows to brokers, the Node-RED MQTT tutorial.
Pair Node-RED Context with ConnectMQ
Context keeps Node-RED's own memory; the broker keeps everyone else's. ConnectMQ is a fully managed MQTT service with retained messages, MQTT 5 session expiry and TLS, so devices, dashboards and Node-RED all see consistent last-known values. Its portal adds a live topic explorer and threshold alert rules for quick checks without building a flow. Start on the free Serverless plan at app.connectmq.com, no credit card required, and connect Node-RED with the generated username, password and client ID.
Frequently Asked Questions
What is the difference between flow and global context in Node-RED?
Flow context is shared only by nodes on the same tab, while global context is shared by every node in the Node-RED instance. Use flow context unless several tabs genuinely need the same value.
How do I make Node-RED context survive a restart?
Add a localfilesystem store under contextStorage in settings.js, restart Node-RED and write to that store, for example flow.set("key", value, "file").
Where does Node-RED store persistent context files?
In a context folder inside the Node-RED user directory, usually ~/.node-red/context or /data/context in Docker, organised by scope.
Should I use context or retained MQTT messages for latest values?
Use retained messages when other MQTT clients need the latest value, and context when only Node-RED needs it, for example to serve an API or compare readings. Many flows use both.



