writing-mappings
Write field mappings and transforms in Celigo integrations. Covers Mapper 2.0 (imports), Transformation 2.0 (exports), lookups, response mapping, and Mapper 1.0 (NetSuite/Salesforce). Use when editing mappings[], transform{}, responseMapping, or lookup configurations.
By celigo · 1,046 installs
npx skills add celigo/ai --skill writing-mappings
Source repository · Upstream listing
<! TIER:1
Writing Mappings and Transforms
Mappings and transforms are the data reshaping layer in Celigo integrations. They control how fields from one system translate into fields for another. Mappings are used across flows, APIs, and tools.
Take every source path from a real record. Read what the step receives the import's sampleData , the source export's mockOutput or a recent run's records, or a failing record's retry data before writing an extract . A name the user mentions, or the destination field's name, isn't the source field's name, and a path that doesn't exist maps nothing, with no error ([Understand the source data shape]( 2 understand the source data shape)).
How to Run the Steps
This skill names each step by what it reads or changes; the exact calls depend on how you reach the account. Read the one reference that matches your tools both follow the workflows and sections below, and add how to save and check a change:
Celigo MCP tools ( list imports , upsert import , get schema , ...) [references/mcp.md](references/mcp.md)
The celigo CLI [references/cli.md](references/cli.md)
Reading resources, sample records, and metadata is read only; saving a mapping, transform, or response mapping writes to the account. The platform has no single field update for any of them, so every save replaces the whole import, export, or flow document start from the current document and send all of it back. To check a saved change, test run the flow: a test run never executes the flow's imports and doesn't advance its delta state, but it still calls the systems behind the steps it runs.
Mapping Systems
Four systems handle data reshaping:
Mapper 2.0 modern recursive field mapping on imports ( mappings[] array). Handles nested objects, arrays of any depth, lookups, conditionals, and date conversions. Default for new imports on all adaptor types except NetSuite DA ( NetSuiteDistributedImport ), Salesforce and WrapperImport , which support only Mapper 1.0 NetSuiteHTTPImport uses Mapper 2.0. The same rows also reshape the arriving record into an HTTP or NetSuite REST lookup's request ( mappings[] on an isLookup HTTPExport / NetSuiteHTTPExport ; no other export takes them), and build a Tool's output.mappings and an API response's mappings
Mapper 1.0 legacy flat mapping on Salesforce and WrapperImport imports ( mapping.fields[] / mapping.lists[] ) and NetSuite DA imports (the same arrays under netsuite da.mapping ). Body level and sublist fields in separate flat arrays. Also present on many older HTTP/FTP/RDBMS imports created before Mapper 2.0 existed
Transformation 2.0 rule based data reshaping on exports ( transform.expression.rulesTwoDotZero ). Uses the same Mapper 2.0 schema internally. Two modes: "create" (build new record from scratch) or "modify" (edit fields on existing record, unmapped fields pass through)
Response mapping simple extract/generate pairs that carry data from a lookup or import response back into the record ( responseMapping on flow pageProcessors[] ). Uses Transformation 1.0 syntax. It's how a step passes a value to later steps; an import's own mapping never reaches them (gotcha 11)
Lookups translate values in every system. They come in three kinds: static (an inline map ), cached ( lookupCacheId , read from a LookupCache resource), and live (imports only: a query to the destination for each record, such as an HTTP request, SOQL, SQL or a NetSuite search). The schemas call cached lookups "dynamic", and some adaptor schemas use the same word for live ones, so say which you mean. NetSuite DA lookups are always live NetSuite searches.
Direction decides the tool. Mappings translate data going out to a destination every import needs them, because the in flight record almost never matches what the destination expects. Transformations reshape data coming in on exports, listeners, and API/tool entry stages. Never use an upstream transformation to match a destination's shape; that's the destination import's mapping. Transformations earn their keep in two situations: multiple sources feeding one pipeline (reshape each new source to the canonical record shape the existing steps expect) and genuinely messy source data (flatten deep nesting once at entry instead of fighting it in every downstream mapping). With a single well shaped source, don't add a transform just because you can and skip identity transforms that rename nothing.
Quick Reference
Which Mapping System?
Context System Syntax Read schema
Import field mapping (HTTP, RDBMS, FTP, S3, etc.) Mapper 2.0 mappings[] [mappings.yml](references/schemas/mappings.yml)
NetSuite DA / Salesforce / Wrapper import Mapper 1.0 netsuite da.mapping.fields[] / mapping.fields[] see import schema ([netsuitedistributed.yml](../configuring imports/references/schemas/netsuitedistributed.yml); the Salesforce and Wrapper mapping is in [base.yml](../configuring imports/references/schemas/base.yml))
Export data reshaping Transformation 2.0 transform{} [transform.yml](references/schemas/transform.yml)
Response mapping (lookup/import carry back) Transformation 1.0 extract/generate pairs [response mapping.yml](references/schemas/response mapping.yml)
Value translation Lookups lookups[] [lookups.yml](references/schemas/lookups.yml)
Editing existing imports: Many existing imports use Mapper 1.0 even for HTTP and RDBMS adaptor types (pre dating Mapper 2.0). Always check whether an import uses mappings[] (2.0) or mapping.fields[] (1.0) before modifying never mix the two.
Schema Index
Schema Contents
[mappings.yml](references/schemas/mappings.yml) Mapper 2.0 field definitions (generate, extract, dataType, buildArrayHelper, conditionals)
[lookups.yml](references/schemas/lookups.yml) Static and cached lookup definitions (the schema calls cached lookups "dynamic")
[transform.yml](references/schemas/transform.yml) Transformation 2.0 envelope (mode, expression, script)
[response mapping.yml](references/schemas/response mapping.yml) Transformation 1.0 extract/generate pairs for response carry back
[netsuitedistributed.yml](../configuring imports/references/schemas/netsuitedistributed.yml) NetSuite Mapper 1.0 mapping + lookups
[base.yml](../configuring imports/references/schemas/base.yml) The import level Mapper 1.0 mapping that Salesforce and Wrapper imports use
[salesforce.yml](../configuring imports/references/schemas/salesforce.yml) Salesforce import settings and lookups
Related Skills
configuring imports ([Quick Reference](../configuring imports/SKILL.md quick reference)) import adaptor types, operation logic, hooks
configuring exports ([Quick Reference](../configuring exports/SKILL.md quick reference)) export adaptor types, delta syncs, webhooks
writing handlebars ([Quick Reference](../writing handlebars/SKILL.md quick reference)) Handlebars expressions used inside mapping extract fields
building flows ([How to Build a Flow](../building flows/SKILL.md how to build a flow)) wiring exports and imports into a flow pipeline
<! TIER:2
Mapper 2.0 Workflow
The mappings[] array is recursive a mapping can contain nested child mappings of the same structure to any depth. This is the core design principle.
1. Check the existing resource
Before modifying mappings, always retrieve the current state of the resource search the account for it by name if you don't have its id, then read the complete import. Check whether it uses Mapper 2.0 ( mappings[] ) or Mapper 1.0 ( mapping.fields[] ).
2. Understand the source data shape
Map against the record as it reaches this step: the export's records after its transform, output filter and preSavePage hook, plus every field an earlier step's response mapping merged in on a grouped source, an array of rows (step 5). Invoking the export shows its records with the transform applied, not what earlier lookups add; a test run's input to this step, or a failed record's retry data, shows the record exactly. For the full field list of the entity type, query the source system's metadata through its connection.
On a one to many import ( oneToMany: true ) the mapping sees one child at a time. With pathToMany , $.<field is the child's field and $. PARENT.<field the parent's PARENT is the parent without the child array, and the parent's own fields aren't at $ . With a blank pathToMany on a grouped record, each row is the record.
3. Understand the target data shape
Query the target system's metadata through its connection to discover required fields and types list the entity types it offers, then read the fields of the one you're writing to.
4. Choose the input context
The input context controls what JSONPath extracts start from. On an import it's the import's inputContext field; on a Transformation 2.0 transform, transform.expression.rulesTwoDotZero.inputContext :
record (the default) extract paths reference the record directly. $.user id accesses the user id field on the record
envelope record fields shift to $.record.user id , and the envelope also carries job , testMode and settings . Read settings as $.settings.<scope .<field , where the scope is integration , flowGrouping , flow , connection , iClient , or the step's own import or export (e.g., $.settings.connection.api username ); a path outside these resolves to nothing. A webhook listener's transform also gets the incoming request's headers and queryParams , and file provider steps get fileMeta
An import's envelope carries no request headers. In API and Tool runs, the integration, flow group and flow settings are empty.
When to use envelope: when a mapping needs a setting or job value as a JSON path $.settings.connection.fieldName instead of a {{settings.connection.fieldName}} Handlebars expression.
5. Map by data type
Every mapping needs three properties: generate (target field name), dataType (output type), and extract (how to get data from source).
Extract supports three patterns (distinguished by syntax):
JSON path starts with $ , the record as it arrives: $.customer.email on an object record; $[0].field (one row) or $[ ].field (every row) on a grouped record an array of rows from groupByFields , file key columns, or a NetSuite saved search (grouped unless skipGrouping: true ). On a grouped record $.field resolves nothing and the mapper still succeeds. Always references the top level root, even in nested mappings. A key that contains a dot needs bracket notation: $['P.O. Number'] (or $["P.O. Number"] , $.[P.O. Number] ); $.P.O. Number is read as a nested path, maps nothing, and the record still succeeds. Spaces, hyphens and need no brackets ( $.Ship To Name , $.Tracking ). Filter expressions fail the record (gotcha 13).
Handlebars contains {{ (e.g., {{record.firstName}} {{record.lastName}} ). For computed values
Hard coded plain string literal (e.g., "Active" , "USD" ). Neither starts with $ nor contains {{
Simple types ( string , number , boolean ) direct field to field mapping. Give each row the type the destination field expects rather than string by default: string sends a numeric source as text ( "24" ), which an API that checks types refuses. number converts it ( "24" becomes 24 , "007" becomes 7 ), leaves the field out when the source is "" , and fails the record with non parsable string ( <generate : String contains alpha characters ) when the value holds letters or a thousands separator ( "1,250.00" ). Dates are strings: use dataType: "string" with extractDateFormat / generateDateFormat (plus extractDateTimezone / generateDateTimezone when the zone changes). There is no date dataType;