npx skills add ...
npx skills add celigo/ai --skill 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.
npx skills add celigo/ai --skill writing-mappings
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.
Four systems handle data reshaping:
mappings[] array). Handles nested objects, arrays of any depth, lookups, conditionals, and date conversions. Default for new imports on all adaptor types except NetSuite and Salesforcemapping.fields[] / mapping.lists[]). Body-level and sublist fields in separate flat arrays. Also present on many older HTTP/FTP/RDBMS imports created before Mapper 2.0 existedtransform.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)responseMapping on flow pageProcessors[]). Uses Transformation 1.0 syntaxLookups are shared across all systems -- static key-value maps or references to LookupCache resources for large/dynamic datasets. NetSuite imports use a distinct lookup system that queries live NetSuite records.
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.
| Context | System | Syntax | Read schema |
|---|---|---|---|
| Import field mapping (HTTP, RDBMS, FTP, S3, etc.) | Mapper 2.0 | mappings[] | mappings.yml |
| NetSuite/Salesforce import | Mapper 1.0 | mapping.fields[] | see import schema (netsuitedistributed.yml, salesforce.yml) |
| Export data reshaping | Transformation 2.0 | transform{} | transform.yml |
| Response mapping (lookup/import carry-back) | Transformation 1.0 | extract/generate pairs | response-mapping.yml |
| Value translation | Lookups | lookups[] | 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 | Contents |
|---|---|
| mappings.yml | Mapper 2.0 field definitions (generate, extract, dataType, buildArrayHelper, conditionals) |
| lookups.yml | Static and dynamic lookup definitions |
| transform.yml | Transformation 2.0 envelope (mode, expression, script) |
| response-mapping.yml | Transformation 1.0 extract/generate pairs for response carry-back |
| netsuitedistributed.yml | NetSuite Mapper 1.0 mapping + lookups |
| salesforce.yml | Salesforce Mapper 1.0 mapping |
extract fieldsThe mappings[] array is recursive -- a mapping can contain nested child mappings of the same structure to any depth. This is the core design principle.
Before modifying mappings, always retrieve the current state of the resource. Check whether it uses Mapper 2.0 (mappings[]) or Mapper 1.0 (mapping.fields[]).
Invoke the upstream export to see real records, or query the source system's metadata for the full field list.
Query metadata for the target system to discover required fields and types.
The input context controls what data is available to extract paths. Set via the "Input context" dropdown in the mapper UI:
record (default) -- extract paths reference the record directly. $.user_id accesses the user_id field on the recordenvelope -- extract paths reference a wrapper object containing record, job, settings (with connection, flow, integration, flowGrouping), iClient, and import. Record fields shift to $.record.user_id, but you gain access to metadata like $.settings.connection.api_username, $.job.type, $.settings.flow.fieldNameWhen to use envelope: APIs and tools where you need request context (headers, path params, query params, connection settings) directly in mappings without Handlebars. Also useful on transforms at the beginning of API/tool steps where the envelope exposes the full request context. Envelope context eliminates the need for {{settings.connection.fieldName}} Handlebars expressions -- use $.settings.connection.fieldName instead.
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):
$. (e.g., $.customer.email). Always references the top-level root, even in nested mappings{{ (e.g., {{record.firstName}} {{record.lastName}}). For computed values"Active", "USD"). Neither $. prefix nor {{Simple types (string, number, boolean, date) -- direct field-to-field mapping. For dates, set extractDateFormat/generateDateFormat for conversion.
Objects -- set dataType: "object", add child mappings in the mappings[] array. Never use dot notation in generate.
Arrays -- set dataType to an array type (stringarray, numberarray, booleanarray, objectarray, arrayarray) and configure buildArrayHelper[]. Three patterns for object arrays:
extract: "$.items[*]")buildArrayHelper entry creates one array element)[*] in the extract path collapse to single objects inside the mappings, so $.orders[*].items[*] becomes $.orders.items.fieldName in child extract paths. Parent context remains accessible (e.g., $.orders.id, $.customerName)Define lookups alongside mappings and reference them by name via lookupName on any mapping.
map object with key-value pairs. Best for small, fixed sets (country codes, status values)_lookupCacheId referencing a LookupCache resource, with optional extract JSON path to pull a specific field from the cached objectallowFailures: true + default to continue processing when lookup keys are missingControl when a mapping applies: record_created (only on insert), record_updated (only on update), or extract_not_empty (skip when source is null/empty).
All Mapper 2.0 field definitions: mappings.yml, lookups.yml
Transformation 2.0 reshapes data on exports before it enters the pipeline. It wraps Mapper 2.0 syntax in a transform envelope with a mode selector.
Before modifying transforms, retrieve the current export to inspect any existing transform configuration.
create -- build a completely new record. Only mapped fields appear in output. Use when the output structure differs significantly from the sourcemodify -- edit specific fields on the existing record. Unmapped fields pass through unchanged. Use for surgical adjustments (rename, add, remove a few fields)Same Mapper 2.0 syntax: generate, dataType, extract, nested mappings, buildArrayHelper, lookups. Everything described in the Mapper 2.0 section above applies here, including input context.
Input context is especially valuable on transforms for APIs and tools -- set it to envelope to access the full request context (headers, path params, query params, connection settings) directly via JSON path instead of Handlebars.
Set transform.type: "expression", expression.version: "2", then place mappings[] and lookups[] under expression.rulesTwoDotZero with the chosen mode.
Script alternative: Set transform.type: "script" with script._scriptId and script.function for programmatic transforms when expression rules aren't sufficient.
All Transformation 2.0 field definitions: transform.yml, mappings.yml, lookups.yml
NetSuite and Salesforce imports use the older flat mapping structure. Two arrays within the mapping object:
mapping.fields[] -- body-level field mappings. Each entry has extract (source path) or hardCodedValue (static value), generate (target field ID), and optional lookupName, dataType, internalId, immutable, discardIfEmpty, conditionalmapping.lists[] -- sublist/line-item mappings. Each entry has generate (sublist ID, e.g., "item"), jsonPath (source array path), and fields[] (column mappings with the same properties as body fields, plus isKey for matching existing lines)NetSuite lookups are different -- they query live NetSuite records using recordType, searchField, resultField, and operator. Not static maps. Defined in netsuite_da.lookups[], referenced by lookupName in field mappings. To discover valid field IDs for searchField and resultField, run celigo metadata fields <connectionId> <recordType> — the returned field IDs are the exact values to use.
Salesforce lookups follow the same Mapper 1.0 pattern but the lookup structure is simpler.
Sublist field discovery: For NetSuite mapping.lists[].generate, the sublist name (e.g., "item", "addressbook") comes from celigo metadata fields <connectionId> <recordType> — sublists appear as field groups. For Salesforce related lists, use celigo metadata fields <connectionId> <sObjectType> to discover relationship fields and child object names for distributed.relatedLists[].sObjectType.
NetSuite Mapper 1.0: see mapping and lookups in the configuring-imports skill's netsuitedistributed.yml
Salesforce Mapper 1.0: see mapping in the configuring-imports skill's salesforce.yml
Response mapping extracts fields from a lookup or import API response back into the original record. It lives on the flow's pageProcessors[] entry, not on the resource itself -- but it's planned when building the resource.
Two sections, both plain arrays (never wrap them in an object like "fields": {"type": [...]} -- the API accepts that shape but silently erases the step's mappings):
fields[] -- field-level extract/generate pairs using dot notationlists[] -- array mappings with generate (target array name) and fields[] (extract/generate pairs applied per array item)For lookup exports: the response envelope is {"statusCode": 200, "data": [...result records], "errors": []}, and every extract starts from data.
data[0].fieldName (or the equivalent data.0.fieldName).extract: "data", generate: "<arrayField>". The record gains an array of the result objects; downstream steps read it with normal JSON array paths (e.g. $.<arrayField>[*].fieldName in Mapper 2.0) or fan out over it with one-to-many (pathToMany: "<arrayField>").lists entry whose field extracts use data[*].fieldName. Produces one array item per result; the [*] marks the array to iterate:The [*] wildcard is only honored inside lists[].fields[].extract. In a top-level fields[].extract, data[*].fieldName (and bare data[*]) is silently ignored -- the field is never merged onto the record. Bare result-field names (fieldName without the data prefix) resolve to nothing in either section.
For imports: the response is available via _json. Use _json.fieldName (e.g., _json.id for a created record's ID, _json.output.1.content.0.text for AI model responses).
All response mapping field definitions: response-mapping.yml
Before submitting any mapping configuration, verify:
mappings[] for Mapper 2.0, mapping.fields[] for Mapper 1.0; never mixstatus: "Active" on every mapping entry -- API rejects entries without itgenerate -- use nested dataType: "object" with child mappings[] instead$. paths always reference the top-level input, even in nested mappingsbuildArrayHelper -- required for all *array dataTypeslookupName on a mapping has a corresponding entry in lookups[]extractDateFormat/generateDateFormat set when dataType: "date"responseMapping lives on flow.pageProcessors[], not on the import resource itselfbuildArrayHelper has both extract and mappings, child extract paths drop the [*] bracketsmappings[] means 2.0, mapping.fields[] means 1.0. Never mix.$. paths start from the top level (the record in record context, or the envelope in envelope context), not the current nesting level.buildArrayHelper has both extract and mappings, array brackets in the extract path are replaced with single objects in child mapping contexts. $.orders[*].items[*] becomes $.orders.items.fieldName inside the mappings.rulesTwoDotZero structure in responseMapping. It uses simple extract/generate pairs with dot notation.netsuite_da.lookups[] searches NetSuite records at runtime (recordType, searchField, resultField), unlike Mapper 2.0 static map lookups.generate must not use dot notation in Mapper 2.0. Build nested structures with dataType: "object" and child mappings[]. "generate": "customer.name" silently creates a field literally named "customer.name".generate indicates inner array in arrayarray. For nested array structures, inner array mappings have no generate field -- this is expected, not an error.set command handles this.[*] in response mapping only works inside lists. data[*].fieldName in a top-level fields[].extract is silently ignored -- nothing is merged. For multiple lookup results use extract: "data" (whole array) or a lists entry with data[*].fieldName extracts.| Error | Cause | Fix |
|---|---|---|
| "Mapping object must have status field present" | Missing status on a mapping entry | Add status: "Active" to every mapping object |
Import silently creates field named "customer.name" | Dot notation in generate | Use nested dataType: "object" with child mappings[] |
| Mapped fields missing in output | Using Mapper 2.0 syntax on a Mapper 1.0 import (or vice versa) | Check existing format: mappings[] = 2.0, mapping.fields[] = 1.0 |
Extract returns null in nested mapping | Extract path relative to nesting level | Extract paths always start from root ($.), not the current level |
| Array output is empty | Missing buildArrayHelper on array dataType | Add buildArrayHelper[] for all *array dataTypes |
| Lookup key not found / processing stops | allowFailures not set on lookup | Set allowFailures: true and provide a default value |
| Response mapping not applied | responseMapping placed on the import resource | Move to flow.pageProcessors[] entry for that import |
| Lookup response field empty / never merged | data[*].fieldName (or a bare field name) in a top-level fields[].extract | Use data[0].fieldName for one result; extract: "data" or a lists entry with data[*].fieldName extracts for many |
| Composite object paths return wrong data | [*] brackets still in child extract paths | Drop [*] -- arrays collapse to single objects inside buildArrayHelper mappings |
| Date values malformed in output | Missing date format configuration | Set extractDateFormat and generateDateFormat on date mappings |
| PUT overwrites entire resource | Partial JSON sent without GET first | Always GET full resource, modify mapping section, PUT complete object |
celigo metadata types <targetConnectionId>
celigo metadata fields <targetConnectionId> <entityType>celigo exports get <exportId>
celigo account search <keyword>{
"fields": [],
"lists": [
{
"generate": "matchedOrders",
"fields": [
{ "extract": "data[*].id", "generate": "orderId" },
{ "extract": "data[*].total", "generate": "amount" }
]
}
]
}# Discover resources
celigo account search <keyword> # Find imports/exports by name
celigo imports get <importId> # Inspect existing import (check mappings vs mapping)
celigo exports get <exportId> # Inspect existing export (check transform)
# Understand data shapes
celigo exports invoke <exportId> # See real source records
celigo metadata types <connectionId> # List entity types
celigo metadata fields <connectionId> <type> # List fields for an entity
# Update mappings (GET -> modify -> PUT)
celigo imports set <importId> <key>=<value> [<key2>=<value2> ...] # Field-level edit (dot/bracket paths, JSON values)
celigo exports set <exportId> <key>=<value> [<key2>=<value2> ...]
celigo imports update <importId> < import.json # Full PUT replace from stdin JSON
celigo exports update <exportId> < export.json