Skip to main content

Data Transformation

General-purpose nodes for reshaping, computing, encoding, and extracting data between steps. Most accept {{ ... }} expressions in their config fields, resolved against steps, vars, trigger, secret, and input (see Workflows & the DSL).

transform

Remaps fields from upstream step outputs into a new output shape. config.mapping is a JSON object where each key becomes an output field and each value is an expression resolved against the step's inputs (the merged map of all upstream step outputs, trigger data, vars, etc.).

{
"id": "shape_lead",
"type": "transform",
"config": {
"mapping": {
"email": "{{steps.fetch_lead.output.body.email}}",
"full_name": "{{steps.fetch_lead.output.body.first_name}} {{steps.fetch_lead.output.body.last_name}}",
"source": "hubspot"
}
}
}
FieldTypeDefaultDescription
mappingobjectRequired. Map of output_key -> expression. Each expression is resolved with dsl.ResolveExpressions against the step's inputs.

Output: steps.<id>.output is exactly the resolved mapping object — one field per mapping key, with {{...}} expressions replaced by their resolved values.

set

Creates or overrides fields on top of a copy of all current inputs (every upstream step's output, merged). Use it to add computed fields or constants without losing access to everything already produced upstream.

{
"id": "annotate",
"type": "set",
"config": {
"fields": {
"status": "processed",
"processed_at": "{{steps.now.output.iso}}"
}
}
}
FieldTypeDefaultDescription
fieldsobjectRequired. Map of key -> value. Values support {{...}} expressions.

Output: steps.<id>.output is a deep copy of the merged inputs map (all upstream step outputs, by step ID) with fields overlaid on top — fields keys win on conflicts.

code

Runs user-supplied JavaScript or WASM in-process (via goja and wazero — no Docker container, no external runtime). For shell commands or multi-step file processing pipelines that need a real filesystem, use the agent's terminal/file tools instead, which run in the Sandbox.

{
"id": "transform_payload",
"type": "code",
"config": {
"language": "javascript",
"code": "const reply = inputs.steps.llm.text.toUpperCase();\nreturn { reply, length: reply.length };",
"timeout_ms": 10000
}
}
FieldTypeDefaultDescription
languagestring"javascript""javascript" ("js" also accepted) or "wasm".
codestringRequired. JS source for javascript; base64-encoded .wasm binary for wasm.
timeout_msnumber10000Max execution time in milliseconds. Values <= 0 or > 120000 fall back to 10000.

JavaScript (language: "javascript")

  • Powered by goja, a pure-Go ES5.1+ engine with a Node-like API.
  • Globals available: inputs (object containing all trigger and step outputs), console.log / console.warn / console.error (captured into __logs).
  • Code is wrapped in (function(inputs) { <code> })(inputs), so a top-level return { ... } works.
  • The return value becomes the step output:
    • If it's an object, its keys become steps.<id>.output.* (with __logs appended if any console.* calls were made).
    • If it's a primitive (string, number, etc.), the output is { "result": <value>, "__logs": [...] }.
    • If nothing is returned, the output is { "logs": [...] }.
const reply = inputs.steps.llm.text.toUpperCase();
return { reply, length: reply.length };

WASM (language: "wasm")

  • Powered by wazero, a pure-Go WASM runtime with WASI preview1 support — no host filesystem or network access.
  • code must be the base64-encoded .wasm module bytes.
  • The module must export:
    • memory
    • alloc(size i32) -> ptr i32
    • execute(input_ptr i32, input_len i32) -> output_ptr i32
  • The runtime writes a JSON-encoded inputs map into the module's memory (via alloc), calls execute, and reads a null-terminated JSON string (up to 64 KB) from the returned pointer as the output. The output JSON must be an object — it becomes steps.<id>.output directly.

Output: Shape depends on the script's return value (see above) — steps.<id>.output.<field> for object returns, or steps.<id>.output.result for primitives.

csv

Converts between CSV text and JSON arrays of row objects.

{
"id": "parse_csv",
"type": "csv",
"config": {
"operation": "csv_to_json",
"csv": "{{steps.download.output.body}}",
"has_header": "true",
"delimiter": ","
}
}
FieldTypeDefaultDescription
operationstring"csv_to_json""csv_to_json" or "json_to_csv".
delimiterstring","Single character used as the field delimiter. Only the first character is used; falls back to "," if empty.
has_headerstring"true"Set to "false" to treat the first row as data, not column names.

csv_to_json

FieldTypeDefaultDescription
csvstringRequired. CSV text (resolved expression). Parsed with lazy quotes and leading-space trimming.

Output:

FieldTypeDescription
dataarray of objectsOne object per row. With has_header: "true", keys are the header row's column names; otherwise keys are col1, col2, ...
rowsnumberRow count (excluding header).
columnsnumberColumn count.
headersarray of stringsColumn names (header row, or empty if has_header: "false").

json_to_csv

FieldTypeDefaultDescription
dataarrayRequired. Array of row objects (if has_header: "true") or arrays/scalars (if "false"). May be a JSON string, which is parsed first.

With has_header: "true", headers are the union of keys from the first array element, sorted alphabetically; every row is rendered using those keys (missing keys render as <nil>).

Output:

FieldTypeDescription
csvstringThe generated CSV text.
rowsnumberNumber of data rows written (excluding the header row if present).

xml

Parses XML into a nested JSON structure, or builds an XML document from a JSON object.

{
"id": "parse_feed",
"type": "xml",
"config": {
"operation": "parse",
"value": "{{steps.fetch.output.body}}"
}
}
FieldTypeDefaultDescription
operationstring"parse""parse" or "build".

parse

FieldTypeDefaultDescription
valuestringRequired. XML text to parse.

Parsing rules:

  • Each XML element becomes a map[string]any. Attributes become keys prefixed with @ (e.g. @id).
  • An element containing only text content collapses to that text string.
  • An empty element collapses to "".
  • Repeated sibling elements with the same tag name become a JSON array under that key.

Output: { "data": <parsed nested object> }.

build

FieldTypeDefaultDescription
dataobjectRequired. JSON object to render as XML (or a JSON string of one).
root_tagstring"root"Root element tag name.

Rendering rules: object keys become child elements; arrays render each item as <item>...</item>; scalars render as escaped element text.

Output: { "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>...</root>" }.

graphql

Sends a GraphQL query or mutation as a POST request to any GraphQL endpoint.

{
"id": "fetch_repo",
"type": "graphql",
"config": {
"url": "https://api.github.com/graphql",
"query": "query($owner:String!,$name:String!){ repository(owner:$owner,name:$name){ stargazerCount } }",
"variables": { "owner": "goagents", "name": "platform" },
"bearer_token": "{{secret.GITHUB_TOKEN}}"
}
}
FieldTypeDefaultDescription
urlstringRequired. GraphQL endpoint URL.
querystringRequired. GraphQL query or mutation document.
variablesobjectOptional GraphQL variables, sent if non-empty.
operation_namestringOptional operationName, sent if non-empty.
actionstringSemantic only ("query" or "mutation") — both send the same POST request.
bearer_tokenstringIf set, sent as Authorization: Bearer <token>.
headersobjectAdditional request headers (key: value).
timeout_secondsnumber60HTTP client timeout.

Output:

FieldTypeDescription
dataanyThe GraphQL response's data field.
statusnumberHTTP status code.
okbooleantrue if status is 2xx and the response had no errors field.
errorsarrayPresent if the response included GraphQL errors. When present, the step also returns a Go error ("graphql: server returned errors: ..."), which fails the step unless continue_on: "error" is set.

datetime

Date/time utilities: get the current time, format/parse, add/subtract durations, diff two timestamps, and convert timezones.

{
"id": "now",
"type": "datetime",
"config": {
"operation": "current",
"timezone": "America/New_York"
}
}
FieldTypeDefaultDescription
operationstring"current"current | format | parse | add | subtract | diff | convert_timezone.

Date format tokens (input_format / output_format) accept either a Go layout string (e.g. 2006-01-02) or friendly tokens YYYY, YY, MM, DD, HH, mm, ss, SSS — translated automatically (e.g. YYYY-MM-DD HH:mm:ss).

Operations

OperationExtra fieldsDescription
currenttimezone (default "UTC")Returns the current time in the given timezone.
formatvalue, input_format (optional), output_format (default Go RFC3339)Parses value and re-formats it as output_format.
parsevalue, input_format (optional)Parses value and returns the full date/time breakdown.
addvalue, input_format (optional), plus any of years, months, days, hours, minutes, seconds (numbers)Parses value, adds the given duration, returns the result.
subtractSame as addParses value, subtracts the given duration, returns the result.
difffrom, to (both date/time strings, auto-detected format)Returns the difference to - from.
convert_timezonevalue, input_format (optional), timezone (default "UTC")Parses value and re-expresses it in the target timezone.

years/months/days are approximated as 365/30/24-hour and 24-hour multiples respectively (no calendar-aware arithmetic).

If input_format is omitted, parse/format/add/subtract/convert_timezone try, in order: RFC3339, RFC3339Nano, 2006-01-02T15:04:05, 2006-01-02 15:04:05, 2006-01-02, 01/02/2006, 02/01/2006, Jan 2, 2006, RFC1123Z, RFC1123, RFC822Z, RFC822.

timezone accepts "UTC", "Local", or any IANA timezone name (e.g. "Asia/Kolkata").

Output for current, parse, add, subtract, convert_timezone:

FieldTypeDescription
isostringRFC3339 timestamp.
unixnumberUnix seconds.
unix_msnumberUnix milliseconds.
year / month / day / hour / minute / secondnumberDate/time components.
weekdaystringe.g. "Monday".
timezonestringResolved timezone name.
formattedstring2006-01-02 15:04:05.

Output for format: { "result": "<formatted string>", "unix": <int64> }.

Output for diff: { "seconds": int64, "minutes": int64, "hours": int64, "days": int64 } (all derived from to - from; days is hours / 24, truncated).

math-helper

Arithmetic and numeric utilities. Numeric fields accept JSON numbers or numeric strings.

{
"id": "calc_total",
"type": "math-helper",
"config": {
"operation": "add",
"a": "{{steps.cart.output.subtotal}}",
"b": "{{steps.cart.output.tax}}"
}
}
FieldTypeDefaultDescription
operationstring"add"See table below.

Operations

OperationFieldsOutput
adda, b{ "result": a + b }
subtracta, b{ "result": a - b }
multiplya, b{ "result": a * b }
dividea, b (b != 0){ "result": a/b, "quotient": trunc(a/b), "remainder": a mod b }
moduloa, b (b != 0){ "result": a mod b }
powerbase, exponent{ "result": base ^ exponent }
sqrta (a >= 0){ "result": sqrt(a) }
absa{ "result": abs(a) }
ceila{ "result": ceil(a) }
floora{ "result": floor(a) }
rounda, decimals (default 0){ "result": a rounded to "decimals" places }
loga (a > 0), base (default 10; 2 and 10 use optimized functions, otherwise natural-log change-of-base){ "result": log_base(a) }
percentagevalue, total (total != 0){ "result": (value/total) * 100 }
randommin (default 0), max (default 100, min <= max), integer (string, default "true"){ "result": random number in [min,max] } — integer if integer != "false"
mina, b{ "result": min(a, b) }
maxa, b{ "result": max(a, b) }

All operations return a single numeric result field except divide (also quotient/remainder).

text-helper

String manipulation: concatenation, splitting, regex find/replace, case conversion, HTML/Markdown conversion, padding, truncation, and more.

{
"id": "make_slug",
"type": "text-helper",
"config": {
"operation": "slugify",
"text": "{{steps.fetch_post.output.title}}"
}
}
FieldTypeDefaultDescription
operationstring"concat"See table below.

Operations

OperationFieldsOutput
concatparts (array, preferred) or a, b; separatorIf parts is set, joins all resolved items with separator; else { "result": a + separator + b }
splittext, separator{ "result": [parts...], "count": n }
replacetext, find, replace, use_regex ("true"/"false", default "false"), replace_all (default "true"){ "result": "<text with replacements>" }. With use_regex: "true", find is a regex. replace_all: "false" replaces only the first match.
findtext, find, use_regexRegex: { "found": bool, "first": string, "all": [...], "count": n }. Plain: { "found": bool, "index": int, "count": n }
slugifytext{ "result": "lowercase-hyphenated-text" } — non-alphanumeric runs collapse to single -, trimmed at edges
strip_htmltext{ "result": "<text content only>" }
html_to_markdowntext{ "result": "<markdown>" } — converts h1-h3, p, strong/b, em/i, code, pre, a, li, br, hr
markdown_to_htmltext{ "result": "<html>" } — converts headings, lists, blockquotes, ---/*** rules, code fences, and inline **bold**, _italic_, `code`, [text](url)
default_valuetext, default{ "result": text }, or default if text is empty/<nil>
trimtext, chars (optional){ "result": "<trimmed>" } — trims whitespace, or the given character set if chars is set
uppercasetext{ "result": "<TEXT>" }
lowercasetext{ "result": "<text>" }
title_casetext{ "result": "<Title Case>" }
lengthtext{ "result": <rune count>, "bytes": <byte count> }
reversetext{ "result": "<reversed text>" }
count_occurrencestext, find{ "result": <count> }
truncatetext, max_length (default 100), suffix (default "..."){ "result": "...", "truncated": bool, "original_length": n (only if truncated) }
padtext, length, pad_char (default " "), direction ("left" | "right", default "right"){ "result": "<padded text>" }
extract_betweentext, start, end{ "result": "<substring>", "found": bool } — text between the first occurrence of start and the following end

text_splitter

Chunks text for RAG/embedding pipelines, mirroring the LangChain/Flowise text-splitter family. This is the same chunking logic used by Knowledge Base document processing (chunk_size/chunk_overlap), exposed as a standalone step so you can chunk arbitrary text mid-workflow (e.g. before a custom embedding call).

{
"id": "chunk_doc",
"type": "text_splitter",
"config": {
"text": "{{steps.fetch_doc.output.body}}",
"strategy": "recursive",
"chunk_size": 1000,
"chunk_overlap": 100
}
}
FieldTypeDefaultDescription
textstringRequired. Text to split.
strategystring"recursive"recursive | character | markdown | code | token.
chunk_sizenumber1000Target max characters per chunk (or tokens for "token"). Values <= 0 reset to 1000.
chunk_overlapnumber100Characters/tokens shared between adjacent chunks. Negative resets to 0; if >= chunk_size, it's reduced to chunk_size / 5.
separatorstring"\n\n"Only used by the "character" strategy.

Strategies

StrategyBehavior
recursive (default)RecursiveCharacterTextSplitter — tries separators "\n\n", "\n", " ", "" in order, recursing into oversized pieces with the remaining separators, then merges pieces back up to chunk_size with chunk_overlap.
characterCharacterTextSplitter — splits on a single separator, then merges pieces up to chunk_size with chunk_overlap.
markdownRecursive split using Markdown-aware separators first (\n## , \n### , ... \n# ), then falling back to \n\n, \n, " ", "".
codeRecursive split using code-aware separators first (\nclass , \ndef , \nfunc , \nfunction ), then \n\n, \n, " ", "".
tokenApproximate token-based split at ~4 characters per token (chunk_size * 4 / chunk_overlap * 4 characters).

Resulting chunks are trimmed of surrounding whitespace and empty chunks are dropped.

Output:

FieldTypeDescription
chunksarray of stringsThe text chunks, in order.
countnumberNumber of chunks.
strategystringThe strategy used.

data-summarizer

Aggregates numeric values from an array of items, optionally extracting a field from each object first.

{
"id": "avg_score",
"type": "data-summarizer",
"config": {
"operation": "average",
"data": "{{steps.fetch_results.output.items}}",
"field": "score"
}
}
FieldTypeDefaultDescription
operationstring"sum"sum | average | count | min | max | count_unique | median | std_dev.
dataarrayRequired. Array of numbers or objects.
fieldstringIf set, each array element must be an object and the named field's value is extracted before aggregation; non-numeric values are skipped.

Operations

OperationOutput
sum{ "result": <sum>, "count": <n numeric values> }
average{ "result": <mean>, "sum": <sum>, "count": n } ({ "result": 0, "count": 0 } if no numeric values)
count{ "result": <total array length>, "numeric_count": <n numeric values> }
min{ "result": <min> } (errors if no numeric values)
max{ "result": <max> } (errors if no numeric values)
count_unique{ "result": <n unique stringified items>, "total": <array length> }
median{ "result": <median>, "count": n } (errors if no numeric values)
std_dev{ "result": <population std dev>, "variance": <variance>, "mean": <mean>, "count": n } (errors if no numeric values)

crypto

Hashing, HMAC signing, AES-256-GCM encryption/decryption, and Base64 encoding/decoding.

{
"id": "sign_payload",
"type": "crypto",
"config": {
"action": "hmac",
"algorithm": "sha256",
"value": "{{steps.build_body.output.json}}",
"secret": "{{secret.WEBHOOK_SIGNING_KEY}}"
}
}
FieldTypeDefaultDescription
actionstring"hash"hash | hmac | encrypt | decrypt | base64_encode | base64_decode.
valuestringThe input string for the selected action.

Actions

ActionExtra fieldsOutput
hashalgorithm (default "sha256"; md5|sha1|sha256|sha512){ "result": "<hex digest>", "algorithm": "<algo>", "input_length": <bytes> }
hmacalgorithm (default "sha256"; sha1|sha256|sha512|md5), secret{ "result": "<hex HMAC>", "algorithm": "hmac-<algo>" }
encryptkey{ "result": "<base64 ciphertext>", "algorithm": "aes-256-gcm" }key is SHA-256-derived into a 256-bit AES key; output is base64(nonce || ciphertext)
decryptkey{ "result": "<plaintext>" }value must be the base64 output of encrypt with the same key
base64_encode{ "result": "<base64>" }
base64_decode{ "result": "<decoded string>" } — tries standard then raw (no-padding) base64

compression

Gzip and Zip compression/decompression. Binary data is passed and returned as base64.

{
"id": "zip_report",
"type": "compression",
"config": {
"operation": "zip_create",
"files": [
{ "name": "report.csv", "content": "{{steps.to_csv.output.csv}}" },
{ "name": "summary.json", "content": "{{steps.summarize.output}}" }
]
}
}
FieldTypeDefaultDescription
operationstring"gzip_compress"gzip_compress | gzip_decompress | zip_create | zip_extract.

Operations

OperationFieldsOutput
gzip_compressvalue (string){ "data_base64": "<base64 gzip>", "original_size": <bytes>, "compressed_size": <bytes> }
gzip_decompressvalue (base64 or raw bytes){ "data": "<decompressed string>", "size": <bytes> }
zip_createfiles: array of { "name": string, "content": string }{ "data_base64": "<base64 zip archive>", "size": <bytes> }
zip_extractvalue (base64 or raw zip bytes){ "files": [{ "name", "content", "size" }, ...], "count": n }

For gzip_decompress and zip_extract, value is first base64-decoded if possible; if decoding fails it's treated as raw bytes.

qrcode

Generates a QR code as a base64-encoded PNG.

{
"id": "make_qr",
"type": "qrcode",
"config": {
"operation": "generate",
"content": "{{steps.create_invite.output.url}}",
"size": 256,
"error_correction": "medium"
}
}
FieldTypeDefaultDescription
operationstring"generate"Only "generate" is supported.
contentstringRequired. Text/URL to encode.
sizenumber256PNG width/height in pixels. Clamped to [64, 2048].
error_correctionstring"medium"low | medium | high | highest.

Output:

FieldTypeDescription
image_base64stringBase64-encoded PNG bytes.
image_data_urlstringdata:image/png;base64,<image_base64> — usable directly in an <img src> or sent as a Slack/email attachment.
contentstringThe encoded content.
sizenumberThe (clamped) pixel size used.
formatstring"png".

html_extract

Extracts structured data from HTML using simple CSS-like selectors (tag, .class, #id, tag.class, tag#id — no descendant/combinator support).

{
"id": "scrape_page",
"type": "html_extract",
"config": {
"source": "url",
"value": "https://example.com/blog/post",
"extractions": [
{ "name": "title", "selector": "h1", "attribute": "text" },
{ "name": "links", "selector": "a", "attribute": "href", "multiple": true }
]
}
}
FieldTypeDefaultDescription
sourcestring"html""html" (use value as inline HTML) or "url" (fetch value first, 30s timeout).
valuestringInline HTML string, or the URL to fetch when source: "url".
extractionsarrayList of extraction rules (see below).

Extraction rule fields

FieldTypeDefaultDescription
namestring"result"Output key for this extraction.
selectorstringRequired (rules with empty selector are skipped). One of: tag, .class, #id, tag.class, tag#id.
attributestring"text""text" (visible text content), "html" (rendered inner/outer HTML of the matched node), or any HTML attribute name (e.g. "href", "src").
multiplebooleanfalseIf true, returns an array of values for every matching element; if false, only the first match.

Output: One field per extraction rule, keyed by name:

  • multiple: false (default): the extracted string, or null if no element matched.
  • multiple: true: an array of extracted strings ([] if no elements matched).

Plus an always-present full_text field containing the trimmed text content of the entire parsed document.

Next