Custom JavaScript

Learn how to run custom JavaScript as part of your logic flows

Sometimes you need more than just the flow and formula functions that are available.

In these situations, you can use the JavaScript node, found in the component library panel on the left. It allows you to write custom JavaScript code to perform as complex logic as you need.

To use it, drag-and-drop it onto the logic canvas, then double-click it to open the JavaScript node configuration modal.

JavaScript node configured to filter employees based on a search string.

To understand how the JavaScript function node works, we need to cover the following topics:

  • Inputs – how to bring data from your app state into the function execution context

  • Outputs – how to describe what data is available after the function has completed its execution

  • Function syntax and execution – what code can be written and how it is run during app runtime

Inputs

The function box runs in isolation from the rest of the app context, so we need to explicitly define the data we want to bring in and work with. This is done in the inputs section on the left side of the modal.

To add a new input property, write its name in the Add new property field and click the plus icon. Existing properties can be removed via the x icon.

After adding an input property, you need to define where it gets its value. You do this identically to how you configure Flow function input properties: selecting the value type and then the value itself.

The input values are available in the function execution context under properties of the inputs object.

In the example screenshot above, we've defined two input properties, rawEmployees and searchString, both of which are bound to page variables with the same keys. These values are then available under inputs.rawEmployees and inputs.searchString.

As mentioned, we could just as well use a formula function, an app variable, the output of another flow function and so on as the value for an input argument.

Input values are static

Input values are always evaluated to a static value when the JavaScript function execution begins. They are never dynamic references to the value's original source.

For example, let's say we have a long-running JavaScript function where an input argument is bound to a page variable and that page variable changes before the function's execution is completed, the value available via the inputs object remains as the "snapshot" taken at the beginning of execution.

Outputs

Output arguments define what comes out of your JavaScript function node. Like all values in Composer, they have a schema, which is defined via the section on the right.

To add a new output argument, type its key into the Add new property field and click the plus icon. To remove an output argument, click the x icon next to its key.

Data is passed to the output arguments as-is, so it is up to you to ensure your JavaScript function doesn't output data that's different from the expected schema.

You can keep the schema ambiguous, but as a rule, the output schema should be as precise as possible to avoid potential errors in subsequent flow/formula functions.

(In the above screenshot, the filteredEmployees output schema is provided just an array of objects for brevity. In reality, we should explicitly define what properties the objects in the array are expected to have, e.g. firstName and lastName and so on.)

Multiple outputs

In many cases, we want our node to have multiple outputs, so we can trigger different logic flows depending on the result. For example, we could have a complex regex matcher in our JavaScript function, and then trigger the first output if the match is successful, and the second one if it is not.

To make multiple outputs available, change the Number of outputs value at the top of the Outputs section.

You need to explicitly define what output arguments are available in each of the outputs. This is useful so that e.g. an error object will only be available in the second output when an error has occurred, and the successful result will only be available in the first output.

Function syntax and execution

When your JavaScript function node is triggered in flow logic, the code written in the middle of the configuration modal is executed.

Let's look at the code from the example screenshot above.

const filteredEmployees = inputs.rawEmployees.filter(employee => {
const firstName = employee.firstName.toLowerCase();
const lastName = employee.lastName.toLowerCase();
const searchString = inputs.searchString.toLowerCase();
return firstName.includes(searchString) ||
lastName.includes(searchString);
});
return { filteredEmployees };

All ES6 features are supported, from arrow functions to object destructuring and so on. Any input values you've defined are available as properties of the inputs object, e.g. inputs.rawEmployees and inputs.searchString in the code above.

Here, we run a filter function on the inputs.rawEmployees array, checking if our search string matches either the first name or last name of the employee.

Once we've got the data we want to output, we terminate the function execution with the standard JavaScript return statement. What we return should always be an object whose properties match what we've defined for our output arguments.

In the code above, we use ES6 Object Property Shorthand to return an object whose filteredEmployees property contains the filtered data.

Multiple outputs

By default, the return statement triggers the first output. To trigger another output, you need to pass in an array of the format [index, returnObject] where index is the index number of the desired output (starting from 0) and returnObject is an object whose properties match the output arguments defined for the given output.

For example, to return an error object in the second output:

const message = "An error occurred!";
return [1, { errorMessage: message } ];

It is not possible for one JavaScript function node execution to trigger multiple outputs.

Exposed JavaScript APIs

Some JavaScript APIs such as fetch and moment are currently exposed in the runtime context.

Our upcoming plugins feature will allow you to define React Native and web plugins for your project. Once defined, their JavaScript interfaces can be made available inside a JavaScript node, allowing you to code your app-specific implementation logic directly in Composer.

Async/await

The execution context is wrapped inside an async function, so you can use the await keyword on the root level directly. Below is an example where we also implement error handling with try-catch:

const { url } = inputs;
try {
const response = await fetch(url);
const json = await fetchResult.json();
return { json }
} catch (err) {
const error = {
code: 'unknownError',
message: 'Something went wrong.',
rawError: err,
}
return [1, { error }]
}

Error handling

If a JavaScript node throws an uncaught error, the logic flow execution stops. Thus, it is generally best if your code handles any potential errors on its own, with a separate output for the error case.

If you want, you can even get in the habit of constructing error objects with specific structure and error codes like the default flow functions do.

Converting to a flow function

You can convert one or more JavaScript nodes (and other flow functions) to a custom flow function by selecting them and clicking Create a new flow function from the properties panel.

Any input/output arguments are mapped as input/output arguments of the newly created flow function, so everything will work out of the box.

The JavaScript node itself is moved inside the flow function – you can see the converted structure by opening your newly created flow function in isolation mode by double-clicking it.