Node-RED

Node-RED Chart History: Keep MQTT Chart Data Across Restarts

Dashboard charts forget everything when Node-RED restarts. This guide shows two reliable ways to keep Node-RED chart history for MQTT data: file-backed context storage and a small SQLite table, both replayed into Dashboard 2.0 on startup.

Line chart in Node-RED Dashboard 2.0 restoring 24 hours of MQTT temperature data to show Node-RED chart history after a restart
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Node-RED chart history disappears every time Node-RED restarts, because Dashboard charts keep their points in memory. This tutorial shows two practical ways to keep that history for MQTT data: a file-backed context store for small datasets, and a SQLite table when you want queries and longer retention. In both cases you will replay the stored data into a Dashboard 2.0 chart on startup, so operators see the last 24 hours instead of an empty grid.

Why Node-RED Charts Lose Their History

A ui-chart node in Dashboard 2.0 stores the points it has received in a server-side data store, which is why a new browser tab still shows the existing line. That store lives in the Node-RED process memory. Restart Node-RED, reboot the Raspberry Pi it runs on, or update a package and restart, and the chart starts again from nothing.

MQTT does not fill the gap by itself. A retained message keeps only the last value per topic, so a reconnecting subscriber gets one point, not a trend. A broker is a message router, not a database. If you want to understand exactly where that boundary sits, read what an MQTT broker does. History has to be stored somewhere you control, and Node-RED is a convenient place to do it.

Choosing a Storage Approach for Node-RED Chart History

Approach Good for Retention Extra install Trade-offs
File-backed context (localfilesystem) One to a few series, one point per minute Hours to a few days None Whole array rewritten on flush; not queryable
SQLite (node-red-node-sqlite) Many series, filtering by time range Weeks to months One palette node You manage table cleanup
Time-series database (InfluxDB) Many devices, aggregation, Grafana Months to years Separate server More moving parts

The rest of this article implements the first two. For the third, follow the Node-RED InfluxDB tutorial, which uses the same MQTT input and a database built for this job.

Test Data from a Free Broker

You need a stream of readings. Publish a temperature every few seconds to broker.connectmq.com, the public MQTT test broker, with a unique topic prefix. Replace a7f3k2 with your own random string; anything on the public broker is readable by everyone.

# Publishes a random temperature every 5 seconds (Ctrl+C to stop)
while true; do
  T=$(awk -v r=$RANDOM 'BEGIN { printf "%.2f", 20 + (r % 800) / 100 }')
  mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-hist-a7f3k2 \
    -t connectmq/demo/a7f3k2/room1/telemetry -m "{\"temperature\": $T}"
  sleep 5
done

In Node-RED, add an mqtt in node on connectmq/demo/a7f3k2/room1/telemetry, output "a parsed JSON object", using a broker config node with a unique client ID.

Method 1: File-Backed Context Storage

Enable a persistent context store

By default, Node-RED context lives in memory. Edit settings.js in your Node-RED user directory (usually ~/.node-red/settings.js) and define a second store that writes to disk:

// ~/.node-red/settings.js (inside module.exports = { ... })
contextStorage: {
    default: { module: "memory" },
    file: {
        module: "localfilesystem",
        config: { flushInterval: 30 }   // seconds between writes to disk
    }
},

Restart Node-RED. Keeping memory as the default and naming the file store explicitly means only the data you choose is written to disk, which matters on SD cards. Values are flushed every 30 seconds, so a hard power cut can lose up to that window; a clean shutdown flushes everything. The Node-RED context guide explains scopes and stores in more detail.

Record one point per minute

Storing every raw reading quickly produces a large array that gets rewritten on every flush. Average the readings per minute instead. Wire the mqtt in node into this function node and wire its output to the chart:

// Function node "record history" - 1 output
const RETENTION_MS = 24 * 60 * 60 * 1000;   // keep 24 hours
const value = Number(msg.payload.temperature);
if (Number.isNaN(value)) {
    return null;
}

const now = Date.now();
const minute = Math.floor(now / 60000);
let acc = context.get("acc") || { minute: minute, sum: 0, n: 0 };

if (acc.minute !== minute) {
    if (acc.n > 0) {
        let history = flow.get("tempHistory", "file") || [];
        history.push({
            x: acc.minute * 60000,
            y: Math.round((acc.sum / acc.n) * 100) / 100
        });
        const cutoff = now - RETENTION_MS;
        history = history.filter(p => p.x >= cutoff);
        flow.set("tempHistory", history, "file");
    }
    acc = { minute: minute, sum: 0, n: 0 };
}

acc.sum += value;
acc.n += 1;
context.set("acc", acc);

// Live point for the chart
return { topic: "Temperature", payload: { x: now, y: value } };

At one point per minute, a day of history is 1,440 small objects per series, well within what a context file handles comfortably.

Configure the chart

In the ui-chart node, choose a line chart, keep Series as msg.topic, set X to the key x and Y to the key y, and limit the chart to 24 hours of points so it matches your retention.

Replay history on startup

Add an inject node set to Inject once after 1 second, with no repeat, and wire it to this function node, then to the same chart:

// Function node "replay history" - 1 output
const history = flow.get("tempHistory", "file") || [];
node.status({ fill: "green", shape: "dot", text: history.length + " points" });

return [[
    { topic: "Temperature", payload: [] },        // clear the chart first
    { topic: "Temperature", payload: history }    // then send the stored array
]];

Sending an empty array clears the chart, which prevents duplicated lines after a redeploy. Sending an array of {x, y} objects adds them all at once. Restart Node-RED and the chart comes back with the last day of averages, then continues with live points.

Method 2: SQLite for Longer Retention

When you have several series, want a week or more, or want to filter by time, a database is cleaner. Install node-red-node-sqlite from the palette manager. On some platforms this compiles a native module, so give it a minute.

Create the table

Add an inject node (inject once on start) with msg.topic set to the statement below, and wire it to a sqlite node with the database file /home/pi/.node-red/history.db (adjust the path) and SQL query mode Via msg.topic.

CREATE TABLE IF NOT EXISTS readings (
  ts     INTEGER NOT NULL,
  series TEXT    NOT NULL,
  value  REAL    NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_readings ON readings (series, ts);

If your node version only executes one statement per message, send the two statements as two separate injects.

Insert each reading

Use a second sqlite node in Prepared Statement mode with this SQL:

INSERT INTO readings (ts, series, value) VALUES ($ts, $series, $value)

Feed it from a function node that builds the parameters:

// Function node "to sqlite params"
const value = Number(msg.payload.temperature);
if (Number.isNaN(value)) return null;
msg.params = { $ts: Date.now(), $series: "Temperature", $value: value };
return msg;

Query on startup and clean up daily

On startup, query the last 24 hours and send the rows to the chart. A sqlite node in Via msg.topic mode returns the rows as an array in msg.payload:

// Function node "history query" - triggered by an inject once on start
msg.topic = "SELECT ts AS x, value AS y FROM readings " +
            "WHERE series = 'Temperature' AND ts > " + (Date.now() - 86400000) +
            " ORDER BY ts";
return msg;

Wire the result through a change node that sets msg.topic to Temperature, then into the chart. Add a daily inject that runs DELETE FROM readings WHERE ts < <cutoff> with the cutoff computed the same way, so the file does not grow forever.

Checklist Before You Rely on It

  1. Choose a retention period and enforce it in code or SQL, not by hope.
  2. Downsample before storing if you only display averages.
  3. Make sure the chart's point limit matches the retention, or old points vanish early.
  4. Clear the chart before replaying to avoid duplicate lines after deploys.
  5. Back up the context directory or database file with the rest of your Node-RED project.
  6. Watch disk usage on SD cards; prefer SQLite or an external database for high-frequency data.

Where the Data Should Come From in Production

History is only as good as the stream feeding it. If the broker drops, gaps appear in the chart, and on a shared public broker anyone can read or publish to your demo topics. For real installations, point the mqtt in node at a private broker with credentials and TLS. With ConnectMQ, the portal issues a username, password and client ID for each broker, and the flow change is limited to the config node: server, port 8883, TLS on, and the credentials.

A hosted MQTT broker also keeps devices publishing while you restart or upgrade Node-RED. Pair that with a persistent MQTT session and QoS 1 on the subscription, and short Node-RED restarts can be bridged by the broker queueing messages for the session, subject to your plan's session expiry. For dashboards themselves, the Dashboard 2.0 tutorial covers gauges, controls and layout, and the Node-RED MQTT tutorial covers the broker basics.

Keep Your Node-RED Data Flowing with ConnectMQ

Sign up at app.connectmq.com and start on the free Serverless plan, no credit card required. Create a broker, paste the credentials into your Node-RED config node, and your charts are fed by a private, TLS-encrypted stream. The ConnectMQ live topic explorer helps you confirm readings are arriving before you start debugging storage.

Frequently Asked Questions

Does Node-RED Dashboard 2.0 save chart data automatically?

It keeps chart data in memory so new browser tabs see existing points, but nothing is written to disk. After a Node-RED restart the chart is empty unless you replay stored data.

Can retained MQTT messages store chart history?

No. A retained message holds only the latest value for a topic. It is useful for showing current state immediately, but you need context storage or a database for a trend.

How much history can I keep in Node-RED context?

File-backed context works well for a few series downsampled to one point per minute over a day or a few days. For larger volumes, use SQLite or a time-series database such as InfluxDB.

How do I stop duplicate lines after a deploy?

Send an empty array to the chart before replaying the stored points. That clears the chart so the history is drawn once.

Related articles

Node-RED OPC UA Client to MQTT with ISA-95 Topics

Use node-red-contrib-opcua to subscribe to OPC UA variables on a PLC or simulation server, map node IDs to ISA-95 style MQTT topics, and publish values with quality and timestamps.

9 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.