[Announce] Dashboard version 2.10.0



I'm trying to develop my first UI contribution, but not sure how to continue further...

I need to generate something like this:

<!DOCTYPE html>
    <video style="border: solid 1px;"></video>
      var mediaSource = new MediaSource(); 
      var video = document.querySelector('video');
      video.src = URL.createObjectURL(mediaSource);

The <video> tag should be generated with the HTML function:

function HTML(config) {
        var html = String.raw`
<video style="border: solid 1px;"></video>
        return html;


  1. But where do I have to generate the Javascript code: do I also have to generate a <script> tag in this HTML function, or should I add a initController callback function, or others?
  2. @TotallyInformation advised me in this discussion not to use template literals. Isn't this a problem here, since Node-RED currently still supports older NodeJs versions?
  3. In the examples of the new API, the html text is always left aligned. Is this (for some reason) advised or mandatory, or is it allowed to have indentations (to make the .js file more readable)?

Thanks !!



New dashboard API is basically exposed interface used by ui_template node. So you may test your HTML script with ui_template and copy & modify the code to create your new UI widget node.

Following are answers to your questions:

  1. <script> tag should be included in HTML code generated by HTML function. initController is for initialization purpose of angular scope.
  2. my understanding is that oldest Node.js version suupported by Node-RED is version 4. And template literal is supported from that version according to https://node.green/. But may need testing on older version.
  3. you can freely indent your HTML code.


Hi @nisiyama,

I must confess that I have few experience in developing template nodes, so I will have to disturb you a couple of times here :flushed:

I want to send input messages to my UI node, and expected those messages to arrive here:

initController: function($scope, events) {
     /*$scope.click = function(item) {
             $scope.send({payload: item});
     scope.$watch('msg', function (msg) {
            if (!msg) {

            // Extract information from the input message here ...

But I never arrive at the debugger statement ... Does this has to be accomplished in another way?

Second question: there is a lot of code in the Github example that needs to be available in every UI contribution (I assume), like templateScope / emitOnlyNewValues / width / height /... As a beginner in this kind of stuff, I find it rather difficult too see which stuff needs to be there always, and which stuff is only needed for this specific example. Could it be a solution perhaps to add some comment (like e.g. /*Mandatory*/) before each mandatory line, in your 'reference' example?? It is just an idea ...



Don't worry - as we only added this capability a couple of weeks ago we are all beginners... I "think" the only mandatory ones are width, height and group (as they should all be editable within the edit config panel to maintain consistency with other widgets) - but technically width and height should default automatically (to auto and 1 I think), and even the group could be set in the dashboard sidebar, which makes none of them mandatory :slight_smile:


Hi guys,

Seems I had used scope instead of $scope. Now it works a bit better, but not what I expected yet...

I created a simple flow where I inject a message into my UI node (with msg.payload = 'Hello world'):

  • When I watch msg, I only get 'items' but not 'payload':


  • When I watch msg.payload (like in this nice explanation from @TotallyInformation), the callback function is NOT called:


Any help is appreciated !!


Thanks for the kind words :slight_smile:

There are more and possibly easier to follow examples here:

Indeed, the first example on that page should be what you need I think?


Wow Julian,
Had the impression that you answered my question, before I had posted it ...

Have added (similar to your example) the extra binding in the raw html part:

function HTML(config) {
        var html = String.raw`<div ng-bind-html="msg.payload"></div>...`;
        return html;

However then my callback function (defined in the initController) is still NOT called.

Afterwards I have moved the entire callback function registration to the raw html:

function HTML(config) {
   var html = String.raw`<div ng-bind-html="msg.payload"></div>
                              ;(function(scope) {
                                 scope.$watch('msg.payload', function(newVal, oldVal) {
   return html;

However then the callback still isn't called.

PS. Everytime is say 'is not called' this means that it is only called after a page reset, but then the 'msg' is 'undefined'. When I inject an input message, it is not called...


I think it must be the way you are wrapping it. I've just tried my simple example and it works OK.

[{"id":"df64ea9.d9ab518","type":"inject","z":"586832c7.9dc7fc","name":"","topic":"","payload":"<div>Hi there 2</div>","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":280,"y":340,"wires":[["74ab178.d4cebe8"]]},{"id":"5095eb79.ae5a24","type":"debug","z":"586832c7.9dc7fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":630,"y":340,"wires":[]},{"id":"74ab178.d4cebe8","type":"ui_template","z":"586832c7.9dc7fc","group":"f2b5e7df.7450b8","name":"","order":0,"width":0,"height":0,"format":"<div ng-bind-html=\"msg.payload\"></div>\n<script>\n    //console.dir(scope) // this also works\n    //console.dir(scope.msg) // This doesn't because scope.msg doesn't yet exist\n\n    // Lambda function to access the Angular Scope\n    ;(function(scope) {\n        //console.log('--- SCOPE ---')\n        //console.dir(scope) // this works but you only get it once (on startup)\n        //console.dir(scope.msg) // Doesn't work for  because scope.msg doesn't yet exist\n\n        //Have to use $watch so we pick up new, incoming msg's\n        scope.$watch('msg.payload', function(newVal, oldVal) {\n            console.log('- Scope.msg -')\n            console.dir(scope.msg)\n        })\n\n    })(scope)\n</script>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":460,"y":340,"wires":[["5095eb79.ae5a24"]]},{"id":"f2b5e7df.7450b8","type":"ui_group","z":"","name":"Default","tab":"e43d97b6.e05218","disp":true,"width":"6","collapse":false},{"id":"e43d97b6.e05218","type":"ui_tab","z":"","name":"TEST","icon":"dashboard"}]

The above works and you can send multiple messages. In the browser console, you will see that the function is called just fine.


What is specified for beforeEmit property of an argument for addWidget function?
Could you try following?

beforeEmit: function(msg, value) {
    return { msg: msg };


@TotallyInformation: It indeed works fine, as long as I keep the code in the template node. But as soon as I use it in my own UI contribution, nothing ... But thanks for spending your time on this one!

@nisiyama: If have tried your new version of beforeEmit, and that seems to do the job!

Did a simple test by injecting msg.payload = "Hello world" or "Hello mars" into my UI node like this:


When I use now the following code snippet:

initController: function($scope, events) {
    $scope.$watch('msg', function(newVal, oldVal) {
        console.log('Watch is called ...');

Then the stuff below is being logged in my Chrome console:

  1. When dashboard is loaded in new tabpage, all 3 variables are undefined.
  2. When a message (with msg.payload = "Hello world") is injected, the newVal contains the new message and the oldVal is undefined.
  3. When a message (with msg.payload = "Hello mars") is injected, the newVal contains the new message and the oldVal contains the old message.
  4. When the page is refreshed, newValue contains the last message and oldValue is undefined.

So $scope.msg is always equal to newVal (as expected), so we don't need the $scope.msg here anymore.
And the on-load situation (1) can be ignored with a simple IF statement:

initController: function($scope, events) {
    $scope.$watch('msg', function(newVal, oldVal) {
        if (!newVal) {


But I hadn't expected that the refresh situation (4) would trigger the watch with an older message. Or is that normal behaviour to start again where we left before the refresh?

It would be useful for other users to add your information to the above wiki page, and perhaps integrate it somehow in the 'reference' ui-list example node ...

Thank you very much!!!


I second this motion...
I have many templates and getting them into a configurable ui node would be great.
The UI List example is good to go by, if you knew which parts were added by the author and which ones are more or less boiler-plate.

Or maybe we need just a boiler-plate package created to start from?


@BartButenaers, @seth350
Thank you for your advise.
I'd like to modify ui_list example or to prepare much simpler and descriptive template to start with.


That would be great!

There was one instance today I had trouble figuring out and that was the HTML file for ui list.

Looking at the configurables section, you seem to pull some of the text from someplace else.

<span data-i18n=“ui_list.label.group”</span></label>

I would had thought to place the text “Group” here, but wasn’t sure and could not find where ui_list.label.group was used.


Corresponding text is looked up from locales/<language>/ui_list.json.
This is for internationalization for displayed texts (please look at https://nodered.org/docs/creating-nodes/i18n).