writing-handlebars
Write Handlebars template expressions for Celigo integrations -- dynamic values in mappings, HTTP bodies, SQL queries, URIs, and filters. Use when building any resource configuration that needs computed, conditional, or formatted field values.
By celigo · 1,050 installs
npx skills add celigo/ai --skill writing-handlebars
Source repository · Upstream listing
<! TIER:1
Writing Handlebars Expressions
Handlebars is Celigo's template language for embedding dynamic values into resource configurations. Any string field that the platform evaluates at runtime can contain Handlebars expressions.
Concerns when writing Handlebars:
Context where the expression runs determines what data is available and how output is treated
Braces double {{ }} vs triple {{{ }}} controls output escaping
Field access record. prefix (AFE 2.0) outside a few bare field contexts ([Key Syntax]( key syntax)), @root for job/settings/connection, bracket notation for special characters
Helpers 80 custom helpers for math, string manipulation, dates, encoding, regex, lookup caches, and more
Block helpers each , if , compare , with for iteration and conditional logic
Date/time moment.js format tokens with timezone support
Used across exports, imports, mappings, output filters, and APIs.
Copy field paths from the data, never from the request. Before writing an expression, read a record the step actually receives the import's sampleData , the source export's mockOutput or a recent run's records, or a failing record's retry data and take each path from it. The field the user names, or the destination field being filled, isn't evidence of the source's name ( phone may arrive as phoneNumber ), and a wrong path renders empty with no error ([step 2]( 2 know the data shape)). An import holds a record of its own only in sampleData ; when it has none, read the flow that uses it (the import's dependencies name it) and that flow's source export's mockOutput or a recent run's records. When you report the change, say which record each path came from.
Where Handlebars Are Used
Mapping extracts
In import mappings[].extract fields, Handlebars concatenates, transforms, or conditionally selects values. The context is the current record.
HTTP request templates
Export and import http blocks use Handlebars in relativeURI , body , headers , and postBody . In a relativeURI , double braces URL encode the value at runtime use them for record values in path segments and query parameters, and triple braces only for a value that is already URL encoded (a paging cursor or next link from a response). In a JSON body, double braces are JSON escaped at runtime, so write string values as "{{record.name}}" ; triple braces insert raw text, and a value holding a quote, backslash, newline or tab then breaks the body (422 invalid json ). Use triple braces for numbers, booleans and serialized JSON in a body, and in headers.
RDBMS SQL queries
SQL queries in rdbms.query use Handlebars with the mandatory record. prefix (a per page query iterates batch of records instead see [Template roots]( template roots)). Double braces render the value as a single quoted SQL string literal with embedded quotes escaped use them for strings and timestamps, without adding quotes of your own. Triple braces output the raw value, for numbers and identifiers. For full SQL patterns (MERGE, upsert, bulk operations, dialect differences), see [writing sql](../writing sql/SKILL.md).
Filter rule operands
Filters are rule arrays, not templates (see [configuring filters](../configuring filters/SKILL.md)). A Handlebars string can be one operand of a rule, but only when the whole string is a single {{…}} expression with record. prefixed paths: ["equals", "{{lowercase record.status}}", "open"] . It renders as text, so wrap it in ["number", …] before comparing it to a number.
File paths and names
Dynamic file names in FTP/S3 exports and imports use Handlebars for timestamps and record derived values. A file provider export or lookup also describes each record's source file as fileMeta , with exactly fileName , fileSize and lastModifiedTime (epoch milliseconds): {{fileMeta.fileName}} , {{dateFormat "YYYY MM DD" fileMeta.lastModifiedTime "x"}} . A run date stamp is {{timestamp "YYYYMMDD" "<time zone "}} : orders {{timestamp "YYYYMMDD" "America/New York"}}.csv renders orders 20261023.csv . There is no date helper ( {{date 'YYYYMMDD'}} is refused with Missing helper: "date" ), and a bare {{timestamp}} renders the full ISO time, colons included. The names are case sensitive ([configuring exports File Transfers](../configuring exports/SKILL.md file transfers)).
Delta tokens
Delta exports get two platform variables: {{lastExportDateTime}} , the last successful run (the window's lower bound), and {{currentExportDateTime}} , this run's upper bound. These are not record fields the platform injects them at runtime into the export's HTTP/query context only.
Quick Reference
Context Decision Matrix (AFE 2.0)
All contexts use record. prefix to access the current record's fields (AFE 2.0). Do NOT use bare field names, data.field , or data.0.field those are deprecated AFE 1.0 patterns. Exceptions: Mapper 1.0 (Salesforce / NetSuite DA), NetSuite saved search criteria and an HTTP import lookup's body use bare field names without record. prefix (see [Key Syntax]( key syntax)).
Where Syntax Data prefix Example
Mapping extract {{ }} (double) record. {{record.firstName}}
HTTP relative URI {{ }} (double) URL encoded at runtime; triple only for an already encoded value record. /customers?email={{record.email}}
HTTP body (JSON) {{ }} inside quotes for strings (JSON escaped); {{{ }}} for numbers, booleans and serialized JSON record. {"name": "{{record.name}}", "qty": {{{record.qty}}} }
HTTP import lookup body ( http.lookups[].body ) as in a JSON body none: the record's fields are at the top level {"email": "{{email}}"}
SQL query (RDBMS) {{ }} for strings and dates (quoted for you), {{{ }}} for numbers and identifiers record. WHERE email = {{record.email}} AND qty {{{record.qty}}}
Filter rule operand {{ }} (double), one expression filling the operand record. ["equals", "{{record.status}}", "open"]
Delta URI parameter {{ }} (double) (platform injected) ?updated since={{lastExportDateTime}}
An ID with no special characters renders the same with either brace style, which is why URI examples like /customers/{{{record.id}}} work too.
Additional context objects available via @root :
Object Description
record Current record being processed ( rows when it's a grouped record)
job Current job metadata
settings Settings by scope key: settings.integration , settings.flowGrouping , settings.flow , settings.connection , settings.iClient , and the step's own settings.export or settings.import
connection Connection object (for auth headers)
The root follows the record's shape. An object record is record ; a grouped record (an array of rows) is rows , and record. is undefined on it. batch of records replaces both only when an HTTP import sends several records per request ( http.batchSize above 1) or an RDBMS import runs per page . One to many doesn't batch by itself: each child renders as record . See [Template roots]( template roots).
Key Syntax
{{double braces}} context aware output at runtime. In a relativeURI the value is URL encoded, which a path segment or query parameter needs. In an RDBMS query it becomes a single quoted SQL string literal with embedded quotes doubled ( O'Neil renders 'O''Neil' ), so never add your own quotes around it. In a JSON body it is JSON escaped, so it goes inside the value's quotes ( "{{record.name}}" ).
{{{triple braces}}} raw output, no encoding or quoting. Use for serialized JSON, numbers and booleans in a body, headers, file paths, numbers and identifiers in SQL, and URI values that are already URL encoded (double braces would encode them a second time). A raw string inside a JSON body's quotes breaks the body on the first quote, backslash, newline or tab. Never hand quote a triple brace string in SQL ( '{{{record.name}}}' ) the first apostrophe in the data breaks the statement.
The preview isn't the runtime. The expression preview HTML escapes double braces ( & shows as & , ' as & x27; ); a running request URL encodes them in a URI, quotes them in SQL and JSON escapes them in a JSON body instead, with no HTML escaping. An & x27; in the preview is no reason to switch a body value to triple braces. It also renders a null value empty where a run writes null (gotcha 16).
Always use record. prefix (AFE 2.0) {{{record.fieldName}}} in all contexts, never bare {{{fieldName}}} or {{{data.fieldName}}} (AFE 1.0). Nested fields: {{{record.properties.email}}} .
Exceptions: Mapper 1.0, NetSuite search criteria and HTTP import lookup bodies Mapper 1.0 (Salesforce / NetSuite DA) uses bare field names without record. prefix. So do NetSuite saved search lookup criteria ( netsuite.restlet.criteria[].searchValue and criteria formulas), where the incoming record is the root: write {{poNumber}} . {{record.poNumber}} resolves in runs but fails the step preview with handlebars template parse error . So does the request template of an HTTP import's own lookup, in http.lookups[].body (a postBody there is never sent): the record's fields sit at the top level, so {{email}} renders the record's value and {{record.email}} renders empty ([configuring imports Gotchas](../configuring imports/SKILL.md gotchas)). A lookup export ( isLookup: true ) reads {{record.email}} as usual.
Related Skills
[configuring exports Quick Reference](../configuring exports/SKILL.md quick reference) export adaptor types, delta sync setup, output filters
[configuring imports Quick Reference](../configuring imports/SKILL.md quick reference) import adaptor types, operation modes, mapping systems
[writing mappings Quick Reference](../writing mappings/SKILL.md quick reference) Mapper 2.0 fields, lookups, conditional mappings
<! TIER:2
Syntax Fundamentals
Braces
Syntax Behavior When to use
{{ }} Context aware at runtime URL encoded in a relativeURI ; a quoted SQL string literal (embedded quotes doubled) in an RDBMS query; JSON escaped in a JSON body Record values in URIs; string and date values in SQL, with no quotes of your own; string values inside quotes in a JSON body
{{{ }}} Raw output, no encoding or quoting Serialized JSON, numbers and booleans in a body; headers, file paths, SQL numbers and identifiers, and URI values that are already URL encoded
\{{ }} Escaped tag a backslash before {{ prints that tag literally ( \{{name}} renders {{name}} ) Emitting literal Handlebars syntax. Raw blocks ( {{{{raw}}}} ) aren't supported they fail at runtime
Inside a GraphQL variables value stored as a JSON string, the body is two JSON levels deep and the brace choice shifts with it: a string value is "{{jsonEncode record.x}}" and an object or array {{jsonSerialize record.x}} , both in double braces; triple braces there fail with 422 invalid json ([configuring imports GraphQL imports](../configuring imports/SKILL.md graphql imports)).
Field access
Pattern Meaning
record.fieldName Standard field reference (AFE 2.0), outside the [Key Syntax]( key syntax) bare field contexts
record.nested.field Dot notation for nested objects
record.[Field With Spaces] Bracket notation for special characters in field names
record.items.[0].name Array index access. Mid path, record.items.0.name works too; a trailing .0 ( record.tags.0 ) and record.items[0] fail to parse
@root.fieldName Root context escape nested each scope
../fieldName Parent context one level up from current each
this Current iteration element
@index / @key Current array index / object key in each
@first / @last Boolean first/last