Skip to content
English
  • There are no suggestions because the search field is empty.

Function Element

1. What a Function element actually is

A Function element is a little calculator you drop into a flow. It doesn't ask the operator anything. Instead it looks at answers that have already been given, does something with them, and shows a result.

Think of it like a formula cell in a spreadsheet. The operator fills in the boxes; the Function element does the sum.

Typical jobs it does:

  • Turn a reading into something else (°C into °F, a measurement into an area)
  • Add up defects, scrap or counts across a whole batch
  • Turn a set of yes/no checks into a score or a Pass/Fail
  • Compare what was measured against the target on the unit

A few things that are true of every Function element:

It updates itself There's no "calculate" button. Change an answer it depends on and it recalculates a second later.
It works offline Behaves exactly the same in the mobile app with no signal.
It can be invisible Mark it Hidden and it still calculates and stores its value — the operator just never sees it. Handy when it's only there to feed another calculation or a piece of logic.
It's sandboxed It can't reach the internet or pull in anything from outside the report. Everything it uses has to come from the flow itself.
2. How to read the code in this guide

The instructions you type are a stripped-down version of JavaScript. You don't need to learn JavaScript — you mostly need to recognise about eight symbols. Here they are:

You'll see It means
answer("temp") A command, with the thing it acts on in round brackets. Read it as "fetch the answer tagged temp".
"double quotes" A piece of text. Everything inside the quotes has to match exactly, including capital letters.
* / + - Multiply, divide, add, subtract. + also glues two bits of text together.
=== "is exactly equal to". Three equals signs, not one. Used for checking, not for setting.
? ... : ... "if… then… otherwise…". See the Pass/Fail example below.
[ ] A list of things. [2, 0, 1] is a list of three numbers.
{ } A little bundle of options you tack on to narrow down what a command looks at.
var name = ... Creates a named box to keep something in, so you can use it again further down.
return "This is the answer — show this." If the last line is just a value, you can leave return off entirely.

One habit worth having: anything in quotes is compared letter-for-letter. "Yes" and "yes" are two different things as far as the Function element is concerned. This causes more head-scratching than everything else combined.

3. Fetching an answer: answer() vs answers()

This is the single most important distinction in the whole feature.

Command What it gives you back
answer("tag") One value. The answer to that question.
answers("tag") A list of values — every time that question was answered.

Both work off the tag you've put on the element in the flow builder, not the question wording.

Doubling a reading

answer("temp reading") * 2

The operator typed 42, so this shows 84.

A simple Pass/Fail

answer("ppe check") === "Yes" ? "Pass" : "Fail"

Read it left to right: is the PPE check answer exactly "Yes"? If it is, show "Pass". If it's anything else at all, show "Fail".

Getting every answer to a repeated question

answers("defect count")

If that question was answered three times — once per unit in a loop — this gives you back the list [2, 0, 1].

Note what it does not do: it doesn't add them up. You get the three separate numbers. Adding them up is a separate step, covered in section 7.

Counting how many times something was answered

answers("result").length

.length means "how many items are in this list". If "result" was answered 8 times, this shows 8.

4. Narrowing down what gets fetched (scope)

By default, answers() sweeps up every matching answer in the whole report. Often that's too wide — especially if the same tag is used in more than one step.

To narrow it, add a bundle of options in curly brackets after the tag:

Write this And it only looks at
answers("tag", { scope: Scope.loopIteration }) Answers from the current pass through a content loop
answers("tag", { scope: Scope.local }) Answers in the same section as this Function element
answers("tag", { step: "Final inspection" }) Answers from one named step
answers("tag", { type: "number" }) Answers from number-type elements only
answers("tag", { scope: Scope.global }) The whole report (this is the default)

Example — pulling a reading from one specific step:

answer("pressure", { step: "Final inspection" })

This only reads the pressure captured in "Final inspection". Any earlier step reusing the same pressure tag is ignored.

5. Working inside a Content Loop

A Content Loop runs the same set of questions once per unit — pallet by pallet, machine by machine. That creates two very different needs, and where you place the Function element decides which one you get.

Inside the loop → one result per unit

Put the Function element inside the loop and scope every read to the current iteration:

answer("length",     { scope: Scope.loopIteration }) * answer("width",     { scope: Scope.loopIteration })

For a unit measuring 4 by 2.5, this shows 10 — that unit's area only, with no other unit's numbers mixed in.

Get in the habit: scope every read inside a loop. If even one read is left unscoped, it quietly pulls in data from the other iterations and your per-unit figure goes wrong without any error appearing.

Outside the loop → one result for the whole batch

Put the Function element after the loop has finished:

sum(answers("defect count"))

The loop ran five times with defect counts of 2, 0, 1, 0 and 3, so this shows 6 — the batch total.

Reading the unit the loop is currently on

loop.subject.name + ": " + loop.subject.data["Line"]

Remember + glues text together. If the loop is currently on "Pallet 12" and its Line attribute is "Line 3", this shows Pallet 12: Line 3.

6. Pulling in things that aren't answers

Not everything you need was typed in by the operator. Three keywords give you the surrounding context.

subject — the unit the report is about

Write Get
subject.name Its display name
subject.data["Unit attribute name"] One of its data attributes
subject.idc.maximoID Its external ID

Example — target vs actual:

subject.data["target_weight"] - answer("measured weight")

The unit's target is 500 and the operator measured 485, so this shows 15 — how far under target it came in.

loop — the unit the current loop iteration is on

Same shape as subject: loop.subject.name, loop.subject.data["Line"].

param() — a unit passed into the workflow as a parameter

param("shift").name

Run under the "Night Shift" parameter, this shows Night Shift.

Easy trap: with param(), custom properties live under .prop, not .data — so it's param("target_sku").prop.product_name. Everywhere else it's .data.

user — whoever is filling in the report

user.name and user.email.

7. Doing maths across a set of answers

These take a list (so pair them with answers(), not answer()) and give back a single number.

Function What it does Example Result
sum(...) Adds everything up sum(answers("defect count")) on [2,0,1,0,3] 6
mean(...) Average mean(answers("pressure")) on [100,102,98] 100
max(...) Highest value max(answers("temp")) on [68,72,70] 72
std(...) Standard deviation — how spread out the values are std(answers("fill weight")) a single number
round(x, 1) Rounds x to 1 decimal place round(2.96, 1) 3.0

std is the useful one for spotting a filler that's drifting: a low number means every unit came out much the same, a high number means they're all over the place.

Example — scrap percentage to one decimal:

round(       (sum(answers("scrap count")) / sum(answers("units produced")))         * 100, 1)

15 scrapped out of 500 produced, so this shows 3.0.

If you need to tidy a list up before adding it together, map, filter, forEach and sort are available too — the next section shows map in action.

8. A longer example, line by line

Scoring a set of yes/no checks as a percentage:

var scores = answers("checklist item").map(function (value) {   return value === "yes" ? 1 : 0; }); return round((sum(scores) / scores.length) * 100, 1);

Line by line:

  1. answers("checklist item") — fetch every checklist answer as a list, e.g. ["yes", "no", "yes", ...].
  2. .map(function (value) { ... }) — go through that list one item at a time and swap each item for something else. value is just a temporary name for whichever item we're on.
  3. value === "yes" ? 1 : 0 — the swap: a "yes" becomes 1, anything else becomes 0. We now have a list of 1s and 0s.
  4. var scores = ... — keep that new list in a box called scores.
  5. sum(scores) / scores.length — the total (how many passed) divided by how many there were.
  6. * 100 and round(..., 1) — turn it into a percentage with one decimal place.

7 out of 10 answered "yes" gives 70.0.

9. Two rules you have to stick to

Use the older style of JavaScript (ES5)

Practically, this means:

✅ Use ❌ Don't use
var let, const
function (x) { ... } (x) => { ... } (arrow functions)
"text " + value `text ${value}` (backtick templates)

Using any of the right-hand column throws a script error.

Match the exact text of your answer options

Before writing === "Yes", go and look at how the option is actually written on the element. "Yes", "yes" and "YES" are three different things.

10. When it doesn't work

"It returns 0 or nothing, but answers were definitely captured"

Almost always the tag isn't matching. Diagnose it by temporarily replacing your whole function with just the fetch:

return answers("your tag");
  • You get an empty list [] → the tag doesn't match anything. Go back to the flow builder and check the spelling and capitalisation on the source elements. Tags are case-sensitive.
  • You get values, but your real calculation still gives 0 → look at one value on its own with return answers("your tag")[0]; ([0] means "the first item") and compare it against what your code is testing for. Usually it's "Yes" vs "yes", or a number arriving as text.

"My yes/no comparison isn't matching, even though the answer looks right"

Same root cause, and it's sneaky because it fails silently rather than erroring. Either inspect the raw value as above, or make the comparison forgiving:

answer("ppe check").toLowerCase() === "yes"

.toLowerCase() flattens the value to lower case before comparing, so "Yes", "YES" and "yes" all match.

"My per-unit calculation is picking up other units' data"

Check that every answer() and answers() inside the loop has { scope: Scope.loopIteration } on it. One unscoped read is enough to contaminate the result.

"My batch roll-up outside the loop finds nothing"

answers() should reach across all iterations by default from outside the loop. If it comes back empty, rule scope out by stating it explicitly:

sum(answers("defect count", { scope: Scope.global }))

"It's not recalculating when I change an answer"

Recalculation runs on a short delay after a dependency changes — give it a moment. If it still doesn't move, check that the tag you changed is genuinely one this function reads. A typo in a tag name inside your code doesn't throw an error; it just silently returns nothing for that read.

"I'm getting a script error instead of a result"

The error tells you the message and the line number. The two usual culprits:

  • Newer JavaScript syntax slipping in (let, const, arrow functions, backticks) — see section 9.
  • A loop that never ends, like while (true) {}, hitting the evaluation limit.
11. Cheat sheet
answer("tag")                                  one value answers("tag")                                 a list of values answers("tag").length                          how many  answer("tag", { step: "Step name" })           only from that step answers("tag", { scope: Scope.loopIteration }) only this loop pass answers("tag", { scope: Scope.local })         only this section  sum / mean / max / std (answers("tag"))        maths across a list round(value, 1)                                to one decimal  subject.name                                   the report's unit subject.data["attribute"]                      its attributes loop.subject.name                              the loop's current unit param("name").prop.property                    a workflow parameter user.name                                      who's filling it in  a === "Yes" ? "Pass" : "Fail"                  if / then / otherwise "text " + value                                join text together