Rhai Cookbook
Rhai is the general-purpose scripting option in a Script Task pipeline. Reach for it when a single FEEL expression or jq filter is awkward — building reference strings from many pieces, looping with intermediate state, conditional multi-statement logic.
The Rhai engine is constructed fresh per call with strict per-engine limits and no IO functions registered — Rhai steps cannot read files, open sockets, or shell out. See Script Task for the limit knobs.
How Rhai writes back: the Rhai scope is seeded with every current variable that is a valid identifier. After the script runs, every scope entry that was added or changed is merged into the process variable map. There is no “result variable” for Rhai — assigning to a name is the write.
For the full language reference see The Rhai Book. What follows are the patterns that come up most often in Conduit pipelines.
Setting variables
1. A single new variable
let total = price * qty;
2. Multiple new variables
let subtotal = price * qty;
let tax = subtotal * 0.10;
let grand = subtotal + tax;
3. Mutate an existing variable
counter += 1;
The variable must already exist in the scope (i.e. it was set on the instance before this step).
4. Set with a default if missing
if !is_def_var("retries") { let retries = 0; }
retries += 1;
5. Compute one field from many
let grn_ref = "GRN-" + vendor + "-" + doc_no + "-" + ts.to_string();
Strings
6. Concatenation
let label = customer_name + " (" + customer_id + ")";
7. Uppercase / lowercase
let tag = vendor.to_upper();
8. Substring
let prefix = sku.sub_string(0, 4);
9. Replace
let safe = description.replace(",", "·");
10. Contains / starts-with / ends-with
let is_invoice_ref = ref.starts_with("INV-");
11. Split into an array
let parts = email.split("@");
let user = parts[0];
let host = parts[1];
12. Pad with leading zeros
let padded = ("00000" + counter.to_string());
let padded = padded.sub_string(padded.len() - 5, 5);
Numbers
13. Arithmetic
let total = (subtotal * (1.0 + tax_rate)).to_int();
14. Round to two decimals
let rate = ((received.to_float() / ordered.to_float()) * 10000.0).to_int().to_float() / 100.0;
15. Min / max
let capped = if amount > 1000 { 1000 } else { amount };
Booleans and conditionals
16. If-else expression
let tier = if total >= 1000 { "gold" } else { "silver" };
17. Multi-branch tier
let tier = if total >= 5000 {
"platinum"
} else if total >= 1000 {
"gold"
} else if total >= 100 {
"silver"
} else {
"bronze"
};
18. Switch on a value
let label = switch status {
"pending" => "Awaiting approval",
"approved" => "Ready to ship",
"rejected" => "Closed",
_ => "Unknown",
};
19. Type-of guard
let normalized_qty = if type_of(qty) == "i64" { qty.to_float() } else { qty };
Loops
20. For over a range
let sum = 0;
for i in 0..10 { sum += i; }
21. For over an array
let total = 0;
for line in order_lines {
total += line.qty * line.price;
}
22. While
let i = 0;
while i < retries { i += 1; }
23. Break and continue
let first_error = ();
for r in results {
if r.status == "ok" { continue; }
first_error = r;
break;
}
The unit literal () represents “no value”; the engine drops it on merge-back.
Arrays
24. Build an array
let tags = [];
tags.push("priority");
if customer.tier == "gold" { tags.push("vip"); }
25. Length
let line_count = order_lines.len();
26. Filter into a new array
let active = users.filter(|u| u.disabled_at == ());
27. Map into a new array
let amounts = invoices.map(|i| i.total);
28. Reduce / fold
let total = invoices.reduce(|acc, i| acc + i.total, 0);
29. Sort
let by_date = orders.clone();
by_date.sort(|a, b| a.created_at.compare(b.created_at));
Objects (object maps)
30. Build a map
let summary = #{
total: grand,
line_count: order_lines.len(),
has_tax: tax > 0.0,
};
#{} is Rhai’s object-map literal.
31. Read a field that may not exist
let zip = if customer.contains("address") && customer.address.contains("zip") {
customer.address.zip
} else {
""
};
Error handling
32. Try / catch
let port = 0;
try {
port = config_port.to_int();
} catch (err) {
port = 8080;
}
33. Throw to fail the step
if amount < 0 { throw "amount must be non-negative"; }
A thrown value fails the step with the message wrapped into a Rhai script error.
Counters and accumulators
34. Increment-or-create
if !is_def_var("counter") { let counter = 0; }
counter += 1;
35. Append-or-create a list
if !is_def_var("history") { let history = []; }
history.push(#{ ts: ts, status: status });
36. Conditional accumulator
if status == "shipped" {
if !is_def_var("shipped_count") { let shipped_count = 0; }
shipped_count += 1;
}
Putting it together
A two-step pipeline that aggregates over a list, then formats a human label:
Step 1 (rhai):
let total = 0;
for line in order_lines { total += line.qty * line.price; }
let line_count = order_lines.len();
Step 2 (rhai):
let summary_label = "Order " + order_id + ": " + line_count.to_string()
+ " lines, total " + total.to_string();
After running, the instance has total, line_count, and summary_label set.
See also
- Script Task — pipeline merge rules and Rhai sandbox limits
- jq Cookbook — when you mostly need to reshape JSON
- Expressions (FEEL) — FEEL cookbook
- The Rhai Book — full language reference