Workflow data mapping
This module supplies safe data expressions and an Edit Fields (set_data) action. Production registration is owned by the workflow adoption task; see registration proposal.
| Syntax | Value |
|---|---|
{{ trigger.record.primaryEmail | lower }} | Lowercase CRM email; built-in keys match CRM conditions |
{{ trigger.event.data.body.id }} | Original trigger data, when captured |
{{ steps.s1.output.items[0].id }} | Earlier successful/planned step output; positions are one-based in expressions |
{{ steps.fetch.output }} | Same output via a safe, unambiguous action name |
{{ run.id }}, {{ run.workflowId }}, {{ run.version }} | Stored run identity and pinned version |
{{ now | date:"iso" }} | Context creation time in UTC |
{{ steps.s2.output.missing | default:"n/a" }} | Fallback for missing, null or empty string |
id={{ steps.s1.output.id }} | Mixed text; objects/arrays become JSON |
A whole-string expression retains JSON type. Objects and arrays are recursively resolved without mutating the input. Expressions support property identifiers and nonnegative integer array indices only. There are no calls, operators, computed properties, environment access or code evaluation. Prototype keys (__proto__, constructor, prototype) and inherited/accessor properties cannot be traversed.
The fixed filters are default (JSON scalar argument), json, string, number (finite conversion), lower, upper, trim, first, last, length, date:"iso", date:"date", and urlencode. Other filters/arguments are errors. Text filters require scalars; collection filters require arrays or strings (length also accepts objects). Date formatting rejects invalid dates. Pipe and brace characters inside quoted arguments are literal.
Limits: 4,000 characters per template, 16 path components including the root, 8 filters per expression, 256 KiB of serialized resolved output, and bounded recursive traversal. Missing paths throw expression_unresolved with the path and template. strict: false preserves an unresolved token for previews; malformed syntax still fails. Do not persist error messages containing user templates in run history; persist the error code only.
set_data accepts 1–50 { name, value } fields. Names are safe dot paths; values are JSON literals or templates. The output contains only these fields. Duplicate or parent/child conflicting paths fail, rather than silently replacing another field. A value can itself contain arrays and objects with expressions.
buildRunContext(tx, runId, { beforePosition, event }) loads a run and its earlier applied/planned steps in position order. Omit beforePosition only for a completed-run inspector; execution must supply its zero-based current position. It reads the tenant-scoped unarchived CRM row using the same table/identity predicate as engine conditions, with an explicit built-in field allowlist. Consent/protection fields, arbitrary custom fields, and credential values are not record snapshot fields. Safe names supplement positional aliases; reserved positional names are never overwritten.
Runs currently retain event identity, not a general event body. The builder reads a saved result.triggerEvent snapshot or matching workflow_waits.event, otherwise exposes identity metadata only. Execution supplies the original event and the proposed registration persists its redacted snapshot for subsequent inspection. Historical uncaptured event bodies cannot be reconstructed. The CRM record is a snapshot at context construction, not an event-time historical record.
describeMappablePaths(ctx) returns at most 500 { path, sample, type } entries, including array-index paths. Samples are bounded and redacted. Every listed path is valid expression syntax. The context route must retain tenant/workflow/run binding and CRM read/feature/API-key gates.
redactForStorage removes common credential keys recursively, case-insensitively and across hyphen/underscore spellings. Store only its result in result.input; arbitrary secrets under unrelated field names cannot be detected, so connector adopters must extend the central credential list for their exact placements. Do not resolve credential references into raw credentials before mapping/storage. Legacy actions, especially draft-email {{firstName|there}} merge syntax, remain outside the new kind allowlist.
References consulted: n8n expressions, Edit Fields, other-node output. This module uses the deliberately restricted grammar above rather than n8n's JavaScript expression runtime.