Handlebars Templates
Both the automations feature and the monitors feature of Remote Manager support templates that can be used to assign values to extract values and generate values.
-
The monitors feature uses templates to convert or generate the output of a monitor event. The template is used to convert a payload from the system into the expected payload that the target of the monitor wants to receive.
-
The automations feature uses templates to generate variables that can be used in the automation steps, to construct other variables or in if-then-else conditions.
Table Of Contents
Template Format
The templating engine supported by Remote Manager is Handlebars. Handlebars.java is the specific implementation used which has a good support reference at https://jknack.github.io/handlebars.java/.
The JavaScript implementation of Handlebars (Handlebars Introduction is a bit more common and documentation for it should apply, though the output of templates may differ slightly from the JavaScript version, typically with regard to whitespace. Sandbox environments like https://handlebarsjs.com/playground.html can be helpful in testing out a template, though keep in mind they are based on the JavaScript version.
Helpers
The following built-in handlebars helpers are available for use in templates:
each
if
,lookup
unless
with
These additional helpers are also available in Remote Manager.
add
and
capitalizeFirst
capitalize
defaultIfEmpty,
eachFiltered
endsWith
eq
extract
firstPathComponent
formatNum
formatTime
gt
gte,
join
json
lower
lt
lte
mul
neq
not
now
number
or
remainingPathComponents
replace
slice
slugify
substring
upper
yesno
Descriptions of the built-in helpers can be found in the Handlebars documentation. The additional helpers are Remote Manager specific and can be found below.
The model
The handlebars template language uses a model that defines input data and variables that are available to the template.
-
For the monitors feature, the model contains the entire input payload of the monitor event. The payload syntax and type differs depending on the events flowing through the monitor. See Tutorial: Monitors with Templated Payloads for more information.
-
For the automations feature, the model contains the built-in variables and any user variables that have been defined in the automation.
- The automations built-in variables and example values are:
customer_id
- The account ID of that the automation is running indebug_mode
- true/false, is the device currently in debug modedevice_id
- The device ID for the running automation, for example00000000-00000000-123456FF-FF123456
firmware_version
- The firmware version of the device, for example1.0.0.0
group
- The group that the device is in, for examplegroup1
input
- When processing a variable step with multiple expressions, the 2nd through final expression receives theinput
variable in the model. The input variable receives the output of the previous expression. Theinput
variable can be used for building up a final result from a series of expressions in a single variable step.last_result
- The result of the last automation step, for example, the output of a CLI commandlocation
- The value of the location field of a device, for exampleRochester, MN
maintenance_window
- true/false, is the device in a maintenance windowname
- The device name, for exampleBus0042
run_count
- The number of times the step has runtags
- The tags associated with the device, for example:tag1,tag2
type
- The type of device the automation is running on, for exmaple:Digi TX64
- The automations built-in variables and example values are:
Expansion
By default, handlebars escapes the output of the variable, but you can use triple braces to avoid escaping.
For a model with these fields
-
name
with the valueWorld!
-
greeting
withIt's an amazing
-
Template expression
Hello, {{name}}!
will expand toHello, World!
. -
Template expression
{{name}}
will expand toWorld!
. -
Template expression
{{{name}}}
will expand toWorld!
. -
Template expression
{{greeting}} {{name}}
will expand toIt's an amazing World!
-
Template expression
{{{greeting}}} {{{name}}}
will expand toIt's an amazing World!
Using Helpers
Helper Blocks
When a helper has a block inside, the helper is invoked using the #
character at the start tag and the /
character at the end tag.
For example, in the following expression, the if
helper contains blocks of other text, so we use #if
to open and /if
to close
the helper call. {{#if name}}Hello, {{name}}!{{else}}Hi!{{/if}}
will output Hello, World!
if the model contains a field name
with the value World!
.
A helper that does not contain a block can be used with no start and end tags, for example, {{and first_name last_name}}
simply outputs true if both first_name
and last_name
are set.
Nesting
You can use parenthesis to nest helpers inside other helpers.
For example, the template expression {{capitalize (lower 'HELLO, WORLD!')}}
uses the capitalize
and lower
helpers to output Hello, World!
.
First the expansion converts the literal text to lowercase and then capitalizing the first letter of each word.
Helper Reference
add
Syntax: {{add input1 input2...}}
Add all the numbers together. The inputs can be numbers or strings that can be converted to numbers.
Expression {{add 1 2}}
renders 3.0
, while {{add input 2 3}}
adds 5 to the value of input.
Note: Input numbers are converted to decimals and may be formatted with decimal points. Use the formatNum
helper to control the format.
For example, {{formatNum '###0;-###0' (add 1 2)}}
results in 3
.
and
Syntax: {{#and input1 input2...}}output-if-true{{else}}output-if-false{{/and}}
The and operator determines if all inputs are truthy
(true or not empty). Can be used with non-boolean values.
Supports an else section and multiple operators. For example, {{#and input1 input2 input3}}Yes{{else}}No{{/and}}
will output Yes if all inputs are truthy, otherwise No.
Expression {{and input1 input2}}
renders true or false directly.
Expression {{and input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
capitalizeFirst
Syntax: {{capitalizeFirst input}}
Capitalizes the first letter of the input string.
capitalize
Syntax: {{capitalize input}}
Capitalizes the first letter of each word in the input string.
defaultIfEmpty
Syntax: {{defaultIfEmpty input default}}
Output the default value if the input value is empty or not set. For example: {{defaultIfEmpty test 'NotSet'}}
will output NotSet
if the variable test
is not set in the model
eachFiltered
Syntax: {{#eachFiltered input}}output{{/eachFiltered}}
eachFiltered
helps you iterate through input. eachFiltered
operates very similarly to the built-in each
helper. It differs by not updating the @first and @index attributes unless the nested block outputs something other than whitespace. This can be helpful when outputting delimiters when you may be filtering out content using if
or endsWith
. The @last attribute is not provided as the current block does not know whether the next will provide output or not.
A typical use case for this would be to iterate over something in the data model and then output delimiters between your content for entries that have not been filtered out.
The eachFiltered
helper most often is used with an if
helper to comma separate each of the generated objects when iterating over an array.
For example, if the data model contains the following:
{
"units": [
{
"name": "unit1",
"value": 1
},
{
"name": "unit2",
"value": 2
}
]
}
Then the following template would output a comma separated list of the units:
{{#eachFiltered units}}{{#eachFiltered this}}{{#if @index}},{{/if}}
{{name}}
{{/eachFiltered}}
Will generate the following (some whitespace not included here):
unit1,unit2
endsWith
Syntax: {{#endsWith input1 input2}}output{{/endsWith}}
endsWith
will optionally include a block if the first argument ends with the second argument.
A typical use for this would be to filter out stream ids unless they match a particular suffix.. For example, if streamId=00000000-00000000-123456FF-FF123456/ts1/bat
in the data model then {{#endsWith streamId '/ts1/bat'}}battery{{/endsWith}}
will output battery.
This helper is very similar to the built-in if
helper and also provides else
sections. An else section looks like {{#endsWith streamId '/ts1/bat'}}battery{{else}}notbattery{{/endsWith}}
and would output notbattery if the streamId argument does not end with the supplied suffix.
eq
Syntax: {{#eq input1 input2}}output{{/eq}}
eq
will optionally include a block if the first argument is equal to the second argument. The comparison is case sensitive.
This can be used in any situation where you want to check if something in the data model matches a particular value or if two parts of the data model are equal.. For example, if streamId=00000000-00000000-123456FF-FF123456/ts1/bat in the data model then {{#eq streamId '00000000-00000000-123456FF-FF123456/ts1/bat'}}battery{{/eq}}
will output battery.
This helper is very similar to the built-in if
helper and also provides else sections. An else section looks like {{#eq streamId '00000000-00000000-123456FF-FF123456/ts1/bat'}}battery{{else}}notbattery{{/eq}}
and would output notbattery if the streamId argument is not equal to the supplied value.
extract
Syntax: {{extract regular-expression input}}
Extract substring(s) using a regular expression.
The regular expression is evaluated using Google RE2 regular expression syntax.
The first match of the regular expression is found in the input text. If the regular expression doesn’t contain capture groups, then the match is returned as output. If the regular expression contains 1 or more capture groups, then the capture groups are concatenated together and returned as output.
For example, with the data model containing input=This is a RegularExpression extraction
, the template expression {{extract 'Reg[a-zA-Z]+' input}}
will output RegularExpression
while the template expression {{extract '(?i)(this is ).*(extraction)' input}}
will output This is extraction
.
firstPathComponent
Syntax: {{firstPathComponent input}}
firstPathComponent
outputs the first entry in a path.
A typical use for this would be to extract the device identifier from a stream ID. For example, if streamId=00000000-00000000-123456FF-FF123456/ts1/bat in the data model then {{firstPathComponent streamId}}
will output 00000000-00000000-123456FF-FF123456.
formatNum
Syntax: {{formatNum format input}}
Format a number or string that can be parsed as a number given a formatting string.
For example, {{formatNum '###0;-###0' 1234.567}}
results in 1235
, while {{formatNum '#,###.00;(#,###.00)' -1234.567}}
results in (1,234.57)
.
formatTime
Syntax: {{formatTime input}}
formatTime
will output a date and time in ISO-8601 format. The input is expected to be a UNIX timestamp in milliseconds.
Millisecond timestamps are common in monitor events, so this helper is useful when an ISO-8601 time is preferred. For example, if timestamp=1637353224575 in the data model then {{formatTime timestamp}}
will output 2021-11-19T20:20:24.575Z.
gt
Syntax: {{#gt input1 input2}}output-if-true{{else}}output-if-false{{/and}}
Compare input1 is greater than input2.
Supports an else section and multiple operators. For example, {{#gt input1 input2}}Yes{{else}}No{{/and}}
will output Yes or No.
Expression {{gt input1 input2}}
renders true or false directly.
Expression {{gt input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
Note: Most elements are strings, even if they are extracted as digits from prior output or set as digits in a variable expression.
To compare numbers use the number
helper to convert the strings to numbers first.
{{#gt (number input1) (number input2)}}output-if-true{{else}}output-if-false{{/and}}
gte
Syntax: {{#gte input1 input2}}output-if-true{{else}}output-if-false{{/and}}
Compare input1 is greater than or equal to input2.
Supports an else section and multiple operators. For example, {{#gt input1 input2}}Yes{{else}}No{{/and}}
will output Yes or No.
Expression {{gte input1 input2}}
renders true or false directly.
Expression {{gte input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
Note: Most elements are strings, even if they are extracted as digits from prior output or set as digits in a variable expression.
To compare numbers use the number
helper to convert the strings to numbers first.
{{#gte (number input1) (number input2)}}output-if-true{{else}}output-if-false{{/and}}
join
Syntax: {{join input(s) separator}}
Joins an array, iterator or iterable using a separator.
If the model contains input1=a
, input2=b
, expression {{join input1 input2 input3, 'c' ', '}}
renders a, b, c
.
json
Syntax: {{json input [pretty=true] [escapeHTML=true] [default="{}"]}}
json
will generate correctly formatted json for the input object. An input value of this
can be used to use the current object. The pretty
parameter can be used to output the json in a more human readable format. The escapeHTML
parameter can be used to escape HTML characters in the output. The default
parameter can be used to specify a default value to output if the input is null or empty.
A typical use of this would be to output an object after some looping or filtering is done, for example inside an if
, endsWith
, eachFiltered
expression, or other helper.
With a data model of:
[
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/notbat", "data": "0"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat", "data": "1"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat", "data": "2"}
]
The template:
[{{#eachFiltered this}}{{#if @index}},{{/if}}{{json this pretty=true}}{{/eachFiltered}}]
NOTE: You can use a template like this to always turn monitor payloads into a JSON formatted list of elements even if there is only one element in the payload.
Will generate:
[{
"streamId" : "00000000-00000000-123456FF-FF123456/ts1/notbat",
"data" : "0"
},{
"streamId" : "00000000-00000000-123456FF-FF123456/ts1/bat",
"data" : "1"
},{
"streamId" : "00000000-00000000-123456FF-FF123456/ts1/bat",
"data" : "2"
}]
lower
Syntax: {{lower input}}
Convert the input string to lowercase.
Expression {{lower 'HELLO'}}
renders hello
.
lt
Syntax: {{#lt input1 input2}}output-if-true{{else}}output-if-false{{/and}}
Compare input1 is less than input2.
Supports an else section and multiple operators. For example, {{#lt input1 input2}}Yes{{else}}No{{/and}}
will output Yes or No.
Expression {{lt input1 input2}}
renders true or false directly.
Expression {{lt input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
Note: Most elements are strings, even if they are extracted as digits from prior output or set as digits in a variable expression.
To compare numbers use the number
helper to convert the strings to numbers first.
{{#lt (number input1) (number input2)}}output-if-true{{else}}output-if-false{{/and}}
lte
Syntax: {{#lte input1 input2}}output-if-true{{else}}output-if-false{{/and}}
Compare input1 is less than or equal to input2.
Supports an else section and multiple operators. For example, {{#lte input1 input2}}Yes{{else}}No{{/and}}
will output Yes or No.
Expression {{lte input1 input2}}
renders true or false directly.
Expression {{lte input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
Note: Most elements are strings, even if they are extracted as digits from prior output or set as digits in a variable expression.
To compare numbers use the number
helper to convert the strings to numbers first.
{{#lte (number input1) (number input2)}}output-if-true{{else}}output-if-false{{/and}}
mul
Syntax: {{mul input1 input2...}}
Multiply all the numbers together. The inputs can be numbers or strings that can be converted to numbers.
With a model value of input1=42
, expression {{mul input1 2}}
renders 84.0
.
Note: Input numbers are converted to decimals and may be formatted with decimal points. Use the formatNum
helper to control the format.
For example, with a model value of input1=42
, expression {{formatNum '###0;-###0' (add input2 2)}}
results in 84
.
neq
Syntax: {{#neq input1 input2}}output-if-true{{else}}output-if-false{{/and}}
Compare input1 is not equal to input2.
Supports an else section and multiple operators. For example, {{#neq input1 input2}}Yes{{else}}No{{/and}}
will output Yes or No.
Expression {{neq input1 input2}}
renders true or false directly.
Expression {{neq input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
Note: Most elements are strings, even if they are extracted as digits from prior output or set as digits in a variable expression.
To compare numbers use the number
helper to convert the strings to numbers first.
{{#neq (number input1) (number input2)}}output-if-true{{else}}output-if-false{{/and}}
not
Syntax: {{#not input}}output-if-true{{else}}output-if-false{{/and}}
The not operator, determines if all inputs are falsy
(false or empty). Can be used with non-boolean values.
Supports an else section and multiple operators. For example, {{#not input1}}Yes{{else}}No{{/and}}
will output Yes or No.
Expression {{not input1}}
renders true or false directly.
Expression {{not input1 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
now
Syntax: {{now [format] [tz=timezone|timeZoneId]}}
Generate a human-readable formatted current date or time.
The format
parameter is a string that specifies the format of the output.
Format can be one of
full
- for example Thursday, March 28, 2024long
- for example, March 28, 2024medium
- for example, Mar 28, 2024short
- 3/28/24- Or a pattern matching the java DatetimeFormatter syntax.
The tz
parameter is optional and specifies the timezone to use. If the tz
parameter is not provided, the timezone of the system is used.
- Expression
{{now}}
rendersMar 28, 2024
- Expression
{{now format='full' tz='America/Chicago'}}
rendersThursday, March 28, 2024
- Expression
{{now format='full' tz='America/Chicago'}}
renders03/28/24
- Expression
{{now format='YYYY/MM/DD hh:mm:ss a' tz='America/Chicago'}}
renders2024/03/88 10:45:36 AM
- Expression
{{now format='YYYY/MM/DD HH:mm:ss z' tz='America/Chicago'}}
renders2024/03/88 10:45:36 AM
- Expression
{{now format='YYYY/MM/DD HH:mm:ss zzz' tz='America/Chicago'}}
renders2024/03/88 10:51:07 CDT
number
Syntax: {{number input}}
The input can be a number or string that can be converted to a number.
Used to convert a string to a number. Most useful for comparison helpers that require numeric comparison instead of text comparison or to change the formatting of an existing number using formatNum
helper.
Expression {{formatNum '#,###;-#,###' (number '122342')}}
renders 122,342
.
or
Syntax: {{#or input1 input2...}}output-if-true{{else}}output-if-false{{/and}}
The or operator determines if any inputs are truthy
(true or not empty). Can be used with non-boolean values.
Supports an else section and multiple operators. For example, {{#or input1 input2 input3}}Yes{{else}}No{{/and}}
will output Yes if all inputs are truthy, otherwise No.
Expression {{or input1 input2}}
renders true or false directly.
Expression {{or input1 input2 yes='Result Yes' no='Result No'}}
renders Result Yes
or Result No
directly.
remainingPathComponents
Syntax: {{remainingPathComponents input}}
remainingPathComponents
outputs all parts of a path beyond the first entry.
A typical use for this would be to skip the device identifier in a stream ID to output the common stream ID. For example, if streamId=00000000-00000000-123456FF-FF123456/ts1/bat in the data model then {{remainingPathComponents streamId}}
will output ts1/bat.
replace
Syntax: {{#replace input match1 replacement1 [match2 replacement2 ...]}}{{@replaced}}{{/replace}}
replace
combines an if statement with text replacement. It replaces the input string content. All occurrences of match text is replaced with replacement text, and the result is assigned the @replaced
attribute. Multiple replacements can be specified by including additional pairs of match and replace parameters and the replacement occurs from left most parameters to rightmost.
For example, with field=abc in the data model, then {{#replace field "a" "z" "b" "z" "c" "z"}}{{@replaced}}{{/replace}}
will output zzz
.
A typical use for this might be to generate short device IDs by replacing the duplicate zero’s or generating names from streams.
For example, if streamId=00000000-00000000-123456FF-FF123456/ts1/bat in the data model then {{#replace streamId "00000000-00000000-123456FF-FF123456/" "" "ts1/bat" "Battery"}}{"{{@replaced}}": 99}{{/replaced}}
will output {"Battery: 99}
.
slice
Syntax: {{slice input separator start length}}
slice
slice a string with separators into individual components given. slice
first splits the string to a list using the separator, then uses the start and length parameters to select components of the list that are output. The output components are joined with the separator before being output.
- The start value is the 1-based index from the start of the list (1 is the first component)
- Use a negative start value to slice from the end of the list (-1 is the last component)
- Start must be non-zero and length must be greater than 0
- If the absolute value of the start parameter is greater than the number of components, then an empty string is output.
- If the length parameter is greater than the number of components remaining in the list, then all remaining components are output.
A typical use case for this might be to extract several path components from the end of a stream ID.
For example, if streamId=00000000-00000000-123456FF-FF123456/ts1/bat
in the data model then {{slice streamId '/' -2 2}}
will output ts1/bat
.
Similarly, with streamId=00000000-00000000-123456FF-FF123456/metrics/sys/cpu/used
in the data model then {{slice streamId '/' 3 2}}
will output sys/cpu
.
slugify
Syntax: {{slugify input}}
Generate a slug from the input string. A slug is a URL-friendly version of a string, typically used for URLs or filenames.
Converts to lowercase, removes any non-word characters and convers spaces to hyphens. Strips whitespace from the beginning and end of the string.
Expression {{slugify 'Hello, World!'}}
renders hello-world
.
substring
Syntax: {{substring input start-index end-index}}
Extracts a substring from the input string. The start-index is the 0-based index of the first character to include in the output. The end-index is the 0-based index and the output does not include the character at this index. The end-index is optional and if not provided, the substring will include all characters from the start-index to the end of the string.
Expression {{substring 'Hello, World!' 3 5}}
renders lo
.
Expression {{substring 'Hello, World!' 7}}
renders World!
.
upper
Syntax: {{upper input}}
Convert the input string to uppercase.
Expression {{upper 'Hello'}}
renders HELLO
.
yesno
Syntax: {{yesno booleanval [yes='YesValue'] [no='NoValue'] [maybe='MaybeValue']}}
Renders a yes, no or maybe value based on the input boolean value. If the input is not boolean this helper will fail. If the input is true render the yes value, false render the no value, and if the input is null or unset render the maybe value.
For example, {{yesno true yes='True' no='False' maybe='Maybe'}}
will output True
.
NOTE: Convert the input to boolean using the and
or or
helper if needed.
For example, {{yesno (and 'inputstring') yes='True' no='False' maybe='Maybe'}}
will output True
.
Additional Examples
Filtering
For example, given the data model
[
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/notbat"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat"}
]
and template
[
{{#eachFiltered this}}
{{#if @index}},{{/if}}
{{#endsWith streamId 'ts1/bat'}}
{"battery":99}
{{/endsWith}}
{{/eachFiltered}}
]
the eachFiltered
helper would ensure that the @index attribute is not incremented after the first item is entirely filtered out which lets it be used to determine if a delimiter should be included in the output. In this case it allows for the template to output JSON with the correct comma placement, resulting in output of (some newlines not shown):
[
{"battery":99}
,
{"battery":99}
]
NOTE: Whitespace has been added to the template and removed from the output for clarity.
Filtering with replacement
For example, given the data model
[
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/notbat", "data": "0"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat", "data": "1"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat", "data": "2"}
]
and template
[
{{#eachFiltered this}}
{{#replace (slice streamId "/" -2 2) "ts1/bat" "Battery" "cl2/cval" "Sensor"}}
{{#if @index}},{{/if}}
{
"{{@replaced}}": {{data}}
}
{{/replace}}
{{/eachFiltered}}
]
The replace
helper would ensure that only streams that are of interest are used, and, along with the
subexpression using the slice
helper those streams are used to name the output data.
The output would be
[
{
"Battery": 1
},
{
"Battery": 2
}
]
NOTE: Whitespace has been added to the template and removed from the output for clarity.
Note the built-in each helper can generally be used instead if you are not filtering out entries while iterating.
Use Streams as item names
For example, given the data model
[
{"streamId":"00000000-00000000-123456FF-FF123456/cl2/val", "data": "0"},
{"streamId":"00000000-00000000-123456FF-FF123456/ts1/bat", "data": "1"},
{"streamId":"00000000-00000000-123456FF-FF123456/cl1/cval", "data": "2"}
]
and template
[
{{#eachFiltered this}}
{{#replace (slice streamId "/" -2 2) "/" "."}}
{{#if @index}},{{/if}}
{
"{{@replaced}}": {{data}},
"unit": "{{firstPathComponent streamId}}"
}
{{/replace}}
{{/eachFiltered}}
]
The combined use of the replace
helper maps the stream name into a valid json name, and, along with the
subexpression using the slice
helper those streams are used to name the output data.
The output would be
[
{
"cl2.val": 0,
"unit": "00000000-00000000-123456FF-FF123456"
},
{
"ts1.bat": 1,
"unit": "00000000-00000000-123456FF-FF123456"
},
{
"cl1.cval": 2,
"unit": "00000000-00000000-123456FF-FF123456"
}
]
NOTE: Whitespace has been added to the template and removed from the output for clarity.