Support for Progress Bar in Node Status API

Current Behavior:

Node-RED's custom nodes can use the status API to indicate runtime information directly in the editor. This API allows developers to show simple visual indicators such as a colored circle or square accompanied by text.

Limitations:

While this works well for brief or static messages, there is currently no built-in way to visually convey dynamic progress—such as loading states or task completion—beyond manually updating text with percentage values. This limits the UX for long-running operations where intermediate feedback would improve clarity.

Proposed Enhancement:

Introduce support for a progress/loading bar in the status API that:

  • Appears below the node (similarly positioned to current status indicators).
  • Visually expands from left to right to represent progress.
  • Accepts configurable parameters such as:
    • min and max values (e.g., 0–100 or 0–1)
    • value to indicate current progress
    • Optional label text (e.g., "Uploading... 45%")
    • Optional color customization

Benefits:

  • Provides clearer visual feedback to users for background or time-consuming tasks.
  • Makes Node-RED nodes more user-friendly and intuitive.
  • Encourages consistency and reduces the need for custom front-end hacks or external dashboards.

Example Usage:

node.status({
  progress: {
    min: 0,
    max: 100,
    value: 45,
    text: "Uploading... 45%",
    color: "#00bfff"
  }
});

This would be a valuable addition for developers building nodes for tasks like file uploads, API calls, batch processing, or streaming data.

Node Status Progress type

type NodeStatusProgress = {
  value: number;          // Current progress value (between min and max)
  min?: number;           // Defaults to 0 if undefined
  max?: number;           // Defaults to 100 if undefined
  text?: string;          // Optional label (e.g., "Uploading... 45%")
  color?: string;         // Optional color (CSS color value)
};

type NodeStatus = 
  | { fill: string; shape: string; text: string }
  | { progress: NodeStatusProgress };

Why not just make the status text say 45% loaded for example ? Much simpler. No arguing about visual style, size,thickness,etc.
The original intent of the status was to show the status of the node itself, and not the state of messages. EG showing state of a server link, and not messages being passed. But yes a function node author can do what they like. And of course “ the editor is not a dashboard” :-).

1 Like

Even though I like the idea, we shouldn't encourage developers to abuse RED.comms traffic.

Furthermore, if we support this feature, we risk receiving requests for many other visual effects.

This reminds me of the concept of an additional node status displayed in a tooltip when hovering over the node.

Counterarguments:

1. Visual Feedback Is More Effective Than Textual Feedback

Text like "45% loaded" is cognitively slower to interpret than a visual bar.
A progress bar gives immediate, intuitive insight into completion status without reading or parsing numbers.

This is particularly useful when multiple nodes are running tasks at the same time: it’s far easier to scan for progress visually than to read lines of text.

2. Accessibility and UX Best Practices Support Visual Indicators

In UI/UX design (including for dashboards, monitoring, and automation), visual progress indicators are standard and expected.

Users with cognitive or visual processing challenges may benefit from visual contrast or motion, which a progress bar can provide far better than static text.

3. Text Is Not Always Reliable or Concise Enough

A percentage in text may be truncated or not shown in full when other text is also present (e.g., long labels or status messages).

Embedding both status and percentage in one string can result in redundancy or clutter.

4. Optional and Declarative, Not Prescriptive

Introducing a progress bar doesn’t mean replacing text—it means augmenting the existing system with an optional feature.

The API can remain opinionated and simple:

status: {
  progress: { value: 42 } // uses default min/max
}

Node-RED already allows different shapes (circle, square) and colors. Adding a simple progress bar doesn’t meaningfully increase complexity—it’s still just a visual component with a few properties.

Counterarguments:

1. The Editor Is the Live Window into What’s Happening

For many users, especially developers and sysadmins, the Node-RED editor is not just a design-time tool — it’s a live console to monitor flows in real time.

It’s common to:

  • Click an inject node to trigger a script or job.
  • Watch the flow run.
  • Use node.status() to track output, debug states, or completion.

In this workflow, status feedback is essential to monitor runtime behavior without switching to a dashboard.

2. The Dashboard Isn’t Always Needed or Wanted

Not all flows are built for end users or UIs. Some automations are:

  • Internal tools
  • Scheduled maintenance tasks
  • One-off scripts
  • Integrations with external systems

For these, building a dashboard just to watch progress is overkill.

Developers naturally expect the editor to show real-time updates — and a progress bar would be the clearest, most efficient way to represent long-running operations.

3. Click → Run → Watch Is a Common Workflow

The most basic and frequent interaction in Node-RED is:

Click a button → trigger something → watch it run

Progress bars would improve that third step:

Instead of watching for "Processing: 5%", "10%", "20%", etc., scattered as text updates...

A single, clear visual bar immediately communicates state — especially useful when multiple nodes are active.

4. Reduces Reliance on Custom Debug Logs or External UI

Currently, developers often:

  • Send debug messages to the sidebar
  • Print percentage updates in the status
  • Build temporary dashboards just for internal ops

A native progress bar would reduce this friction and make the editor a more powerful, self-contained environment for interactive development.

Counterarguments:

1. Progress Bars Don't Require High-Frequency Updates

A typical progress bar only needs updates at key points:

  • Percent change (e.g., 10%, 20%, ..., 100%) — not every millisecond.
  • If developers throttle updates (e.g., every 500ms or on percentage change), traffic remains minimal and manageable.
  • Official documentation or examples could encourage best practices, such as:
Remember to use `node.status` with caution because X, Y, Z.

2. A Native Solution Reduces Abusive Workarounds

Without a progress bar, developers already abuse status updates with text like "10%", "20%", "30%" — often more frequently than needed.

Or worse, they send debug messages or invent visual tricks to simulate progress.

By providing a built-in progress API, you give them:

  • A well-documented, declarative approach
  • A clear place to update progress
  • An opportunity to optimize performance centrally (e.g., debouncing internally)

3. Traffic Control Can Be Baked In

The Node-RED core could implement rate limiting or coalescing of progress updates under the hood:

  • Only update the editor if the new value is different and X ms have passed.
  • Clamp redundant updates.

This ensures developers can't misuse it accidentally, and it protects comms bandwidth by design.

4. Set Expectations in Docs or Linting

The Node-RED team already provides guidance for good practices.

You could simply recommend limiting status updates to no more than once every 200–500ms, or only on value change.

A lint rule or runtime warning could flag nodes sending excessive status updates.

Counterarguments:

1. This Isn’t a “Visual Effect” — It’s a Semantically Useful Primitive

A progress bar isn’t just a "cool visual effect" — it’s a standard, highly meaningful UI element to convey status over time.

It’s already commonplace in tooling, from build systems to file uploads to CI/CD systems.

Unlike spinners or blinking lights, a progress bar communicates:

  • Start and end
  • Completion %
  • Estimated effort/time remaining (visually implied)

2. Precedent Already Exists — Carefully Scoped

Node-RED already supports:

  • Text
  • Colors
  • Shapes (circle/square)

Adding a single non-interactive, declarative progress bar is consistent with these primitives — it’s still a one-line status API, not a complex widget.

Just like status({ fill: "green", text: "Connected" }), the progress bar API could be strictly scoped:

  • No animation
  • No theming
  • No arbitrary shapes or styles

3. Reasonable Feature Requests Can Be Filtered

Approving this one thoughtful, useful addition does not oblige the project to accept every follow-up request.

The project already exercises judgment in what to accept or reject.

A simple principle can guide future decisions:

Is the feature a semantically meaningful enhancement of node runtime visibility without turning the editor into a dashboard?

This provides a clear framework for filtering out "visual noise" while still allowing genuine usability improvements.

4. Progress Is a Universal Concept

Unlike spinners or flashy effects, a progress bar has clear utility across:

  • File uploads/downloads
  • Batch processing
  • ETL pipelines
  • Deployment steps
  • API rate limiting

It is one of the most requested and re-implemented "visual hacks" in nodes today — it deserves a clean, native implementation.

Can you give an example of extended actions which would benefit from a progress display?

I do have a couple of places where a job takes a few seconds to complete.
These are all processes external to Node-red though, for example a complex grep of a large log file.
It's difficult to see how these could report back progress messages, and of course it's a time-honoured feature of Linux that you ask it to do something; it's silent until finished.

Any async process that wants to report status to the editor using progress bar instead of text.

  • file uploads/downloads
  • file reads/writes
  • internal jobs progress
  • external jobs progress (query external service status and display it in the editor using a progress bar)

For more details, read each counter argument above.

Thank you for the extensive list ! I won't pick each part to reply to but rather will use this image as a starting point.

  1. You can easily create a simple text based progress bar already using block based characters. Of course this could also be a simple sub flow that handled scaling for reuse etc.

  2. If it is to take up similar screen space to "XX%" it can only be 3 chars wide - IE be very concise. - If the bar is that width then I'm not sure how effective/easily readable it is. If it is wider so you can see more detail then how wide ? why waste space. (of course if you used the block approach above you can control the width yourself)

  3. If chunked by characters (my way) - then the code author is "obviously" in control of when to send a change to the status from (say) ██░░ to ███░ rather than the core have to scale any input to fit. (IE better optimise red.comms usage)

  4. re optional and declarative - doing it with characters is totally optional and non-prescriptive and doesn't need any change of API :slight_smile:

Re not a Dashboard - I agree lots of developers do use it in monitor mode. I also manage to monitor file transfers using SCP with text only output just fine. Due to the nature of Node-RED with nodes all over having to watch all over the screen is not immediately more readable whether text or a little bar - If I move / layout all the nodes so they line up to make them more readable I am heading rapidly towards a dashboard being a better option.

2 Likes

You are a genius. I didn't think about using ██░░ ███░. I really liked it. Can I remove the square/circle icon, and color the progress bar "text" with ANSI codes?

Remove the Icon yes - instead of sending the status object - just send a string only.... msg.status("███░ almost there");

Ansi codes/chalk - not tried... suspect not but hey let's try.

(quick test says no...)

Ansi codes are a terminal only feature :frowning: But Im ok with this solution.

Maybe the editor core could provide a utils function to help node authors to create this progress bar using those characters? This would ensure style consistency across nodes

[{"id":"752dc8f5a6176b74","type":"inject","z":"0398035473c0bf77","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"█░░░ 25%","payloadType":"str","x":110,"y":180,"wires":[["c5fb61646aa9f4d5"]]},{"id":"c5fb61646aa9f4d5","type":"function","z":"0398035473c0bf77","name":"function 1","func":"node.status(msg.payload)\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":180,"wires":[[]]},{"id":"564638dacc45314f","type":"inject","z":"0398035473c0bf77","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"██░░ 50%","payloadType":"str","x":110,"y":220,"wires":[["c5fb61646aa9f4d5"]]},{"id":"1d8764c7993a7749","type":"inject","z":"0398035473c0bf77","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"███░ 75%","payloadType":"str","x":110,"y":260,"wires":[["c5fb61646aa9f4d5"]]},{"id":"99ef71ab4e02b89a","type":"inject","z":"0398035473c0bf77","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"████ 100%","payloadType":"str","x":120,"y":300,"wires":[["c5fb61646aa9f4d5"]]}]

giphy

1 Like

Don't forget you can also use Unicode characters (but may be browser/language specific) - eg Unicode Character Table - Full List of Unicode Symbols (◕‿◕) SYMBL if you need finer detail.

1 Like

Just for fun - inspired by Dave ©

chrome_5DnsXKRZTs

[{"id":"51e8f7756ce214e0","type":"function","z":"3eb62d10d1081306","name":"progress","func":"function generateProgressBar(percentage) {\n    // Ensure percentage is within valid range [0..100]\n    percentage = +percentage\n    if (isNaN(percentage)) {\n        node.status({ fill: \"red\", shape: \"dot\", text: \"NaN\" });\n        return\n    }\n    if (percentage < 0) percentage = 0;\n    if (percentage > 100) percentage = 100;\n\n    const INNER_BAR_LENGTH = 20; // Length of the progress bar body (excluding brackets)\n    const FILLED_CHAR = '█';\n    const EMPTY_CHAR = '░';\n    // const OPEN_BRACKET = '‖';\n    // const CLOSE_BRACKET = '‖';\n\n    // const FILLED_CHAR = '■';\n    // const EMPTY_CHAR = '&nbsp;' // ' ';\n    // const OPEN_BRACKET = '[';\n    // const CLOSE_BRACKET = ']';\n\n            // const FILLED_CHAR = '⬛';\n            // const EMPTY_CHAR = '⬜';\n\n    // const FILLED_CHAR = '━';\n    // const EMPTY_CHAR = '┄';\n    const OPEN_BRACKET = '┣';\n    const OPEN_BRACKET0 = '┠';\n    const CLOSE_BRACKET = '┨';\n    const CLOSE_BRACKET100 = '┫';    \n\n    // Calculate the number of filled and empty characters\n    const filledChars = Math.round((percentage / 100) * INNER_BAR_LENGTH);\n    const emptyChars = INNER_BAR_LENGTH - filledChars;\n\n    // Construct the bar part\n    const ob = filledChars === 0 ? OPEN_BRACKET0 : OPEN_BRACKET\n    const cb = filledChars === INNER_BAR_LENGTH ? CLOSE_BRACKET100 : CLOSE_BRACKET\n    const bar = ob + FILLED_CHAR.repeat(filledChars) + EMPTY_CHAR.repeat(emptyChars) + cb;\n\n    // Format the percentage string (e.g., \"  5%\", \" 50%\", \"100%\")\n    const percentageString = (percentage.toString() + '%').padStart(5, ' ');\n\n    let progressBar = bar + percentageString;\n\n    return progressBar;\n}\nconst status = generateProgressBar(msg.payload)\nnode.status(status);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":300,"wires":[[]]},{"id":"d1016f5ee8390c11","type":"inject","z":"3eb62d10d1081306","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"blah blah","payloadType":"str","x":320,"y":200,"wires":[["51e8f7756ce214e0"]]},{"id":"df2f875d584becf0","type":"inject","z":"3eb62d10d1081306","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"32.8","payloadType":"num","x":310,"y":260,"wires":[["51e8f7756ce214e0"]]},{"id":"cf5c5cfe01c5a8d5","type":"inject","z":"3eb62d10d1081306","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"94.1","payloadType":"num","x":310,"y":300,"wires":[["51e8f7756ce214e0"]]},{"id":"35e6a55752b00bc9","type":"inject","z":"3eb62d10d1081306","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-1000","payloadType":"num","x":310,"y":360,"wires":[["51e8f7756ce214e0"]]},{"id":"d27769cacf7185ce","type":"inject","z":"3eb62d10d1081306","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"999999","payloadType":"num","x":310,"y":400,"wires":[["51e8f7756ce214e0"]]},{"id":"b67764bafd6b13b0","type":"inject","z":"3eb62d10d1081306","name":"microsoft progress bar","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[0,20,40,50,60,70,75,80,85,90,91,92,93,94,95,96,96,97,97,97,98,98,98,98,98.5,98.5,98.5,98.5,99,99,99,99,99,99.1,99.1,99.2,99.2,99.3,99.3,99.4,99.4,99.5,99.5,99.6,99.6,99.6,99.7,99.7,99.7,99.7,99.8,99.8,99.8,99.8,99.8,99.8,99.9,99.9,99.9,99.9,99.9,99.9,99.9,99.9,99.9,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,99.95,100]","payloadType":"json","x":360,"y":460,"wires":[["92f60624bbf072fc"]]},{"id":"92f60624bbf072fc","type":"split","z":"3eb62d10d1081306","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":350,"y":520,"wires":[["5490db128dc5347c"]]},{"id":"5490db128dc5347c","type":"delay","z":"3eb62d10d1081306","name":"0.3s","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"0.3","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":470,"y":520,"wires":[["51e8f7756ce214e0"]]}]
4 Likes

It would be nice if we could make it render from the far left when the circle/square isnt rendered