A transform is a small safe tool. It makes a new value and touches nothing else. It runs inside your server, with no call to the internet. It costs nothing. The same input always gives the same answer.
The settings of a transform sit flat on the step. They sit right next to
id, transform, skip_when, and fail_on. There is no extra box around
them. Use the setting names below exactly as written. A ? at the end of a
name means you can leave that setting out. The ref {{item.*}} only works
inside map expressions and inside foreach loops.
{ "id": "days", "transform": "date_diff",
"from": "{{step.deal.last_activity_date}}", "to": "now", "unit": "days" }#Collections
A collection is a list of items (an array). These tools work on lists.
| Transform | Parameters | Returns |
|---|---|---|
filter |
array, field, operator, value? — or conditions[] with mode? (all/any) |
array |
sort |
array, fields[] of { field, direction } |
array |
map |
array, expression — or apply + the applied transform's params; output_field? |
array |
reduce |
array, field, operator, initial? |
value |
find |
array, field, operator, value |
the first item that matches |
pluck |
array, field |
array of values |
sum |
array, field |
number |
min_by / max_by |
array, field |
one item |
unique |
array, field |
array |
flatten |
array, depth |
array |
slice |
array, start, end |
array |
group_by |
array, field, aggregate? |
object — or an array when you use aggregate |
partition |
the same parameters as filter |
{ matched, unmatched } |
to_list |
input (an object) |
array of { key, value } |
count |
input |
number |
join |
array, separator |
string |
enrich_by |
array, with, key, with_key?, fields?, prefix? |
array — each item is joined to its match in with, using the key |
#Reduce and map
reduce can use these operators: sum, count, avg, min, max,
concat, or, and.
filter can take many conditions at once. By default all of them must be
true (AND). Set mode: "any" and one true condition is enough (OR).
map has three kinds of expression:
- One ref. It pulls out that value and keeps its type.
- A math expression over
{{item.*}}. It can use+ - * / %,min,max,abs,ceil,floor, andround. - An object template.
output_field adds the answer to each item. Without it, the answer replaces
the item:
{ "id": "weighted", "transform": "map", "array": "{{step.deals}}",
"expression": "{{item.amount}} * {{item.stage_probability}} / 100",
"output_field": "weighted_amount" }group_by with aggregate works out numbers for each group. It returns an
array:
{ "id": "by_stage", "transform": "group_by",
"array": "{{step.deals}}", "field": "stage",
"aggregate": {
"count": { "operator": "count" },
"total": { "field": "amount", "operator": "sum" }
} }Without aggregate it returns { key: [items] }. To walk through the
groups, chain to_list and then map.
#Objects
| Transform | Parameters | Returns |
|---|---|---|
merge |
sources[], strategy (shallow / deep / concat) |
object |
pick |
source (ref) + fields[] — or source as an object template |
object |
omit |
source, fields[] |
object |
rename |
source, mapping (old → new) |
object |
set |
source, field, value |
object |
prefix_keys |
source, prefix |
object with every key prefixed |
project |
object, expression (an object template) |
one record, reshaped. It is map for a single object |
#Strings
| Transform | Parameters | Returns |
|---|---|---|
template |
template (a string with {{refs}}) |
string |
concat |
values[] |
string |
split |
input, delimiter |
array |
replace |
input, pattern, replacement, all |
string |
truncate |
input, max_length, suffix |
string |
strip_html |
input |
plain-text string |
lowercase / uppercase / trim |
input |
string |
starts_with / ends_with |
input, prefix / suffix |
boolean |
contains_any |
input, terms[], case_sensitive |
boolean: true if any term appears |
string_length |
input |
the number of characters |
sha256 |
input |
hex digest (a fixed-length fingerprint written in hex). Use it to make a stable id from your inputs |
encode_base64 / decode_base64 |
input |
string |
#Encoding and size
These help in two cases. Some operations have a size limit. And sometimes you need an id that is always the same, so a re-run repeats the work instead of making a copy.
| Transform | Parameters | Returns |
|---|---|---|
json_stringify |
input |
JSON string |
json_parse |
input |
parsed value |
json_byte_length |
input |
the size of the value as JSON, in bytes |
utf8_byte_length |
input |
the size of a string in UTF-8 (the usual text encoding), in bytes |
#Numbers
| Transform | Parameters | Returns |
|---|---|---|
to_number |
input |
number |
math |
left, operator, right? |
number |
round |
input, precision |
number |
clamp |
input, min, max |
number |
percent |
value, total, precision |
decimal (show it with :percent) |
weighted_score |
scores[] of { value, weight }, clamp? [min, max], precision? |
number |
math can use add, subtract, multiply, divide, and modulo. It can
also use abs, ceil, and floor. Those three take one number only.
#Dates
| Transform | Parameters | Returns |
|---|---|---|
date_diff |
from, to (accepts "now"), unit |
number |
date_format |
date, format, time_zone? |
string |
date_add |
date, amount, unit |
ISO 8601 string |
date_parse |
input, time_zone?, require_offset? |
Unix milliseconds, or null |
is_past / is_future |
date |
boolean |
date_period |
period, time_zone?, date? |
{ start, end } ISO strings |
Units are days, hours, minutes, and seconds.
date_period takes one of these names:
today, yesterday, tomorrow, this_week, last_week, this_month,
last_month, this_quarter, last_quarter, this_year, last_year,
last_7_days, last_30_days, last_90_days, next_7_days, next_30_days.
The start day and the end day are both included. Weeks are ISO weeks (weeks
start on Monday). The period comes from the run's own clock. So one run gets
one fixed answer, and running it again gives that same answer. With date,
you get the period around that time instead.
Give time_zone the value {{context.server.time_zone}}. That is your own
time zone. Then a day starts at your midnight. Without it, a day starts at
midnight in UTC (world clock time). In Los Angeles, that is 5 pm the day
before. The time_zone setting works the same way in date_format and
date_parse.
#Timestamps
| Transform | Parameters | Returns |
|---|---|---|
to_recent_date |
input (a number) |
The value turned into Unix milliseconds (milliseconds since 1970), or null when it is not a recent timestamp. Check the result with is_null. It never guesses |
#Calendar events
| Transform | Parameters | Returns |
|---|---|---|
event_when |
event, time_zone?, part? |
when a calendar event happens |
event_when takes a calendar event and says when it happens on your clock.
It reads start_at, end_at, and is_all_day. It gives back start, end,
day, time, date_time, and text. An all-day event says All day where
a time would go. Use part to get just one of them, like "part": "time".
Inside map, leave out field, and each whole event goes in.
#Logic
| Transform | Parameters | Returns |
|---|---|---|
compare |
left, operator (any of the 14), right? |
boolean |
any / all |
values[] (booleans) — or conditions[] (condition strings) |
boolean |
coalesce |
values[] |
the first value that is not null |
default |
value, fallback |
the value, or the fallback when it is null or an empty string |
not |
input |
the opposite boolean |
ternary |
if, then, else? |
one of two values |
switch |
input, cases (map), default |
the mapped value |
pluralize |
count, zero?, one, many |
the chosen text |
any and all evaluate only conditions. A condition written under values
is text, and text is always true there, so the validator warns about it.
default treats an empty string as missing. It keeps 0 and false. Use it
instead of coalesce when a template might turn into "".
#Privacy
| Transform | Parameters | Returns |
|---|---|---|
hash_replace |
data, fields[] |
{ data, mapping } |
hash_restore |
data, mapping |
the restored object or string |
redact |
data, fields[] (paths, [*] allowed), marker |
the object with those fields blacked out |
Use hash_replace before an AI step. Use hash_restore after it. That way
the AI model never sees the fields you chose. The mapping never leaves the
run.
#Display
| Transform | Parameters | Returns |
|---|---|---|
to_checklist |
title, items[] |
checklist object |
to_table |
array, columns[] of { field, label, format? } |
table object |
to_summary |
fields[] of { label, value } |
summary object |
to_csv |
array, columns?, header?, delimiter? |
CSV string |
to_slack_blocks |
blocks[] |
a Slack Block Kit (Slack's message layout format) payload, for a notification step that posts to Slack |
A checklist item looks like { label, issue, detail_ok, detail_issue, detail_null }.
See Checklist rows for which one a row shows.
A table column can have one of these formats: text, currency, number,
date, relative_date, boolean, percent.
A checklist item or a table column with type: "action" can carry an action.
An action always shows as a button the owner clicks. The click opens a
normal recipe run. All the usual rules and approvals still apply. An action
never runs on its own.
to_csv escapes text the way RFC 4180 (the standard CSV rules) says. Pair it with a file write. Then
you get a file a spreadsheet can open.
#Checklist rows
A checklist row's issue is a condition string. True shows as a problem
(detail_issue). False shows as fine (detail_ok).
A row shows as unknown, with detail_null, when what it judges is missing:
- a comparison (
equal,greaterand the rest) whose value is missing; is_empty,is_null,is_not_emptyoris_not_nullon a field whose record is missing, such as{{step.contact.result.email}}when the contact was never read;is_not_emptyoris_not_nullon a whole step's output or a setting that is missing.
is_empty or is_null on a whole output or a setting that is missing shows
the problem: the missing value is what it asks about. A row with no issue
shows detail_ok, or detail_null when a value it shows is missing. Without a
detail_null, an unknown row has no text, and the page shows it as no data.
A row reads label, issue, the three detail_ texts, action and
actions, and nothing else. There is no warn state, and a row cannot skip
itself: gate the whole step instead. The validator warns about any other key,
because it does nothing.
#Warehouse
An enrichment is a fact Recued works out ahead of time and sticks onto a
record. The enrichment-or-fetch transform reads one. It returns the stored
fact when one exists, is fresh, and is trusted enough. If not, it tells your fallback step
to fetch a fresh one.
| Parameter | Meaning |
|---|---|
ref |
The dotted enrichment ref, such as data.contact.{{step.email}}.company_summary |
trust_min? |
The lowest confidence you accept (default 0.8) |
max_age_ms? |
Reject rows older than this |
fallback_step? |
The step to jump to on a miss |
It returns { value, source, confidence?, fallback? }. source is one of
enrichment, miss, stale, low_trust, unparseable_ref, or
no_runtime.
#Related
- Recipe schema: the steps these tools run inside.
- Authoring recipes: when to use a transform and when to use an AI step.