Script Interface Plugin Development Guide

Example Code Script

// Find the node by specifying the node path.
const pressureNode = codabix.findNode("/Nodes/Injection molding/Pressure", true);
// Set an interval timer that will call our function every 3 seconds.
timer.setInterval(function () {
    // Generate a random number between 20 and 150.
    let randomNumber = Math.random() * 130 + 20;
    // Write the value to the Node.
    codabix.writeNodeValueAsync(pressureNode, randomNumber);
}, 3000);
The CoDaBix Web Configuration provides the item “Script Interface” to create, edit and delete scripts. You can also stop scripts so that they are not executed anymore, without deleting them.

Script Interface

Property Description
Name Name of the script, used to identify it. This name will also be used in stacktraces when an exception occured.
Description You can enter a more detailed description of the script.
Editor Strictness Level Determines how strict the editor will handle certain code.

Low (default): The editor will not criticize the cases described for the following options.
Medium: The editor will criticize implicit any types, implicit returns, and fallthrough cases in switch statements.
High: Additionally to the cases described in the “Medium” level, the editor will criticize unused local variables and unused function parameters.

Note: For Library Scripts (available in a later version) the level is always “High”.
Script State Enabled: The Script shall be run.
Disabled: The Script shall be stopped.
CurrentScriptState Shows the actual state of the Script.

NotRunning: The Script is not running, either because it is disabled or because it has just been created and there is not yet a live code for it.
Running: The Script has been started and there was no error since then (this can also be the case if a Script has recently been restarted after an uncaught exception).
Stopped: The Script has been started and has been running, but no more event listeners or callbacks are active.
StoppedAndScheduledForRestart: The Script has been stopped because an uncaught exception occured while executing it. It will be automatically restarted after a short period of time.

The default value of “Editor Strictness Level” is “Low”. We recommend this setting if you are just beginning with Scripts and JavaScript. If you are an experienced TypeScript developer, we recommend setting the value to “Medium” or “High” so that the editor can help you manage a clean code base.

Note: It can take up to 3 seconds until a change (e.g. enabling / disabling the script) will become effective and another 3 seconds until the CurrentScriptState and the Script Log are updated.

Once you created a live version of a script (see section Going Live), it will automatically be started, as long as its state is set to “Enabled”. On startup, the script is in the “Initialization Phase”. During this phase, the script can register callbacks (e.g. for events or for a timer). When the callback is called, the script is in the “Callback Phase” (but the script can still register further callbacks in this phase).

The following diagram illustrates the phases of a script:

Script Phases

Note: Although not shown in the diagram above, the script can still register new callbacks for other events when it is already in the Callback Phase.

A timeout of about 15000 ms is applied to the script to protect it against unintended infinite loops, for example while (true) {}. If a script is not finished after the timeout, it is stopped and treated as if an uncaught exception has occured.

In both the Initialization Phase and the Callback Phase the script is automatically restarted after a short period of time (about 3 seconds) when an uncaught exception occurs.

By clicking the Script Editor icon (Script Editor Icon), the editor will appear and you can write your script code.

Edit Script Code

If you have already worked with Visual Studio or VS Code, the Script Editor will look familiar to you (in fact, the editor is based on the Monaco Editor from VS Code). The editor provides IntelliSense for CoDaBix API methods / interfaces and for built-in JavaScript classes as you type, as shown in the screenshot above.

If you hover on variable or method calls, a tooltip appears that shows the type:


If you have an error in your script, the editor will show red squiggly lines on it and show the error if you hover on it:


When you right-click at a position in the code, a context menu appears with useful commands. For example, you can find all references to a specific variable in your code (and e.g. rename it):

Context Menu

Going Live

The Script Editor allows you to write and save code for the script (“draft”) without actually running it. Only when you select “Go Live”, the current draft script code will be saved as the “live version” and will actually be run. This allows you to progressively work on the script code without affecting the currently executed live version. With the “Toggle Diff Editor”, you can switch to a diff editor to compare your changes between the live version and your current draft.

Edit Script Options

Once you are finished with editing your script, make sure to check the “Go Live” checkbox and then click save. This will make your current script draft become the live version so that it is actually executed.

If you have a compile time error in your script when trying to go live, an error box will appear describing the error:

Error Box

Otherwise, the Script Editor will close and the new script code will run after some seconds.

Useful Shortcuts

  • Ctrl+F: Find/Replace
  • Ctrl+F2: Rename (change all occurences)
  • Shift+F12: Find all references
  • Ctrl+F12: Go to defintion
  • Ctrl+K, Ctrl+C: Comment out the selected lines
  • Ctrl+K, Ctrl+U: Uncomment the selected lines
Each script has a log associated with it. When a script has been started (or an uncaught exception occured), an entry will be made in the log. Additionally, you can create a log entry directly from the script code by calling logger.log().
If you click on the log button (Log Button), a dialog with the log content will appear. For example, if the Script started, but an uncaught exception occured in a callback, the log might look like this:

Example Log

In case an exception occurs, the log entry contains a stack trace showing the line numbers of the script (after the colon) that mark the position in the code at which the corresponding functions were executing when the exception occured.

Note: When an uncaught exception occurs, an error message will also be shown in the Runtime Log:

Error Message

JavaScript Basics

Given below is a brief summarization of JavaScript basics. For a more detailed tutorial, please visit the JavaScript Guide on MDN.

In a script, you can declare variables that store values with let and const (const means the variable cannot be modified). You can assign values through the “=” operator (whereas “==” is used to check for equality):

let count = 123;
const myText = "Hello World!";

JavaScript supports a number of basic value types:

  • number: A number (which is a double precision floating point value) can represent both integers and decimals. You can use numbers to do calculations, e.g.:

    let result = (2 + 0.5) * 3;   // 7.5
  • boolean: A boolean is either true or false. A boolean can be the result of comparison operators and used for control flows like if, while etc.
  • string: A string can consist of an arbitrary number of characters and be used to represent text. Strings can be linked using the “+” operator:

    let displayText = "The result of 5+6 is " + (5+6);   // "The result of 5+6 is 11"
  • object: An object stores properties that consist of a key (string) and a value (any value type). For example, the codabix object contains properties that are methods, e.g. findNode. Object properties are mostly accessed using dot (.) notation (codabix.findNode(…), codabix.rootNode, …).

You can use control flow statements to do comparisons:

let result = "The value is"
if (count > 200) {
    result += "greater than";
else if (count < 200) {
    result += "lower than";
else {
  result += "equal to";
result += " 200.";

You can create a function that will contain code which needs to be run more than once. For example, you could create a function that calculates the average of two values:

function average(value1, value2) {
    return (value1 + value2) / 2;
// Calculate the average of 100 and 250 and write it to the Script Log.
logger.log("The average of 100 and 250 is " + average(100, 250) + ".");

When you run this code, it will print something like this to the script's log:

2016-09-28 14:57:41.7 Z: [Log] The average of 100 and 250 is 175.

Script API

The Script Interface Plugin provides the following API namespaces that can be used in a script:

  • codabix: Contains all CoDaBix related functionality, e.g. to access and modify Nodes.
  • timer: Contains methods to create a timer, so that you can let a function of your script be called back at a regular interval.
  • logger: Contains a log method that allows you to write to the script log.
  • storage: Allows you to persist information across restarts of the script.
  • io: Provides I/O operations, e.g. File Access.
  • net Provides network-related operations, e.g. to register an HTTP handler.
  • runtime: Provides functions to interact with the script runtime environment.

Note: The Script Editor supports IntelliSense, so you can see which methods are available in the codabix namespace just by typing codabix. (notice the dot after “codabix”). Similarly, when a method returns an object (for example a Node), you can again type a dot after it to see which methods it has.

NOTE: Until CoDaBix 1.0.0 is released, the Script API is not considered to be stable and may change.

Accessing CoDaBix

Find a Node and Log its Value

Let's assume you installed CoDaBix with the “Demo-Data (Continous)” plugin and want to access the Node Nodes → Demo-Data → Temperature. To do this, you first need to get the Node path or the Identifier of the Node. To do this, open the Node view in the CoDaBix Web Configuration, select the relevant Node and click the Access symbol (Access Symbol). Then copy the “Absolute Node Path”. We then specify this path in the codabix.findNode() method as well as a true parameter so that the method throws an exception if the node could not be found:

// Find the "Temperature" node and check if the node
// has a value.
const temperatureNode = codabix.findNode("/Nodes/Demo-Nodes/Temperature", true);
if (temperatureNode.value != null) {
    // OK, node has a value. Now log that value.
    logger.log("Current Temperature: " + temperatureNode.value.value);

Your script log will then look like this:

2016-09-28 15:08:45.2 Z: Started.
2016-09-28 15:08:45.3 Z: [Log] Current Temperature: 71
2016-09-28 15:08:45.3 Z: Stopped.

However, in this example only one value is logged. This is because when the script is started, the code which finds the Node and logs the value is run, but after that the script is finished.

Now, if we want to log the value not only once but every 5 seconds, we can do this by creating a timer and supplying a function that the timer will call at a regular interval (note: Instead of function () {...}, for a callback you should use a fat arrow function: () => {...}).

// Find the "Temperature" node.
const temperatureNode = codabix.findNode("/Nodes/Demo-Nodes/Temperature", true);
// Now create a timer that will log the node's value
// every 5 seconds.
const interval = 5000;
timer.setInterval(() => {
    // If the node has a value, log it.
    if (temperatureNode.value != null) {
        logger.log("Current Temperature: " + temperatureNode.value.value);
}, interval);

When you run this script your script Log will look like this after some seconds:

2016-09-28 15:15:42.6 Z: Started.
2016-09-28 15:15:47.6 Z: [Log] Current Temperature: 70
2016-09-28 15:15:52.6 Z: [Log] Current Temperature: 75
2016-09-28 15:15:57.6 Z: [Log] Current Temperature: 63
2016-09-28 15:16:02.6 Z: [Log] Current Temperature: 71

Node Events

The example above uses a timer that calls a function in a regular interval. However, it is also possible to register for specific events of a Node:

  • ValueChanged: Raised when a value has been written to the node (property value).
    Note: This event is also raised if the new value is equal to the previous value. To determine if the value has actually changed, you can check the isValueChanged property of the listener argument.
  • PropertyChanged: Raised when a property (other than value) of the Node has been changed, e.g. name, displayName etc.
  • ChildrenChanged: Raised when one or more children Nodes of the current Node have been added or removed.

You can handle the event by adding an Event Listener (callback) to the Node whose event you are interested in.

// Find the "Temperature" node and add a handler for the "ValueChanged" event.
const temperatureNode = codabix.findNode("/Nodes/Demo-Nodes/Temperature", true);
temperatureNode.addValueChangedEventListener(e => {
    // Log the old and the new value of the node.
    logger.log("Old Value: " + (e.oldValue && e.oldValue.value)
        + ", New Value: " + e.newValue.value);

Note: You cannot (synchronously) read or write Node values (or do other changes to Nodes) from within an Node event listener. If you want to do this, use codabix.scheduleCallback() to schedule a callback that is executed as soon as possible after the event listener is left.

Write a Value to a Node

You can also write values to a Node from a script. For example, in the Node configuration, select the “Nodes” Node and create a datapoint Node with the name “Counter” and select on Value Change for “History Options”. Then create a script with the following code:

const counterNode = codabix.findNode("/Nodes/Counter", true);
// Declare a counter variable.
let myCounter = 0;
// Set a callback that increments the counter and writes the
// current value to the node until the value is 5.
let timerID = timer.setInterval(() => {
    myCounter = myCounter + 1;
    codabix.writeNodeValueAsync(counterNode, myCounter);
    if (myCounter == 5)
}, 500);

Then, change back to the Nodes view, select the “Counter” Node and open the history values (History Values Icon):

History Values

You can see now that the values 1, 2, 3, 4 and 5 have been written to the Node with a delay of 0.5 seconds.

Note: There is also an easier way of writing this code (withouth callbacks) using an Async Function as shown in the next chapter.

Async Functions

The example shown in the previous chapter creates a timer by setting a callback. However, such code can quickly become confusing when you have more complex conditions. Async Functions allow to write the code in a simpler way as the code looks like synchronous code (without callbacks). CoDaBix provides a few async functions that can be recognized from their name ending in Async.
For example, the above code can be rewritten like this, using the timer.delayAsync() function:

const counterNode = codabix.findNode("/Nodes/Counter", true);
// Write the values 1 to 5 to the node, and wait 0.5 seconds between each write.
for (let i = 1; i <= 5; i++) {
    await codabix.writeNodeValueAsync(counterNode, i);
    await timer.delayAsync(500);

Notice using the keyword await here. Await means something like “interrupt the execution here until the asynchronous operation of the function has completed”. In this case the delayAsync function returns a Promise object that is fulfilled after 0.5 seconds have passed. As soon as the Promise is fulfilled the execution continues. Other async functions also return Promise objects, which may be fulfilled with a value, if applicable.

If you did not write await, your code would not wait 0.5 seconds but instead immediately continue with the next iteration. It is important to note that when awaiting an operation, in the meantime other code can still be executed, e.g. you may receive an event from the Node while your code pauses at the await position.

In the example above we also use await to wait for writeNodeValueAsync. Writing Node values may take some time if the Node is connected to a device, e.g. a S7 PLC device. In that case, execution would pause until the value has actually been written to the device. If you do not want to wait for that, you can also remove the await here (in which case you will need to write the void operator before the function call to signal to the compiler that the returned Promise has intentionally been discarded).

However, if you try this code directly it will not yet work because in order to use await you need to mark the surrounding function using the async keyword. If the surrounding async function is the main function, you should wrap it in a call to runtime.handleAsync() so that uncaught exceptions are not silently swallowed:

runtime.handleAsync(async function () {
    // Your code here...
} ());

Here is a complete example of a Script using Async Functions:

runtime.handleAsync(async function () {
    for (let i = 0; i < 10; i++) {
        logger.log("Iteration " + i);
        await timer.delayAsync(1000);
} ());

Note: If you want to use an async function as callback for an event listener, you should also wrap it in runtime.handleAsync, as shown in the following case where we handle the event when a Node gets a new value:

const myNode = codabix.findNode("/Nodes/A", true);
myNode.addValueChangedEventListener(() => runtime.handleAsync((async () => {
    // Do async stuff...
}) ()));

Reading Node's Values Using a Synchronous Read

Another example of an async function is codabix.readNodeValuesAsync(). This method invokes a Synchronous Read on a device and reading values from the device may take some time. Therefore you should use await to pause the execution until the resulting values arrive:

runtime.handleAsync(async function () {
    const node1 = codabix.findNode("/Nodes/A", true);
    const node2 = codabix.findNode("/Nodes/B", true);
    // Do a synchronous read, and pause until the values arrive from the device.
    let [nodeValue1, nodeValue2] = await codabix.readNodeValuesAsync(node1, node2);
    logger.log("Node1 Value: " + (nodeValue1 && nodeValue1.value) 
        + ", Node2 Value: " + (nodeValue2 && nodeValue2.value));
} ());

File Access

The namespaces io.file, and io.path contain methods and classes to work with files and directories. For example, you can read and write text files, copy, move or delete files, and enumerate all files in a specific directory.
Note that file access is subject to the File Access Security restrictions that have been defined in the CoDaBix Settings.

Most of the I/O operations are implemented as Async Functions that return a Promise object. This is because I/O operations may take some time to complete (depending on the file system). In order to not block CoDaBix, the I/O operations are run in the background. You can call them in the async functions using the await keyword.

Note: On Windows 10 Version 1511 and older (and Windows Server 2012 R2 and older), e.g. on Windows 7, the maximum file path length is limited to 260 characters (MAX_PATH).
On Windows 10 Version 1607 and higher (as well as Windows Server 2016 and higher) longer path names can be used. However, for this you will need to enable the setting “Enable Win32 long paths” in the Windows Group Policy, see Enabling Win32 Long Path Support.

Basic File Operations

Enumerate files of the CoDaBix data “log” directory:

runtime.handleAsync(async function () {
    // Get the path to the Codabix "log" directory. We use the CoDaBix-defined environment
    // variable "%CodabixDataDir%" for this case.
    // combinePath is an OS independent way to combine path elements.
    const codabixLogDir = io.path.combinePath(
            runtime.expandEnvironmentVariables("%CodabixDataDir%"), "log");
    const fileList = await;
    let result = "";
    for (let file of fileList) {
        result += "File: " + file + "\n";
    logger.log("Files in " + codabixLogDir + ":\n" + result);
} ());

The result might look like this:

Edit Script Result

  • io.path.combinePath(): Combines two or more path elements into one path in an OS independent way. For example, on Windows the path separator is \, while on Linux it is /. This method automatically uses the correct separator to combine the paths.
  • Returns a string[] array containg the file names of the specified directory (similarly, returns the subdirectory names).
  • io.file.copyFileAsync(): Copies a file.
  • io.file.moveFileAsync(): Renames or moves a file.
  • io.file.deleteFileAsync(): Deletes a file.
  • runtime.expandEnvironmentVariables(): Replaces the name of each environment variable (enclosed in two % characters) in the specified string with their value. CoDaBix defines the following environment variables in addition to the OS's variables:
    • CodabixDataDir: Contains the path to the currently used CoDaBix data directory.
    • CodabixInstallDir: Contains the path where CoDaBix has been installed.

Reading and Writing Text Files

Writing a text file in one step:

runtime.handleAsync(async function () {
    // Create a string and write it into a textfile (HelloWorld.txt).
    let filePath = io.path.combinePath(runtime.expandEnvironmentVariables("%CodabixDataDir%"),
            "Temp", "HelloWorld.txt");
    let content = "Hello World from CoDaBix!\r\n\r\n" +
            "Current Time: " + new Date().toLocaleString();
    await io.file.writeAllTextAsync(filePath, content);
} ());

This will create a textfile HelloWorld.txt in the Temp directory (which is created if it doesn't exist) of your CoDaBix data directory that might look like this:

Text File

By calling io.file.writeAllTextAsync(), the file is written in one step (and with io.file.readAllTextAsync(), it is read in one step). However, you can also read or write text files on a line-by-line basis as in the following example:

Reading a text file line-by-line:

runtime.handleAsync(async function () {
    // We want to read from the current CoDaBix Runtime Log file.
    const runtimeLogDir = io.path.combinePath(
            runtime.expandEnvironmentVariables("%CodabixDataDir%"), "logfiles");
    const runtimeLogFiles = await;
    // The returned files are ordered ascending by their name; so we use the last
    // entry to get today's log file.
    const logFile = io.path.combinePath(runtimeLogDir,
            runtimeLogFiles[runtimeLogFiles.length - 1]);
    // Open the file using a FileReader and read the first 5 lines.
    let result = "";
    let reader = await io.file.openFileReaderAsync(logFile);
    try {
        let lines = await reader.readLinesAsync(5);
        for (let i = 0; i < lines.length; i++) {
            // Append the line to the result string
            result += "\n[" + (i + 1) + "]: " + lines[i];
    finally {
        // Ensure to close the reader when we are finished.
        await reader.closeAsync();
    // Log the result string.
} ());

This code reads the first 5 lines of the current CoDaBix runtime logfile and prints it to the script log:

CoDaBix Runtime Logfile

Sending HTTP Requests

The namespace net.httpClient provides methods that allow you to send a HTTP request to another server. This allows you e.g. to call external REST APIs using JSON objects.

Important: When sending requests over the internet (or other potentionally insecure networks), please make sure that you always use https: URLs if possible, as http: URLs use an insecure connection and therefore do not provide server authenticity and data confidentiality/integrity. This is especially important when sending confidential login credentials.

Note: Currently, the request and response bodies can only use text data, not binary data.

Simple GET requests

The following code issues a simple GET request and logs the response body (if present):

    let response = await net.httpClient.sendAsync({
        url: ""
    if (response.content) {
        logger.log("Result: " + response.content.body);

Accessing a JSON-based REST API

When you want to access an URL that can return a JSON result, you can use JSON.parse() to convert the JSON response body string into a JavaScript object, and access it.

The following example accesses an external REST API to get weather data (temperature) in a regular interval, and stores it in the /Nodes/Temperature node:

    let temperatureNode = codabix.findNode("/Nodes/Temperature", true);
    while (true) {
        let valueToWrite: codabix.NodeValue;
        try {
            let response = await net.httpClient.sendAsync({
                // TODO: Use "https:" when the server supports this,
                // to guarantee data authenticity and integrity
                url: ""
            let result = JSON.parse(response.content!.body);
            valueToWrite = new codabix.NodeValue(result[1]);
        catch (e) {
            logger.logWarning("Could not retrieve weather data: " + e);
            valueToWrite = new codabix.NodeValue(null, undefined, {
                statusCode: codabix.NodeValueStatusCodeEnum.Bad,
                statusText: String(e)
        // Write the temperature value.
        await codabix.writeNodeValueAsync(temperatureNode, valueToWrite);
        // Wait 5 seconds
        await timer.delayAsync(5000);

The following code demonstrates how to send a POST request to the CoDaBix-internal REST API (for <encrypted-password> you will need to specify the encrypted user password):

    let result = await net.httpClient.sendAsync({
        // Note: For external servers you should "https:" for a secure connection.
        url: "http://localhost:8181/api/json",
        method: "POST",
        content: {
            headers: {
                "content-type": "application/json"
            body: JSON.stringify({
                username: "",
                browse: {
                    na: "/Nodes"
    let jsonResult = JSON.parse(result.content!.body);
    // TODO: Process JSON result...
The following list gives some recommendations for performant and clean scripts:
  • If you are not writing a Library Script, place your whole code inside an IIFE (immediately-invoked function expression) as follows:
    Blank Template.js
    runtime.handleAsync(async function () {
        // Your code here...
    } ());

    This avoids polluting the global scope and the TypeScript compiler can detect unused local variables and does not complain about using anonymous types in exported variables.
    We recommend to mark the function with the async attribute so that you are able to call asynchronous functions using the await keyword. In that case you should pass the returned Promise object to runtime.handleAsync() to ensure uncaught exceptions are not silently swallowed, as shown in the code example above.

  • Always use let or const to declare variables, not var as the latter is not block-scoped but function-scoped.
  • Instead of using arguments which is a “magic”, array-like object, use rest parameters (...args) which is a real Array instance and doesn't contain previous function parameters.
  • When enumerating an array, don't use for-in - use for-of instead, as the former doesn't guarantee any enumeration order.
  • When throwing an exception, don't throw a primitive value like throw "My Error Message";. Instead, create and throw Error object instances, like throw new Error("My Error Message");. This ensures that you can retrieve the stack trace from where the exception occured.
  • If possible, avoid creating closures in code that is called very often (e.g. in a loop), because creating a closure is expensive in JavaScript. Instead, check if you can reuse a function by passing arguments.
    For example, when you want to handle a node's ValueChanged event and write a new value in it (for which codabix.scheduleCallback() must be used), avoid the following code that creates a closure every time the event is invoked:
    myNode.addValueChangedEventListener(e => codabix.scheduleCallback(() => {
        void codabix.writeNodeValueAsync(otherNode, e.newValue);

    Instead, you can create a closure once and then using the following variant of codabix.scheduleCallback() in the event listener that allows to pass arguments:

    const scheduledHandler = (newVal: codabix.NodeValue) => {
        void codabix.writeNodeValueAsync(otherNode, newVal);
    myNode.addValueChangedEventListener(e => codabix.scheduleCallback(scheduledHandler, e.newValue));

Storing Passwords Securely

In some cases, you need to store passwords in script code, for example to access external password-protected HTTP services. To avoid having to store a password in plaintext, you can first encrypt it once using the CoDaBix Web Configuration by opening the menu item “Password Security”, and then at the start of your script code you can decrypt it by calling This means that the password is encrypted with the password key of the back-end database which was generated using a cryptographically secure random number generator when creating the database.
The same mechanism is also used when storing passwords in datapoint nodes of type “Password”, which e.g. is used by device plugins.

This provides additional protection:

  • Passwords are not displayed in clear text, so you can show Script code to other people, without allowing them to directly read the password.
  • If you disable the option Include Password Key when creating a backup, such passwords cannot be extracted (decrypted) from the backup.

Note: You can generate a new password key in the CoDaBix Settings, which means passwords that were encrypted with the previous key are no longer readable.

For example, if you wanted to use the password “test” in your Script code, open the menu item “Password Security” in the CoDaBix Web Configuration, and then enter the password. After clicking on “Encrypt”, the encrypted password will be printed.


You can now copy the encrypted password and store it in the Script code where you need it:

// Decrypt the password that we need to run a HTTP request.
const myPassword =
// ...
function doRequest() {
    let request = {
        user: "myUser",
        password: myPassword
    // TODO: Send the request...