<!-- 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 , the source export's or a recent run's records, or a failing record's retry data -- before writing an . 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 record root. An object record is addressed as\n '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n - Used for precisely targeting data in structured JSON objects\n - Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n '$[*].Tax' (every row of a grouped record)\n - Wildcards like [*] extract multiple values/objects\n - A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n need no brackets (`$.Ship To Name`, `$.Tracking #`)\n - Filter expressions such as `$.items[?(@.active)]` fail the record with\n \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n ```json\n \"extract\": \"$.customer.addresses[*]\" // Extracts all addresses\n ```\n\n **2. Handlebars Template Syntax**\n - Contains '{{' and '}}' pattern\n - Evaluated by the AFE 2.0 handlebars template engine\n - Can include logic, formatting, and computation\n - Access input record fields with {{record.fieldName}} notation; a grouped\n (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n - Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n - Valid on scalars and on `object` rows, where the rendered output must\n parse into an object. On an array row the template goes in a\n `buildArrayHelper` entry instead: on an `objectarray` row's own\n `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n must be present for object array datatype.), and on a primitive-array\n row's own `extract` it yields nothing (the field is left out) without an\n error\n\n ```json\n \"extract\": \"{{record.price}} {{record.currency}}\" // Combines two fields\n ```\n\n **3. Hard-Coded Value (literal string)**\n - Does NOT start with '

<!-- 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 , the source export's or a recent run's records, or a failing record's retry data -- before writing an . 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…

\n - Does NOT contain handlebars '{{' syntax\n - System treats it as a literal string value\n - VERY COMMON for setting static/constant values\n - Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n ```json\n \"extract\": \"primary\" // Sets field value to the literal string \"primary\"\n \"extract\": \"true\" // Sets field value to the literal string \"true\"\n \"extract\": \"N/A\" // Sets field value to the literal string \"N/A\"\n ```\n\n This third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\n AI agents should use this pattern whenever a field needs a static value that doesn't come from\n the input record or require computation.\n\n **Important implementation details**\n\n - JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n - The system maintains this context even in deeply nested mappings\n - For object mappings without child mappings, extract should return a complete object\n - An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n both: a row with both fails with 422 (`\"extract\" should not be present if\n \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n `extract` and `mappings` work together.\n - The root is the record as it arrives: an object record roots at `$.field`; a\n grouped record (an array of rows — grouped exports, file key columns, NetSuite\n grouped saved searches) roots at the array, so its rows are addressed as\n `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n record's shape resolves to nothing without an error.\n\n For most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\n For hard-coded values, simply use the literal string as the extract value.\n x-celigo-ai-guidance:\n - |-\n ## GROUPED (ARRAY) RECORDS\n Decide the JSONPath root from the record's shape before writing\n any extract. When the record is an array of rows, `$.field`,\n `$.properties.field` and `$.rows[*].field` all resolve to\n nothing — the processor still reports success and the request\n body ships as `{}`. Address rows as `$[0].field` (that row's\n value, typed) or `$[*].field` (every row: joined with commas\n into a `string` destination; rejected with\n `incompatible_destination_conversion` into a `number`\n destination). Column labels with spaces work as\n `$[*][Order Number]`, `$[*].[Order Number]`, or\n `$[*]['Order Number']`. For one destination element per row\n the two array kinds take different helper forms: a primitive\n array (`stringarray` / `numberarray`) uses `buildArrayHelper:\n [{\"extract\": \"$[*].field\"}]`; an `objectarray` uses\n `buildArrayHelper: [{\"extract\": \"$[*]\", \"mappings\": [...]}]`\n with the children addressing the row relatively (`$.field`,\n `{{rows.field}}`). An `extract` placed directly on the\n array-typed field yields nothing. Handlebars\n extracts bind the array record as `rows` (`{{rows.0.field}}`),\n an object record as `record`. The reverse holds too: `$[*]` on\n an object record iterates the object's values, not rows.\n - |-\n ## SIZE LIMIT\n The platform's import save validator caps this field at **1024\n characters**. Saves with a longer `extract` are rejected\n with `[size_limit_exceeded] The maximum size of the field:\n mappings.\u003cindex>.extract in the Import document should be\n 1024 characters`. This typically only matters for\n Handlebars templates that build complex strings inline -- if\n a template is approaching the cap, prefer one of:\n\n - **Decompose into nested mappings** (set `dataType:\n \"object\"` and use child `mappings` -- each child gets\n its own 1024-char budget).\n - **Move the logic to a `preMap` hook** so the heavy\n transformation runs in script and the mapping just reads\n `record.\u003ccomputed>`.\n - **Use a lookup** (`lookupName` + a small `lookups` entry)\n for value translation that would otherwise need a long\n `{{#if ...}}{{else if ...}}` chain.\n examples: [\"$.fullName\", \"{{record.fullName}}\", \"primary\", \"$.lineItems[*].unitPrice\"]\n extractDateFormat:\n type: string\n description: |\n Specifies the format pattern of the input date string to ensure proper parsing.\n\n Used on string-typed mappings whose `extract` yields a date. Uses\n Moment.js-compatible formatting tokens to describe how the incoming date\n string is structured.\n examples: [\"YYYY-MM-DD\", \"MM/DD/YYYY\", \"DD-MMM-YYYY HH:mm:ss\"]\n extractDateTimezone:\n type: string\n description: |\n Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\n Used on string-typed mappings whose `extract` yields a date; tells the system\n how to interpret timestamp values from the input system.\n examples: [\"UTC\", \"America/New_York\", \"Europe/London\"]\n generateDateFormat:\n type: string\n description: |\n Specifies the output format pattern when generating a date string or converting\n from a Date type to String type.\n\n Uses Moment.js-compatible formatting tokens to define the structure of the resulting\n date string.\n examples: [\"YYYY-MM-DD\", \"MM/DD/YYYY\", \"DD-MMM-YYYY HH:mm:ss\"]\n generateDateTimezone:\n type: string\n description: |\n Specifies the timezone to apply when generating or converting timestamp values\n using Olson/IANA timezone identifiers.\n\n Controls timezone conversion when producing date output.\n examples: [\"UTC\", \"America/New_York\", \"Europe/London\"]\n default:\n type: [string, 'null']\n description: |\n Specifies a fallback value to use when extract returns empty/null or when conditional\n logic fails and no other mapping supplies a value.\n\n Explicit JSON `null` is itself a valid fallback: the destination field is written as\n JSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\n action). Omitting the key entirely means no fallback — the field is left out of the\n output when the extract is missing or an empty string (the UI's \"Do nothing\" action),\n while a null source is still written as null unless `conditional.when` is\n `extract_not_empty`. Failing the record instead is `conditional.when:\n fail_record_when_empty`, which excludes a default.\n x-celigo-ai-guidance:\n - |-\n `\"default\": null` and `\"default\": \"\"` are distinct saved values, not\n absent keys: null renders the destination field as JSON null and the\n empty string renders it as `\"\"` when the extract yields nothing.\n Preserve them verbatim when editing or migrating a mapping — dropping\n the key changes runtime behavior from \"use null/empty string as\n default value\" to \"do nothing\" (a missing or `\"\"` source is left out\n of the output, while a null source is written as null unless\n `conditional.when` is `extract_not_empty`).\n - |-\n The four \"Action to take if source field has no value\" choices map to:\n do nothing → no `default` key (add `conditional.when:\n extract_not_empty` to leave a null source out too; without it a null\n source is written as null); use null → `\"default\": null`; use empty string or\n a custom value → `\"default\": \"\u003cvalue>\"`; cause the record to fail →\n `conditional.when: fail_record_when_empty` with no `default` key.\n examples: [\"N/A\", \"0\", \"false\"]\n lookupName:\n type: string\n description: |\n **Purpose**\n References a lookup table for transforming values during the mapping process.\n\n **Usage**\n\n The lookupName refers to a named lookup defined in the lookups array of the same resource.\n On an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\n is dropped on save. On an import, a name that matches no lookup fails every record\n with \"Unable to find lookup in mapping configs - \u003cname>\", and a key the lookup\n doesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\n the field left out, lets the record succeed with the lookup's `default`, or without\n the mapped field when there is none.\n\n ```json\n {\n \"generate\": \"countryName\",\n \"dataType\": \"string\",\n \"extract\": \"$.countryCode\",\n \"lookupName\": \"countryCodeToName\"\n }\n ```\n\n During processing, the system:\n 1. Extracts the value from the input record (e.g., \"US\")\n 2. Finds the lookup table with the specified name\n 3. Uses the extracted value as a key in the lookup\n 4. Returns the corresponding value (e.g., \"United States\")\n\n **Benefits**\n\n - **Standardization**: Ensures consistent value translation across mappings\n - **Centralization**: Define translations once and reference them in multiple places\n - **Maintainability**: Update all mappings by changing the lookup definition\n - **Readability**: Makes mappings more descriptive and self-documenting\n\n The specific lookup capabilities depend on the context where mappings are used.\n examples: [\"countryCodeToName\", \"statusMapping\", \"departmentCodes\"]\n hardCodedValue:\n type: string\n description: |\n Constant written to the destination instead of an extracted value. On a primitive\n array (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\n verbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\n On an `object` it must be a JSON object string and on an `objectarray` a JSON array\n string (an object string becomes one element); the save parses it and refuses non-JSON\n or the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n examples: [\"USD\", \"A,B\", \"[{\\\"code\\\": \\\"A\\\"}, {\\\"code\\\": \\\"B\\\"}]\"]\n description:\n type: string\n description: |\n Optional free-text annotation that appears in the Mapper sidebar to provide context about\n the mapping's purpose for collaboration and documentation.\n\n Has no functional impact on the mapping behavior.\n examples: [\"Maps customer name\", \"Converts price to target currency\", \"Formats date for target system\"]\n sourceDataType:\n type: string\n description: |\n Declares the JSON type of the value produced by `extract`, before any\n conversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\n only — parent mappings (with child `mappings` or `buildArrayHelper`)\n have no extracted value of their own; the children carry their own\n `sourceDataType`.\n\n For date fields use `string` (JSON represents dates as strings); the\n parsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n `extractDateTimezone` / `generateDateTimezone`.\n enum:\n - string\n - number\n - boolean\n - object\n - stringarray\n - numberarray\n - booleanarray\n - objectarray\n - arrayarray\n x-enumDescriptions:\n string: Source value is a text string.\n number: Source value is numeric.\n boolean: Source value is true or false.\n object: Source value is a JSON object.\n stringarray: Source value is an array of strings.\n numberarray: Source value is an array of numbers.\n booleanarray: Source value is an array of booleans.\n objectarray: Source value is an array of objects.\n arrayarray: Source value is an array of arrays.\n examples: [\"string\", \"number\", \"boolean\", \"object\"]\n mappings:\n type: array\n description: |\n **Purpose**\n Enables recursive definition of nested object structures through child mapping objects.\n\n **Guidance**\n This is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n **When to Use**\n - REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n - REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n - NEVER used with simple types (string, number, boolean)\n\n **Behavior**\n - Each mapping in this array becomes a property of the parent object\n - The full Mappings schema is repeated recursively at each level\n - Can be nested to any depth for complex hierarchical structures\n\n **Context Handling**\n - Each level of nesting changes the mapping CONTEXT for 'generate'\n - The extraction CONTEXT always remains the original input record\n - This means child mappings can pull data from anywhere in the input record\n\n **Common Patterns**\n\n **Nested Objects**\n ```json\n {\n \"generate\": \"customer\",\n \"dataType\": \"object\",\n \"mappings\": [\n {\n \"generate\": \"contact\",\n \"dataType\": \"object\",\n \"mappings\": [\n {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n ]\n }\n ]\n }\n ```\n\n **Multiple Fields in Object**\n ```json\n {\n \"generate\": \"address\",\n \"dataType\": \"object\",\n \"mappings\": [\n {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n ]\n }\n ```\n\n IMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\n or if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\n valid contexts for the mappings array.\n items:\n $ref: '#/Mappings/items'\n examples: [[{\"generate\": \"firstName\", \"extract\": \"$.name.first\", \"dataType\": \"string\", \"status\": \"Active\"}, {\"generate\": \"lastName\", \"extract\": \"$.name.last\", \"dataType\": \"string\", \"status\": \"Active\"}]]\n buildArrayHelper:\n type: array\n x-celigo-ai-guidance:\n - |-\n Entry-level `extract` accepts an iterating array path\n (`$.items[*]`), `\"$\"` (the record root), or omission — a\n constructed entry (child `mappings` with the extract omitted or\n `\"$\"`; the two spellings are equivalent) contributes exactly one\n element, and entries append in order. On a grouped (array)\n record the record itself is the array to iterate: `\"$[*]\"`\n yields one `objectarray` element per row (children address the\n row as `$.field`), and `\"$[*].field\"` yields one primitive-array\n value per row. Indexed or comma-joined\n entry extracts (`\"$[0]\"`, `\"$[0],$[0]\"`) are not valid: the\n processor renders the entire destination array as null with no\n validation error. To emit a fixed number of static elements, add\n that many sibling constructed entries — never index the extract.\n - |-\n An entry's `extract` can be a Handlebars template. On an\n `objectarray` it must render a JSON object (one element) or a JSON\n array (one element per item). On a primitive array each entry\n renders one element, so a serialized array becomes one string\n element. A bare literal entry extract is refused with 422 (\"extract\n only supports json path or handlebars expression\"); set a constant\n with the row's `hardCodedValue` instead.\n description: |\n **Purpose**\n Configures how to construct arrays in the output record, handling various array types and inputs.\n\n **Guidance**\n This is the mechanism for building array data types:\n\n **When to Use**\n - REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n - Each entry in this array contributes elements to the output array\n - Multiple entries allow combining data from different input arrays\n\n **Array Type Handling**\n\n **For Simple Arrays (stringarray, numberarray, booleanarray)**\n - Only the 'extract' field is used to pull values\n - JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n on a grouped (array) record the record itself is the array, so the\n path roots at it ($[*].name — one value per row)\n - Each result is converted to the appropriate primitive type\n ```json\n {\n \"generate\": \"productNames\",\n \"dataType\": \"stringarray\",\n \"buildArrayHelper\": [\n {\"extract\": \"$.products[*].name\"}\n ]\n }\n ```\n\n **For Object Arrays (objectarray) - three patterns**\n\n 1. Extract Only (existing objects):\n ```json\n {\n \"generate\": \"contacts\",\n \"dataType\": \"objectarray\",\n \"buildArrayHelper\": [\n {\"extract\": \"$.account.primaryContacts[*]\"}, // Pull primary contact objects\n {\"extract\": \"$.account.secondaryContacts[*]\"}, // Pull secondary contact objects\n {\"extract\": \"$.vendor.contactPersons[*]\"}, // Pull vendor contact objects\n {\"extract\": \"$.subsidiaries[*].mainContact\"} // Pull main contact from each subsidiary\n ]\n }\n ```\n\n 2. Mappings Only (constructed object):\n ```json\n {\n \"generate\": \"contactInfo\",\n \"dataType\": \"objectarray\",\n \"buildArrayHelper\": [\n {\n \"mappings\": [ // Creates one object in the array\n {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n ]\n },\n {\n \"mappings\": [ // Creates another object in the array\n {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n ]\n }\n ]\n }\n ```\n Each constructed entry contributes exactly one element, in entry\n order; writing the entry with `\"extract\": \"$\"` is equivalent to\n omitting the extract (both anchor the element at the record root).\n A fixed number of static elements is built from that many sibling\n constructed entries.\n\n 3. Extract AND Mappings (transform input arrays):\n ```json\n {\n \"generate\": \"lineItems\",\n \"dataType\": \"objectarray\",\n \"buildArrayHelper\": [\n {\n \"extract\": \"$.order.items[*]\", // For each item in the array\n \"mappings\": [ // Transform to this structure using the composite object\n {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"}, // Notice: items is singular\n {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"}, // Notice: items is singular\n {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"}, // Access parent data\n {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"} // Access root data\n ]\n }\n ]\n }\n ```\n\n **Arrays of Arrays**\n - The mapper has no working type for them: `arrayarray` saves, but every record\n fails with `Destination datatype is a required field.` Build an array of arrays\n in a script hook instead.\n\n **Important details**\n\n - When both extract and mappings are provided, the system creates special composite objects\n that maintain hierarchical context during processing\n - This enables accessing both the current array element AND its parent context\n - An ITERATING entry's extract MUST be a JSON path that iterates an array:\n '$.items[*]' when the array is a field of an object record, or '$[*]'\n (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n grouped array of rows. A CONSTRUCTED entry (child mappings building one\n element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n entire destination array as null without raising a validation error\n - Each array helper entry acts independently, potentially adding multiple elements\n\n The buildArrayHelper is the most complex part of the mappings system - always analyze the\n dataType first to understand which pattern is appropriate.\n items:\n type: object\n properties:\n extract:\n type: string\n description: |\n JSON path expression that identifies the input array or values to extract.\n\n For objectarray with mappings, this defines which input objects to iterate through.\n The JSON path must return either a single object or an array of objects.\n\n For a CONSTRUCTED entry (child mappings building exactly one element from\n record-root fields and constants), omit this field or set it to exactly \"$\" —\n the two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n '$[0],$[0]') are not valid at the entry level: the processor renders the\n entire destination array as null without raising a validation error.\n\n The system creates special composite objects during processing to maintain\n hierarchical relationships, allowing easy access to both the current array item\n and its parent contexts.\n\n An iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\n element per item, with no error. Child extracts drop the `[*]` but keep the\n array's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\n element-relative `$.sku` reads the record root instead of the element.\n examples: [\"$.items[*]\", \"$.order.lineItems[*]\", \"$.parents[*].children[*]\", \"$\"]\n name:\n type: string\n maxLength: 200\n description: |\n Display label for this source object in the mapper, so the sources of\n an object array can be told apart instead of all showing as ` writing-mappings — Skillopedia .\n Has no effect at runtime; stored only when non-empty.\n x-celigo-ai-guidance:\n - |-\n Carry an existing `name` through every edit — it is the user's label\n for the tab, and an entry sent without it loses the label. Duplicate\n names across entries are allowed. Set one only when the user names\n the source object; never invent labels.\n examples: [\"Billing address\", \"Shipping address\"]\n sourceDataType:\n type: string\n description: |\n Declares the JSON type of the input array being iterated, to ensure\n proper type handling during array construction. Same enum as `dataType`.\n enum:\n - string\n - number\n - boolean\n - object\n - stringarray\n - numberarray\n - booleanarray\n - objectarray\n - arrayarray\n x-enumDescriptions:\n string: Input array contains string elements.\n number: Input array contains numeric elements.\n boolean: Input array contains boolean elements.\n object: Input array contains object elements.\n stringarray: Input array contains arrays of strings.\n numberarray: Input array contains arrays of numbers.\n booleanarray: Input array contains arrays of booleans.\n objectarray: Input array contains arrays of objects.\n arrayarray: Input array contains arrays of arrays.\n examples: [\"string\", \"number\", \"boolean\", \"object\"]\n default:\n type: [string, 'null']\n description: |\n Specifies a fallback value when the extracted array element is empty or\n not found in the input data.\n\n Explicit JSON `null` is itself a valid fallback (the element is written as\n JSON null); omitting the key means no fallback is applied.\n examples: [\"N/A\", \"0\", \"false\"]\n conditional:\n type: object\n description: |\n Defines conditional rules for including each array element in the result.\n x-celigo-ai-guidance:\n - |-\n Judged against this entry's own `extract` (the array source). The\n design scopes the UI's tab-level \"Action to take if source field has\n no value\" to copy-as-is tabs (no child `mappings`); the API accepts\n `fail_record_when_empty` on a mapped entry as well, and the mapper\n processor honors it there — the record fails when that array is\n missing or empty (`errors[]` entry `code: extract_empty`, `fatal:\n true`, message `\u003cgenerate>: Source field has no value`; probed\n 2026-10-09). Say so when writing it on a mapped entry: the user may\n not find the setting in the mapper's tab. To fail on an empty field\n inside each element, set the action on that child mapping instead.\n properties:\n when:\n type: string\n description: |\n Condition judged on the entry's extract — skip the elements the\n extract does not return, or fail the whole record when the source\n array is missing or empty.\n enum:\n - extract_not_empty\n - fail_record_when_empty\n x-enumDescriptions:\n extract_not_empty: Include an array element only when the extract returns a non-empty value for it\n fail_record_when_empty: Fail the record when the entry's source array is missing or empty\n examples: [\"extract_not_empty\"]\n mappings:\n type: array\n description: |\n Contains recursive mapping definitions for complex array element transformations.\n\n **Composite object mechanism**\n\n When both 'extract' and 'mappings' are used together, the system implements a sophisticated\n \"composite object\" approach that is crucial for AI agents to understand:\n\n 1. The system starts with the complete input record\n\n 2. For each array element matched by the extract path, it creates a modified version of\n the input record where:\n - Array paths in the extract JSON path are REPLACED with single objects\n - Each array ([]) in the path is converted to a single object ({})\n - This preserves the hierarchical relationship between nested arrays\n\n **Example**\n\n Given an input record:\n ```json\n {\n \"customer\": {\n \"name\": \"John Doe\",\n \"orders\": [\n {\n \"id\": \"O-001\",\n \"items\": [\n {\"sku\": \"ABC\", \"qty\": 2},\n {\"sku\": \"XYZ\", \"qty\": 1}\n ]\n },\n {\n \"id\": \"O-002\",\n \"items\": [\n {\"sku\": \"DEF\", \"qty\": 3}\n ]\n }\n ]\n }\n }\n ```\n\n With extract path: `$.customer.orders[*].items[*]`\n\n For each item, the system creates a composite object like:\n ```json\n {\n \"customer\": {\n \"name\": \"John Doe\",\n \"orders\": { // Note: Array replaced with single object\n \"id\": \"O-001\",\n \"items\": { // Note: Array replaced with single object\n \"sku\": \"ABC\",\n \"qty\": 2\n }\n }\n }\n }\n ```\n\n Then in your mappings, you can access:\n - The current item: `$.customer.orders.items.sku`\n - The parent order: `$.customer.orders.id`\n - Top-level data: `$.customer.name`\n\n This approach allows for precise mapping from deeply nested structures while maintaining\n access to all contextual parent data, without requiring complex array index management.\n\n **Implementation guidance**\n\n When implementing the composite object mechanism:\n\n 1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n 2. For each array in the path, understand that it will be replaced with a single object\n 3. In the mappings, use paths that reference these arrays as if they were objects\n 4. Remember that every mapping still has access to the full input record context\n 5. This mechanism is especially powerful when mapping hierarchical data like:\n - Order → Line Items → Taxes/Discounts\n - Customer → Addresses → Address Lines\n - Invoice → Line Items → Serial Numbers\n\n The extract path effectively tells the system \"iterate through these arrays\",\n while the composite object mechanism ensures you can still access both the\n current array item AND its parent context during mapping.\n items:\n $ref: '#/Mappings/items'\n examples: [[{\"generate\": \"itemName\", \"extract\": \"$.name\", \"dataType\": \"string\", \"status\": \"Active\"}, {\"generate\": \"itemPrice\", \"extract\": \"$.price\", \"dataType\": \"number\", \"status\": \"Active\"}]]\n # The save rejects the fail-record action paired with a default on an\n # entry ([unsupported_mapping]); the entry schema has no lookupName or\n # hardCodedValue, so this is the only pair at this level.\n if:\n properties:\n conditional:\n properties:\n when:\n const: fail_record_when_empty\n required: [when]\n required: [conditional]\n then:\n not:\n required: [default]\n status:\n type: string\n description: |\n **Purpose**\n Controls whether the mapping is applied. Send it on every mapping entry.\n\n **Guidance**\n Only `Active` rows run. `Draft` and `Inactive` rows keep their\n configuration, are saved with minimal validation, and are skipped at\n runtime and in preview; a non-Active parent skips its whole subtree\n whatever its children say. Imports (on save and in the mapper processor)\n and API response mappings reject a row missing this field (validation\n error: \"Mapping object must have status field present.\"); a Tool's\n output mapping saves and runs one, which is why `required` doesn't\n list it.\n x-celigo-ai-guidance:\n - |-\n Emit `\"Active\"` for mappings that should run; `\"Draft\"` is also\n accepted and saves the mapping while bypassing the Active-only\n field validations. The value is case-sensitive. The API rejects:\n - Missing field: `[status_field_absent] Mapping object must have status field present.`\n - Wrong case (e.g. `\"active\"`): `[invalid_status_enums] active is not a valid enum for status field.`\n - |-\n `\"Inactive\"` is the user's \"Set inactive\" on a row: use it only when\n asked to disable or park a row, never to mean incomplete (that is\n `\"Draft\"`). Setting a parent `Inactive` means no `Active` row may\n remain under it — the save rejects an `Active` row under an\n `Inactive` parent (`[inactive_parent_active_child]` on `mappings`,\n no message text) and never cascades or promotes on its own; `Draft`\n descendants are kept as they are, and a `Draft` parent does not gate\n its children. The UI's Set inactive on a parent marks the parent and\n every child `Inactive`. Re-enabling a child\n means re-enabling its ancestors in the same payload. Edits that keep\n existing rows must carry their stored status forward — rewriting a\n `Draft` or `Inactive` row to `Active` turns a row the user parked\n back on.\n enum:\n - Active\n - Draft\n - Inactive\n x-enumDescriptions:\n Active: The mapping is enabled and applied during processing\n Draft: In-progress mapping; saved without the Active-only field validations and skipped at runtime\n Inactive: Disabled by the user; configuration kept, skipped at runtime and in preview\n examples: [\"Active\"]\n conditional:\n type: object\n description: |\n **Purpose**\n Defines conditional processing rules for the entire mapping.\n\n **Guidance**\n These conditions determine whether the mapping is applied based on record\n state or field content:\n\n **When to Use**\n - When a mapping should only be applied in specific circumstances\n - To implement conditional logic without using complex handlebars expressions\n - For creating mappings that only run during create or update operations\n\n **Available Conditions**\n\n - `record_created`: Apply only when creating a new record\n Useful for setting initial values that should not be overwritten during updates\n\n - `record_updated`: Apply only when updating an existing record\n Useful for transformation logic that should only run during updates\n\n - `extract_not_empty`: Apply only when the extract field returns a value\n Useful for conditional mapping based on input data availability\n\n - `fail_record_when_empty`: Fail the record when the extract field returns\n no value (the UI's \"Cause record to fail\" action when the source field\n has no value)\n\n **Example**\n ```json\n {\n \"generate\": \"statusMessage\",\n \"dataType\": \"string\",\n \"status\": \"Active\",\n \"extract\": \"$.status.message\",\n \"conditional\": {\n \"when\": \"extract_not_empty\" // Only map when status.message exists\n }\n }\n ```\n x-celigo-ai-guidance:\n - |-\n `when` holds one value, and two different settings share it: the\n composite-import operation gate (`record_created` / `record_updated`,\n the UI's \"Only perform mapping when\") and the empty-source action\n (`extract_not_empty` / `fail_record_when_empty`, the UI's \"Action to\n take if source field has no value\"). A row cannot carry both — writing\n one replaces the other and the save succeeds, so a composite import\n row that needs both has to choose. Never author `fail_record_when_empty`\n on a row that already carries `record_created` or `record_updated`.\n properties:\n when:\n type: string\n description: |\n Condition that gates this mapping — either an operation gate on a\n composite (create-or-update) import or the action to take when the\n extract returns no value. Empty means null, undefined or an empty\n string; `0`, `false` and whitespace are values.\n x-celigo-ai-guidance:\n - |-\n `fail_record_when_empty` is the fourth \"Action to take if source field\n has no value\" and is mutually exclusive with the other three: send it\n with no `default` key. A `default`, a `lookupName` or a `hardCodedValue`\n on the same row — or a `default` on a `buildArrayHelper` entry — is\n rejected at save with `[unsupported_mapping] \"\u003ckey>\" and conditional.when\n \"fail_record_when_empty\" cannot be present in mapping simultaneously.`\n (the lookup variant adds `Use lookup \"allowFailures\" instead.`);\n lookups fail through `allowFailures: false`, and a hard-coded row\n always has a value. At runtime an empty extract fails the record with\n an `errors[]` entry `{code: \"extract_empty\", fatal: true, source:\n \"mapping\", message: \"\u003cgenerate>: Source field has no value\"}`\n (\"Input field has no value\" in a Transformation 2.0); `0` and `false`\n are values and map through.\n enum:\n - record_created\n - record_updated\n - extract_not_empty\n - fail_record_when_empty\n x-enumDescriptions:\n record_created: Apply this mapping only when creating a new record\n record_updated: Apply this mapping only when updating an existing record\n extract_not_empty: Apply this mapping only when the extract field returns a non-empty value\n fail_record_when_empty: Fail the record when the extract field returns no value\n examples: [\"record_created\", \"record_updated\", \"extract_not_empty\", \"fail_record_when_empty\"]\n required:\n - dataType\n if:\n properties:\n conditional:\n properties:\n when:\n const: fail_record_when_empty\n required: [when]\n required: [conditional]\n then:\n not:\n anyOf:\n - required: [default]\n - required: [lookupName]\n - required: [hardCodedValue]\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":52991,"content_sha256":"52bc801b81ac5f934dda6ea0e194361e1aad6592840f911cf948db2fbb4ca41d"},{"filename":"references/schemas/response-mapping.yml","content":"ResponseMapping:\n type: object\n description: |\n Merges fields from a step's response back onto the in-flight record so\n downstream steps can read them. Applies to both lookup steps\n (`type: \"export\"`) and import steps (`type: \"import\"`); without it, the\n step's response is discarded after the call.\n\n `fields` and `lists` are plain arrays — exactly the shape GET returns.\n\n Extract paths do NOT read the raw application response — the platform\n wraps every response in a canonical per-record envelope first, and\n extracts evaluate against that envelope:\n\n - **Lookup steps**: `{\"statusCode\": 200, \"data\": [\u003cresult records>],\n \"errors\": []}` — `data` holds the records the lookup step returns,\n AFTER the export's own `transform` and output `filter` have run, so\n extracts must start from it and name transformed fields by their\n transformed name: `data[0].name` copies a field of the first result,\n `data` copies the whole result array. A bare result-record field name\n (e.g. `name`) resolves to nothing and merges nothing, and so does the\n raw response path a transform consumed. A lookup that groups its\n results returns each group as an array inside `data`\n (`\"data\": [[\u003crow>, \u003crow>]]`): `data[0][0].name` (or `data.0.0.name`)\n reads the first row of the first group, and `data[0]` copies the group.\n - **Import steps**: `{\"id\": \"\u003cdestination record id>\", \"statusCode\": 200,\n \"ignored\": false, \"errors\": [], \"_json\": \u003craw destination response>,\n ...\u003cresponse body fields flattened at the root>}` — use `id` for the\n destination-assigned id and `_json.\u003cpath>` (or the flattened\n `\u003cpath>`) for fields inside the destination's response body. The full\n envelope (including `id`) is present on real runs and in flow-builder\n previews; flow TEST runs never execute imports, and their envelope is\n built from the import's saved `mockResponse`, or from the placeholder\n `{\"id\": \"1234567890\", \"mockResponse\": \"replace me\"}` (numbered\n `1234567890-1`, `1234567890-2`, … when a page holds several records)\n — an `extract: \"id\"` in a test run merges that mock id, never a\n destination record id.\n\n An `extract` may also be a Handlebars expression. It evaluates against\n the same envelope as a plain path, so every path inside `{{ }}` starts\n where the plain paths start — `{{data.0.name}}`,\n `{{#compare data.0.count \"==\" 1}}yes{{else}}no{{/compare}}`,\n `{{#if data.length}}found{{/if}}` on a lookup; `{{_json.order.id}}` or\n `{{id}}` on an import. Index arrays with dots inside Handlebars\n (`data.0.name` or `data.[0].name`; `data[0].name` renders nothing), and\n expect a string result (`\"1\"`, `\"false\"`). There is no `record` object in\n this context: `{{record.\u003canything>}}` renders nothing. Prefer a plain\n path when the value is simply copied.\n x-celigo-ai-guidance:\n - |-\n Handlebars is allowed in `extract` but only rooted at the envelope —\n `{{data.0.x}}` on a lookup, `{{_json.x}}` / `{{id}}` on an import. The\n `record.` root belongs to import-mapping templates; here every\n `{{record.…}}` form renders nothing, silently, and `{{data[0].x}}`\n (bracket index inside Handlebars) renders nothing too. Use Handlebars\n only for a derived value (a flag, a concatenation); copy values with a\n plain path.\n - |-\n A lookup's `data[]` is the step's output after its `transform` and\n `filter`, so author extracts against the transformed records — never\n against the raw response the transform consumed, and never against a\n saved `mockOutput` as stored (it is the raw response; the transform\n runs on top of it in a test run).\n - |-\n Send `fields` and `lists` as bare arrays, exactly as GET returns them.\n Never wrap either in an object (e.g. `\"fields\": {\"type\": [...]}`): the\n API does not reject that shape — it returns 200 and silently persists\n `fields: [null]` / `lists: [{\"fields\": []}]`, erasing the step's\n response mappings.\n - |-\n responseMapping is fully replaced on PUT: omitting it resets the step\n to empty `{\"fields\": [], \"lists\": []}`. Always send the complete\n intended mapping for every step.\n - |-\n To carry several lookup results onto the record, use `extract: \"data\"`\n (the whole result array) or a `lists` entry whose extracts use\n `data[*].x` — never `data[*].x` in `fields[].extract`, which merges\n nothing: the record simply never gains the field.\n examples:\n - fields:\n - extract: \"id\"\n generate: \"ContactId\"\n lists: []\n - fields:\n - extract: \"data[0].customer.name\"\n generate: \"customerName\"\n lists:\n - generate: \"matchedOrders\"\n fields:\n - extract: \"data[*].id\"\n generate: \"orderId\"\n - extract: \"data[*].total\"\n generate: \"amount\"\n properties:\n fields:\n type: array\n description: |\n Field mappings that copy individual values from the response\n envelope onto the in-flight record.\n items:\n type: object\n properties:\n extract:\n type: string\n x-celigo-agent:\n - handlebars\n description: |\n Path within the canonical response envelope to copy the value\n from, or a Handlebars expression over that same envelope. For\n lookup steps, paths start from the envelope's `data` array\n (`data[0].x` and `data.0.x` are equivalent; `data` alone\n copies every result) — a lookup result field written without\n the `data` prefix resolves to nothing. For import steps, use\n the envelope fields `id`, `statusCode`, `ignored`, `_json.\u003cpath>`\n for the raw response body, or the body field itself at the\n envelope root (`orderNumber` and `_json.orderNumber` read the\n same value). A `[*]` wildcard is not honored here — `data[*].x`\n merges nothing; iterate arrays with `lists` instead. Inside\n Handlebars, index with dots (`{{data.0.x}}`, never\n `{{data[0].x}}`) and never use a `record.` root.\n examples:\n [\"data[0].customer.name\", \"data.0.fields.Product Name\", \"data\", \"id\", \"_json.orderNumber\", \"{{#if data.length}}found{{else}}missing{{/if}}\"]\n generate:\n type: string\n description: |\n Field path on the in-flight record where the extracted value\n is stored, using dot notation for nesting. Downstream steps\n read the value at this path.\n examples:\n [\"customerName\", \"supplierDetails.supplierName\", \"netsuite_order_id\"]\n lists:\n type: array\n description: |\n Array mappings that build a target array on the in-flight record\n from an array inside the response envelope — one output item per\n element of the array the item extracts mark with `[*]` (`data[*].x`\n for lookups, `_json.\u003carrayPath>[*].x` for imports); an empty source\n array yields an empty target array. Not needed for one-to-many\n (fan-out) merge-back, which uses plain field mappings.\n items:\n type: object\n properties:\n generate:\n type: string\n description: |\n Array field created on the in-flight record to hold the\n mapped items.\n examples: [\"orderItems\", \"addresses\", \"contacts\"]\n fields:\n type: array\n description: |\n Field mappings applied to each item of the source array to\n produce the corresponding target-array item.\n items:\n type: object\n properties:\n extract:\n type: string\n description: |\n Full envelope path to the source value, with `[*]`\n marking the array to iterate — `data[*].productId` for a\n lookup, `_json.items[*].sku` for an import. A path without\n `[*]` (e.g. `statusCode`) repeats the same envelope value\n on every item; an item-relative name (`productId`)\n resolves to nothing and produces empty objects.\n examples: [\"data[*].productId\", \"data[*].quantity\", \"_json.items[*].sku\"]\n generate:\n type: string\n description: Target field within each mapped array item.\n examples: [\"itemId\", \"qty\", \"unitPrice\"]\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":8714,"content_sha256":"29e8e545086c95dbbfd13e76e0cfd2efd4b017292d49a52201279e972a7b2f9e"},{"filename":"references/schemas/transform.yml","content":"Transform:\n type: object\n description: |\n Configuration for transforming data during processing operations. This object enables\n reshaping of records.\n\n **Transformation capabilities**\n\n Celigo's transformation engine offers powerful features for data manipulation:\n - Precise field mapping with JSONPath expressions\n - Support for any level of nested arrays\n - Formula-based field value generation\n - Dynamic references to flow and integration settings\n\n **Implementation approaches**\n\n There are two distinct transformation mechanisms available:\n\n **Rule-Based Transformation (`type: \"expression\"`)**\n - **Best For**: Most transformation scenarios from simple to complex\n - **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n - **Advantages**: Visual configuration, no coding required, intuitive interface\n - **Configuration**: Define rules in the `expression` object\n - **Use When**: You have clear mapping requirements or need to reshape data structure\n\n **Script-Based Transformation (`type: \"script\"`)**\n - **Best For**: Extremely complex logic or proprietary algorithms\n - **Capabilities**: Full programmatic control, custom processing, complex business rules\n - **Advantages**: Maximum flexibility, can implement any transformation logic\n - **Configuration**: Reference a script in the `script` object\n - **Use When**: Visual transformation tools aren't sufficient for your use case\n properties:\n type:\n type: string\n description: |\n Determines which transformation mechanism to use. This choice affects which properties\n must be configured and how transformation logic is implemented.\n\n **Available types**\n\n **Rule-Based Transformation (`\"expression\"`)**\n - **Required Config**: The `expression` object with mapping definitions\n - **Behavior**: Applies declarative rules to reshape data\n - **Best For**: Most transformation scenarios from simple to complex\n - **Advantages**: Visual configuration, no coding required\n\n **Script-Based Transformation (`\"script\"`)**\n - **Required Config**: The `script` object with _scriptId and function\n - **Behavior**: Executes custom JavaScript to transform data\n - **Best For**: Extremely complex logic or proprietary algorithms\n - **Advantages**: Maximum flexibility, can implement any logic\n\n **Implementation guidance**\n\n 1. For standard data transformations, use `\"expression\"`\n 2. For complex logic or specialized processing, use `\"script\"`\n 3. When selecting a type, you must configure the corresponding object:\n - `type: \"expression\"` requires the `expression` object\n - `type: \"script\"` requires the `script` object\n enum: [\"expression\", \"script\"]\n x-enumDescriptions:\n expression: Rule-based transformation using declarative mapping definitions.\n script: Script-based transformation using custom JavaScript logic.\n examples: [\"expression\", \"script\"]\n expression:\n type: object\n description: |\n Configuration for declarative rule-based transformations. This object enables reshaping data\n without requiring custom code.\n\n **Usage context**\n\n This object is REQUIRED when `transform.type` is set to \"expression\" and should not be\n configured otherwise. It provides a standardized way to define transformation rules that\n can map, modify, and generate data elements.\n\n **Implementation guidance**\n\n The expression system uses a rule-based approach where:\n - Field mappings define how input data is transformed to target fields\n - Formulas can be used to calculate or generate new values\n - Lookups can enrich data by fetching related information\n - Mode determines how records are processed (create new or modify existing)\n properties:\n version:\n type: string\n description: |\n Version of the expression format. Determines which rules\n property contains the transformation logic.\n enum: [\"1\", \"2\"]\n x-enumDescriptions:\n \"1\": \"Legacy format. Rules are in the `rules` array (array of arrays of extract/generate objects).\"\n \"2\": \"Current format. Rules are in the `rulesTwoDotZero` object with mode, mappings, and lookups.\"\n examples: [\"1\", \"2\"]\n rules:\n type: array\n description: |\n Transformation rules for version 1 expressions. An array of\n rule groups; each group is an array of field-mapping objects.\n Most transforms have a single group. Present when `version`\n is `\"1\"`. The output record contains ONLY the generated\n fields — every unmapped field is dropped (v1 has no\n equivalent of Transform 2.0's `modify` mode), and the\n record's trace key does not survive the rebuild.\n x-celigo-ai-guidance:\n - |-\n v1 rules REPLACE the record: a one-field rule group drops\n every other field, silently breaking downstream filters,\n router branch rules, and templates that referenced the\n dropped fields. Either map every field downstream logic\n needs forward, or author Transform 2.0 with\n ``mode: \"modify\"`` instead.\n - |-\n An import's ``responseTransform`` is the exception: it accepts\n version ``\"1\"`` only (``\"2\"`` is refused with 422, verified live\n 2026-10-21), so there, map every response field that response\n mapping needs.\n items:\n type: array\n items:\n type: object\n properties:\n extract:\n type: string\n description: |\n Source field path to read from. Supports multiple\n syntaxes: bare field names (`id`), dot notation\n (`fulfillment.shipment_id`), slash-prefixed paths\n for XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n `*.[Internal ID]`), and array indexing (`SDF[0]`).\n generate:\n type: string\n description: |\n Target field name to write to. Typically a bare name\n (`id`) or dot path (`SDF.Filter.ID`).\n key:\n type: string\n description: |\n Auto-generated identifier for this rule, used by the\n UI to track individual rules for editing and reordering.\n required:\n - extract\n - generate\n rulesTwoDotZero:\n type: object\n description: |\n Configuration for version 2 transformation rules. This object contains the core logic\n for how data is mapped, enriched, and transformed.\n\n **Capabilities**\n\n Transformation 2.0 provides:\n - Precise field mapping with JSONPath expressions\n - Support for deeply nested data structures\n - Formula-based field generation\n - Dynamic lookups for data enrichment\n - Multiple operating modes to fit different scenarios\n properties:\n mode:\n type: string\n description: |\n Transformation mode that determines how records are handled during processing.\n\n **Available modes**\n\n **Create Mode (`\"create\"`)**\n - **Behavior**: Builds entirely new output records from inputs\n - **Use When**: Output structure differs significantly from input\n - **Advantage**: Clean slate approach, no field inheritance\n\n **Modify Mode (`\"modify\"`)**\n - **Behavior**: Makes targeted edits to existing records\n - **Use When**: Output structure should remain similar to input\n - **Advantage**: Preserves unmapped fields from the original record\n enum: [\"create\", \"modify\"]\n x-enumDescriptions:\n create: Builds entirely new output records from inputs, ignoring unmapped fields.\n modify: Makes targeted edits to existing records, preserving unmapped fields.\n x-celigo-ai-guidance:\n - |-\n On a grouped record (the export groups rows, so the record is an\n array) only `create` can author output — build it from the rows with\n `$[0].field`, `$[*].field`, or a `buildArrayHelper` over `$[*]`.\n `modify` has no object to overlay onto an array: the record passes\n through untouched and the processor reports success. Either mode\n reshapes the inside of one record; neither changes how many records\n the page carries (that is the export's `preSavePage` hook) or whether\n the export groups at all (that is its grouping setting).\n examples: [\"create\", \"modify\"]\n mappings:\n $ref: './mappings.yml#/Mappings'\n lookups:\n allOf:\n - description: |\n Shared lookup tables used across all mappings defined in the transformation rules.\n\n **Purpose**\n\n Lookups provide centralized value translation that can be referenced from any mapping\n in your transformation configuration. They enable consistent translation of codes, IDs,\n and values between systems without duplicating translation logic.\n\n **Usage in transformations**\n\n Lookups are particularly valuable in transformations for:\n\n - **Data Normalization**: Standardizing values from diverse source systems\n - **Code Translation**: Converting between different coding systems (e.g., status codes)\n - **Field Enrichment**: Adding descriptive values based on ID or code lookups\n - **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n **Implementation**\n\n Lookups are defined once in this array and referenced by name in mappings:\n\n ```json\n \"lookups\": [\n {\n \"name\": \"statusMapping\",\n \"map\": {\n \"A\": \"Active\",\n \"I\": \"Inactive\",\n \"P\": \"Pending\"\n },\n \"default\": \"Unknown Status\"\n }\n ]\n ```\n\n Then referenced in mappings using the lookupName property:\n\n ```json\n {\n \"generate\": \"status\",\n \"dataType\": \"string\",\n \"extract\": \"$.statusCode\",\n \"lookupName\": \"statusMapping\"\n }\n ```\n\n The system automatically applies the lookup during transformation processing.\n\n For complete details on lookup properties and behavior, see the Lookups schema.\n - $ref: './lookups.yml#/Lookups'\n inputContext:\n type: string\n enum:\n - record\n - envelope\n x-enumDescriptions:\n record: The transform processor evaluates `mappings[].extract` JSONPath against the flat record itself (`$.field`, no `record.` segment) — the default when absent.\n envelope: The transform processor evaluates JSONPath against the wrapped runtime envelope `{record, job, testMode, settings, headers, queryParams}` (file-provider steps also carry `fileMeta`) — record fields need the `record.` segment (`$.record.field`), while `headers` and `queryParams` sit beside `record` (`$.headers['X-Id']`, `$.queryParams.src`).\n description: |\n Controls the JSON shape the transformTwoDotZero processor\n evaluates `mappings[].extract` JSONPath values against at\n flow runtime. Applies only to Transform 2.0 (v2,\n `rulesTwoDotZero`); v1 transforms (the `rules` array on\n `transform.expression.rules`) and script-mode transforms\n ignore this field.\n x-celigo-ai-guidance:\n - |-\n ## Extract anchoring by mode\n\n - `record` (default — absent / empty / undefined): the\n processor sees the FLAT record as its evaluation root.\n `mappings[].extract` JSONPath MUST anchor directly at\n ` writing-mappings — Skillopedia with NO `record.` segment — e.g. `\"$.orderId\"`,\n `\"$.customer.email\"`; PLACEHOLDER extracts use\n `\"$.PLACEHOLDER_\u003cfield>\"`.\n This is the dominant Celigo idiom and the right choice for new transforms.\n - `envelope`: the processor sees a wrapped object\n `{record: {...}, job, testMode, settings, headers,\n queryParams}` (plus `fileMeta` on file-provider steps).\n `mappings[].extract` JSONPath MUST include the\n `record.` segment for record fields — e.g.\n `\"$.record.orderId\"` — and reads `headers` and\n `queryParams` beside it (`$.headers['X-Id']`);\n PLACEHOLDER extracts use `\"$.record.PLACEHOLDER_\u003cfield>\"`.\n Pick this only when the transform was authored against\n the envelope shape on purpose.\n - |-\n Handlebars expressions inside `extract`\n (`{{record.field}}`, `{{dateFormat ... record.field}}`)\n ALWAYS reference the `record` root regardless of\n `inputContext` — only JSONPath anchoring flips.\n - |-\n In `envelope` mode the output is still a bare record: a\n `modify` transform returns the record with its mapped\n fields merged in (no `record` wrapper, no `job` or\n `settings` keys), and a `create` transform returns only\n the mapped fields.\n - |-\n Emit `inputContext` explicitly on new v2 transforms:\n unset is read as `record`, but stating it makes the\n extract-anchoring contract self-documenting and immune\n to a platform default change.\n - |-\n ## Allowed values\n\n - ``record`` (default — emitted when the field is absent\n / empty / undefined): the transform processor sees\n the FLAT record itself as its evaluation root.\n ``mappings[].extract`` JSONPath values MUST anchor\n directly at `` writing-mappings — Skillopedia ` with NO ``record.`` segment — for\n example ``\"$.orderId\"`` or\n ``\"$.customer.email\"``. PLACEHOLDER extracts use\n ``\"$.PLACEHOLDER_\u003cfield>\"``. This is the dominant\n Celigo idiom and the right choice for new transforms.\n\n - ``envelope``: the transform processor sees a wrapped\n object of the shape\n ``{record: {...}, job, testMode, settings, headers,\n queryParams}`` (plus ``fileMeta`` on file-provider\n steps; ``headers`` and ``queryParams`` sit beside\n ``record``) — the\n same runtime-context envelope Handlebars / SQL /\n GraphQL templates render against. ``mappings[].extract``\n JSONPath values for record fields MUST include the\n ``record.`` segment — for example ``\"$.record.orderId\"``\n or ``\"$.record.customer.email\"`` — while ``headers`` and\n ``queryParams`` are read beside it\n (``\"$.headers['X-Id']\"``). PLACEHOLDER extracts\n use ``\"$.record.PLACEHOLDER_\u003cfield>\"``. Pick this\n mode only when the export was authored against the\n envelope shape on purpose (typically because an\n upstream step intentionally feeds the wrapped\n runtime context through to the transform).\n\n ## Handlebars expressions are NOT affected\n\n Handlebars expressions inside ``extract``\n (``{{record.field}}``,\n ``{{dateFormat ... record.field}}``, etc.) ALWAYS\n reference the runtime context's ``record`` root\n regardless of ``inputContext`` — the Handlebars runtime\n always sees the wrapped envelope. Only JSONPath\n behavior flips with this field.\n\n ## Agent guidance\n\n Set this field explicitly on every new v2 transform\n the agent authors. Leaving it unset is interpreted as\n ``record`` by the platform, but emitting\n ``inputContext: record`` makes the contract\n self-documenting and prevents the transform from\n drifting into envelope mode if an upstream change ever\n changes the platform default. Read this value before\n writing ``mappings[].extract`` so the JSONPath form\n matches the runtime root.\n - |-\n ## fileMeta on file-provider steps\n\n File-provider exports and lookups populate a\n ``fileMeta`` envelope key describing each record's\n source file: ``fileName``, ``fileSize``, and\n ``lastModifiedTime`` (epoch milliseconds). Reach it\n as ``$.fileMeta.\u003cfield>`` in envelope mode, or as\n ``{{fileMeta.\u003cfield>}}`` from a Handlebars extract in\n either mode. The keys are exactly these three —\n lowercase variants such as ``filesize`` or\n ``filename`` resolve empty. The standalone transform\n processor substitutes sample fileMeta values when the\n posted data lacks them, so a resolving editor render\n is not proof the step provides fileMeta — only\n file-provider steps populate it at flow runtime.\n examples: [\"record\", \"envelope\"]\n script:\n type: object\n description: |\n Configuration for programmable script-based transformations. This object enables complex, custom\n transformation logic beyond what expression-based transformations can provide.\n\n **Usage context**\n\n This object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\n otherwise. It provides a way to execute custom JavaScript code to transform data according to\n specialized business rules or complex algorithms.\n\n **Implementation approach**\n\n Script-based transformation works by:\n 1. Executing the specified function from the referenced script\n 2. Passing input data to the function\n 3. Using the function's return value as the transformed output\n\n **Common use cases**\n\n Script transformation is ideal for:\n - Complex business logic that can't be expressed through mappings\n - Algorithmic transformations requiring computation\n - Dynamic transformations based on external factors\n - Legacy system data format compatibility\n - Multi-stage processing with intermediate steps\n\n Only use script-based transformation when expression-based transformation is insufficient.\n Script transformation requires maintaining custom code, which adds complexity to the integration.\n properties:\n _scriptId:\n type: string\n description: |\n Reference to a predefined script resource containing the transformation logic.\n\n The referenced script should contain the function specified in the\n 'function' property. It must be a script in this account: any other id fails the save\n with 422 (\"Only a script owned by the user or a script shared by the Integration App can\n be used at transform.script._scriptId.\").\n format: objectid\n x-celigo-refModel: scripts\n examples: [\"60a2c4e6f321d800129a1a3c\", \"5f8d43a1b9e5a80011a35f2c\"]\n function:\n type: string\n description: |\n Name of the function within the script to execute for transformation. This function\n must exist in the script referenced by _scriptId.\n examples: [\"transformData\", \"convertRecord\", \"applyMapping\"]\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":21239,"content_sha256":"0f2a1bdcf3fd4c5a271d5fc97ba73ce416e3c9a25c3767407d9d0eac9738578f"}],"content_json":{"type":"doc","content":[{"type":"paragraph","content":[{"text":"\u003c!-- TIER:1 -->","type":"text"}]},{"type":"heading","attrs":{"level":1},"content":[{"text":"Writing Mappings and Transforms","type":"text"}]},{"type":"paragraph","content":[{"text":"Mappings and transforms are the ","type":"text"},{"text":"data reshaping layer","type":"text","marks":[{"type":"strong"}]},{"text":" in Celigo integrations. They control how fields from one system translate into fields for another. Mappings are used across flows, APIs, and tools.","type":"text"}]},{"type":"paragraph","content":[{"text":"Take every source path from a real record.","type":"text","marks":[{"type":"strong"}]},{"text":" Read what the step receives -- the import's ","type":"text"},{"text":"sampleData","type":"text","marks":[{"type":"code_inline"}]},{"text":", the source export's ","type":"text"},{"text":"mockOutput","type":"text","marks":[{"type":"code_inline"}]},{"text":" or a recent run's records, or a failing record's retry data -- before writing an ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":". 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 (","type":"text"},{"text":"Understand the source data shape","type":"text","marks":[{"type":"link","attrs":{"href":"#2-understand-the-source-data-shape","title":null}}]},{"text":").","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to Run the Steps","type":"text"}]},{"type":"paragraph","content":[{"text":"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:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Celigo MCP tools","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"list_imports","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"upsert_import","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"get_schema","type":"text","marks":[{"type":"code_inline"}]},{"text":", ...) -- ","type":"text"},{"text":"references/mcp.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/mcp.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The ","type":"text","marks":[{"type":"strong"}]},{"text":"celigo","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" CLI","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/cli.md","title":null}}]}]}]}]},{"type":"paragraph","content":[{"text":"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.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Mapping Systems","type":"text"}]},{"type":"paragraph","content":[{"text":"Four systems handle data reshaping:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mapper 2.0","type":"text","marks":[{"type":"strong"}]},{"text":" -- modern recursive field mapping on imports (","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" array). Handles nested objects, arrays of any depth, lookups, conditionals, and date conversions. Default for new imports on all adaptor types except NetSuite DA (","type":"text"},{"text":"NetSuiteDistributedImport","type":"text","marks":[{"type":"code_inline"}]},{"text":"), Salesforce and ","type":"text"},{"text":"WrapperImport","type":"text","marks":[{"type":"code_inline"}]},{"text":", which support only Mapper 1.0 -- ","type":"text"},{"text":"NetSuiteHTTPImport","type":"text","marks":[{"type":"code_inline"}]},{"text":" uses Mapper 2.0. The same rows also reshape the arriving record into an HTTP or NetSuite REST lookup's request (","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an ","type":"text"},{"text":"isLookup","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"HTTPExport","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"NetSuiteHTTPExport","type":"text","marks":[{"type":"code_inline"}]},{"text":"; no other export takes them), and build a Tool's ","type":"text"},{"text":"output.mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":" and an API response's ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mapper 1.0","type":"text","marks":[{"type":"strong"}]},{"text":" -- legacy flat mapping on Salesforce and ","type":"text"},{"text":"WrapperImport","type":"text","marks":[{"type":"code_inline"}]},{"text":" imports (","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"mapping.lists[]","type":"text","marks":[{"type":"code_inline"}]},{"text":") and NetSuite DA imports (the same arrays under ","type":"text"},{"text":"netsuite_da.mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Body-level and sublist fields in separate flat arrays. Also present on many older HTTP/FTP/RDBMS imports created before Mapper 2.0 existed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Transformation 2.0","type":"text","marks":[{"type":"strong"}]},{"text":" -- rule-based data reshaping on exports (","type":"text"},{"text":"transform.expression.rulesTwoDotZero","type":"text","marks":[{"type":"code_inline"}]},{"text":"). 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)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Response mapping","type":"text","marks":[{"type":"strong"}]},{"text":" -- simple extract/generate pairs that carry data from a lookup or import response back into the record (","type":"text"},{"text":"responseMapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" on flow ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":"). 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)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Lookups translate values in every system. They come in three kinds: ","type":"text"},{"text":"static","type":"text","marks":[{"type":"strong"}]},{"text":" (an inline ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"cached","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"_lookupCacheId","type":"text","marks":[{"type":"code_inline"}]},{"text":", read from a LookupCache resource), and ","type":"text"},{"text":"live","type":"text","marks":[{"type":"strong"}]},{"text":" (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.","type":"text"}]},{"type":"paragraph","content":[{"text":"Direction decides the tool.","type":"text","marks":[{"type":"strong"}]},{"text":" Mappings translate data going ","type":"text"},{"text":"out","type":"text","marks":[{"type":"em"}]},{"text":" to a destination -- every import needs them, because the in-flight record almost never matches what the destination expects. Transformations reshape data coming ","type":"text"},{"text":"in","type":"text","marks":[{"type":"em"}]},{"text":" -- 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.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Which Mapping System?","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Context","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Syntax","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read schema","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Import field mapping (HTTP, RDBMS, FTP, S3, etc.)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mapper 2.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mappings.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/mappings.yml","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NetSuite DA / Salesforce / Wrapper import","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mapper 1.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"netsuite_da.mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"see import schema (","type":"text"},{"text":"netsuitedistributed.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/netsuitedistributed.yml","title":null}}]},{"text":"; the Salesforce and Wrapper ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" is in ","type":"text"},{"text":"base.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/base.yml","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Export data reshaping","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transformation 2.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"transform{}","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"transform.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/transform.yml","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Response mapping (lookup/import carry-back)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transformation 1.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"extract/generate pairs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"response-mapping.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/response-mapping.yml","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Value translation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lookups","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"lookups[]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"lookups.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/lookups.yml","title":null}}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Editing existing imports:","type":"text","marks":[{"type":"strong"}]},{"text":" 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 ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (2.0) or ","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (1.0) before modifying -- never mix the two.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Schema Index","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Schema","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Contents","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mappings.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/mappings.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mapper 2.0 field definitions (generate, extract, dataType, buildArrayHelper, conditionals)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"lookups.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/lookups.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Static and cached lookup definitions (the schema calls cached lookups \"dynamic\")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"transform.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/transform.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transformation 2.0 envelope (mode, expression, script)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"response-mapping.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/response-mapping.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transformation 1.0 extract/generate pairs for response carry-back","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"netsuitedistributed.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/netsuitedistributed.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NetSuite Mapper 1.0 mapping + lookups","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"base.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/base.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The import-level Mapper 1.0 ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" that Salesforce and Wrapper imports use","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"salesforce.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/salesforce.yml","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Salesforce import settings and lookups","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"configuring-imports","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#quick-reference","title":null}}]},{"text":") -- import adaptor types, operation logic, hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"configuring-exports","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-exports/SKILL.md#quick-reference","title":null}}]},{"text":") -- export adaptor types, delta syncs, webhooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"writing-handlebars","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../writing-handlebars/SKILL.md#quick-reference","title":null}}]},{"text":") -- Handlebars expressions used inside mapping ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"building-flows","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"How to Build a Flow","type":"text","marks":[{"type":"link","attrs":{"href":"../building-flows/SKILL.md#how-to-build-a-flow","title":null}}]},{"text":") -- wiring exports and imports into a flow pipeline","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"\u003c!-- TIER:2 -->","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Mapper 2.0 Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" array is ","type":"text"},{"text":"recursive","type":"text","marks":[{"type":"strong"}]},{"text":" -- a mapping can contain nested child mappings of the same structure to any depth. This is the core design principle.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Check the existing resource","type":"text"}]},{"type":"paragraph","content":[{"text":"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 (","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":") or Mapper 1.0 (","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Understand the source data shape","type":"text"}]},{"type":"paragraph","content":[{"text":"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.","type":"text"}]},{"type":"paragraph","content":[{"text":"On a one-to-many import (","type":"text"},{"text":"oneToMany: true","type":"text","marks":[{"type":"code_inline"}]},{"text":") the mapping sees one child at a time. With ","type":"text"},{"text":"pathToMany","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"$.\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the child's field and ","type":"text"},{"text":"$._PARENT.\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":" the parent's -- ","type":"text"},{"text":"_PARENT","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the parent without the child array, and the parent's own fields aren't at ","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":". With a blank ","type":"text"},{"text":"pathToMany","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a grouped record, each row is the record.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Understand the target data shape","type":"text"}]},{"type":"paragraph","content":[{"text":"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.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Choose the input context","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"input context","type":"text","marks":[{"type":"strong"}]},{"text":" controls what JSONPath extracts start from. On an import it's the import's ","type":"text"},{"text":"inputContext","type":"text","marks":[{"type":"code_inline"}]},{"text":" field; on a Transformation 2.0 transform, ","type":"text"},{"text":"transform.expression.rulesTwoDotZero.inputContext","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"record","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (the default) -- extract paths reference the record directly. ","type":"text"},{"text":"$.user_id","type":"text","marks":[{"type":"code_inline"}]},{"text":" accesses the ","type":"text"},{"text":"user_id","type":"text","marks":[{"type":"code_inline"}]},{"text":" field on the record","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"envelope","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- record fields shift to ","type":"text"},{"text":"$.record.user_id","type":"text","marks":[{"type":"code_inline"}]},{"text":", and the envelope also carries ","type":"text"},{"text":"job","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"testMode","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"settings","type":"text","marks":[{"type":"code_inline"}]},{"text":". Read settings as ","type":"text"},{"text":"$.settings.\u003cscope>.\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":", where the scope is ","type":"text"},{"text":"integration","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"flowGrouping","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"flow","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"connection","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"iClient","type":"text","marks":[{"type":"code_inline"}]},{"text":", or the step's own ","type":"text"},{"text":"import","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"export","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., ","type":"text"},{"text":"$.settings.connection.api_username","type":"text","marks":[{"type":"code_inline"}]},{"text":"); a path outside these resolves to nothing. A webhook listener's transform also gets the incoming request's ","type":"text"},{"text":"headers","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"queryParams","type":"text","marks":[{"type":"code_inline"}]},{"text":", and file-provider steps get ","type":"text"},{"text":"fileMeta","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"An import's envelope carries no request headers. In API and Tool runs, the integration, flow-group and flow settings are empty.","type":"text"}]},{"type":"paragraph","content":[{"text":"When to use envelope:","type":"text","marks":[{"type":"strong"}]},{"text":" when a mapping needs a setting or job value as a JSON path -- ","type":"text"},{"text":"$.settings.connection.fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of a ","type":"text"},{"text":"{{settings.connection.fieldName}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" Handlebars expression.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Map by data type","type":"text"}]},{"type":"paragraph","content":[{"text":"Every mapping needs three properties: ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" (target field name), ","type":"text"},{"text":"dataType","type":"text","marks":[{"type":"code_inline"}]},{"text":" (output type), and ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (how to get data from source).","type":"text"}]},{"type":"paragraph","content":[{"text":"Extract supports three patterns","type":"text","marks":[{"type":"strong"}]},{"text":" (distinguished by syntax):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSON path","type":"text","marks":[{"type":"strong"}]},{"text":" -- starts with ","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":", the record as it arrives: ","type":"text"},{"text":"$.customer.email","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an object record; ","type":"text"},{"text":"$[0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" (one row) or ","type":"text"},{"text":"$[*].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" (every row) on a grouped record -- an array of rows from ","type":"text"},{"text":"groupByFields","type":"text","marks":[{"type":"code_inline"}]},{"text":", file key columns, or a NetSuite saved search (grouped unless ","type":"text"},{"text":"skipGrouping: true","type":"text","marks":[{"type":"code_inline"}]},{"text":"). On a grouped record ","type":"text"},{"text":"$.field","type":"text","marks":[{"type":"code_inline"}]},{"text":" 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: ","type":"text"},{"text":"$['P.O. Number']","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"$[\"P.O. Number\"]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"$.[P.O. Number]","type":"text","marks":[{"type":"code_inline"}]},{"text":"); ","type":"text"},{"text":"$.P.O. Number","type":"text","marks":[{"type":"code_inline"}]},{"text":" is read as a nested path, maps nothing, and the record still succeeds. Spaces, hyphens and ","type":"text"},{"text":"#","type":"text","marks":[{"type":"code_inline"}]},{"text":" need no brackets (","type":"text"},{"text":"$.Ship To Name","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"$.Tracking #","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Filter expressions fail the record (gotcha 13).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handlebars","type":"text","marks":[{"type":"strong"}]},{"text":" -- contains ","type":"text"},{"text":"{{","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., ","type":"text"},{"text":"{{record.firstName}} {{record.lastName}}","type":"text","marks":[{"type":"code_inline"}]},{"text":"). For computed values","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hard-coded","type":"text","marks":[{"type":"strong"}]},{"text":" -- plain string literal (e.g., ","type":"text"},{"text":"\"Active\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"USD\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Neither starts with ","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":" nor contains ","type":"text"},{"text":"{{","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Simple types","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"number","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"boolean","type":"text","marks":[{"type":"code_inline"}]},{"text":") -- direct field-to-field mapping. Give each row the type the destination field expects rather than ","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":" by default: ","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":" sends a numeric source as text (","type":"text"},{"text":"\"24\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"), which an API that checks types refuses. ","type":"text"},{"text":"number","type":"text","marks":[{"type":"code_inline"}]},{"text":" converts it (","type":"text"},{"text":"\"24\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"24","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"007\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"7","type":"text","marks":[{"type":"code_inline"}]},{"text":"), leaves the field out when the source is ","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", and fails the record with ","type":"text"},{"text":"non_parsable_string","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"\u003cgenerate>: String contains alpha characters","type":"text","marks":[{"type":"code_inline"}]},{"text":") when the value holds letters or a thousands separator (","type":"text"},{"text":"\"1,250.00\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Dates are strings: use ","type":"text"},{"text":"dataType: \"string\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"extractDateFormat","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"generateDateFormat","type":"text","marks":[{"type":"code_inline"}]},{"text":" (plus ","type":"text"},{"text":"extractDateTimezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"generateDateTimezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" when the zone changes). There is no ","type":"text"},{"text":"date","type":"text","marks":[{"type":"code_inline"}]},{"text":" dataType; ","type":"text"},{"text":"\"date\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" is rejected like any unknown type (","type":"text"},{"text":"Destination datatype is a required field","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"Objects","type":"text","marks":[{"type":"strong"}]},{"text":" -- set ","type":"text"},{"text":"dataType: \"object\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", add child mappings in the ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" array. Never use dot notation in ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":". A Handlebars ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an ","type":"text"},{"text":"object","type":"text","marks":[{"type":"code_inline"}]},{"text":" row must render a JSON object; anything else fails the record (","type":"text"},{"text":"You can't map string (source) to object (destination)","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"Arrays","type":"text","marks":[{"type":"strong"}]},{"text":" -- set ","type":"text"},{"text":"dataType","type":"text","marks":[{"type":"code_inline"}]},{"text":" to an array type (","type":"text"},{"text":"stringarray","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"numberarray","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"booleanarray","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":") and configure ","type":"text"},{"text":"buildArrayHelper[]","type":"text","marks":[{"type":"code_inline"}]},{"text":". The schema also lists ","type":"text"},{"text":"arrayarray","type":"text","marks":[{"type":"code_inline"}]},{"text":", but the mapper refuses it as an unknown type (","type":"text"},{"text":"Destination datatype is a required field","type":"text","marks":[{"type":"code_inline"}]},{"text":"), though a save accepts it -- build an array-of-arrays value in a script hook instead. Three entry patterns for object arrays:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract only","type":"text","marks":[{"type":"strong"}]},{"text":" -- pull existing objects from source (","type":"text"},{"text":"extract: \"$.items[*]\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mappings only","type":"text","marks":[{"type":"strong"}]},{"text":" -- construct objects from individual fields (each ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry creates one array element)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract + mappings","type":"text","marks":[{"type":"strong"}]},{"text":" -- iterate a source array and reshape each element. Uses the ","type":"text"},{"text":"composite object mechanism","type":"text","marks":[{"type":"strong"}]},{"text":": array brackets ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the extract path collapse to single objects inside the mappings, so ","type":"text"},{"text":"$.orders[*].items[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"$.orders.items.fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" in child extract paths. Parent context remains accessible (e.g., ","type":"text"},{"text":"$.orders.id","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"$.customerName","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The entry's own ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" keeps the ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (without it, ","type":"text"},{"text":"$.line_items","type":"text","marks":[{"type":"code_inline"}]},{"text":" yields one empty element per item), and child extracts drop it but keep the array's path. A child ","type":"text"},{"text":"$.line_items[*].sku","type":"text","marks":[{"type":"code_inline"}]},{"text":" maps nothing, and an element-relative ","type":"text"},{"text":"$.sku","type":"text","marks":[{"type":"code_inline"}]},{"text":" reads the record root, not the line:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\"generate\": \"lines\", \"dataType\": \"objectarray\", \"status\": \"Active\", \"buildArrayHelper\": [{\n \"extract\": \"$.line_items[*]\",\n \"mappings\": [\n {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.line_items.sku\", \"status\": \"Active\"},\n {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.line_items.quantity\", \"status\": \"Active\"},\n {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.name\", \"status\": \"Active\"}\n ]\n}]}","type":"text"}]},{"type":"paragraph","content":[{"text":"dataType: \"number\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" converts a numeric string (","type":"text"},{"text":"\"2\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"2","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"Entries add their elements in order and can be mixed: an iterating entry (","type":"text"},{"text":"$.lines[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":") followed by a constructed one (child ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":", no ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":") gives every line plus one more, such as a fee line. Never index an entry's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"$[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":") -- the whole array is silently dropped.","type":"text"}]},{"type":"paragraph","content":[{"text":"An array row takes its values only from ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries or a ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":", never from its own ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- JSONPath, Handlebars or a literal: an ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" row is refused (","type":"text"},{"text":"\"buildArrayHelper\" or \"hardCodedValue\" must be present for object array datatype.","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and a primitive-array row silently yields nothing. Handlebars works in an entry: an ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry that renders a JSON array (","type":"text"},{"text":"{{{jsonSerialize record.items}}}","type":"text","marks":[{"type":"code_inline"}]},{"text":") adds one element per item; on a primitive array each entry is one element.","type":"text"}]},{"type":"paragraph","content":[{"text":"For a constant array or object, set the row's ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- a literal entry ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" is refused (","type":"text"},{"text":"extract only supports json path or handlebars expression","type":"text","marks":[{"type":"code_inline"}]},{"text":"). A primitive array splits the value on commas exactly as written (","type":"text"},{"text":"\"A, B\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" gives ","type":"text"},{"text":"[\"A\", \" B\"]","type":"text","marks":[{"type":"code_inline"}]},{"text":"; a ","type":"text"},{"text":"numberarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" converts each part to a number), an ","type":"text"},{"text":"object","type":"text","marks":[{"type":"code_inline"}]},{"text":" takes a JSON object string, and an ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" a JSON array string.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"6. Add lookups for value translation","type":"text"}]},{"type":"paragraph","content":[{"text":"Define lookups alongside mappings and reference them by name via ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":" on any mapping; the row's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the key the lookup receives. An import whose adaptor keeps its own copy of the lookups (","type":"text"},{"text":"http.lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an HTTP import; the import schema's ","type":"text"},{"text":"lookups","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/base.yml","title":null}},{"type":"code_inline"}]},{"text":" lists the others) needs each entry there and the same entry in the top-level ","type":"text"},{"text":"lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":". On an HTTP import the save keeps ","type":"text"},{"text":"http.lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":" and mirrors it to the top level, overwriting a top-level copy that differs; one saved with only the top-level array keeps neither copy, and every record that uses the lookup then fails with ","type":"text"},{"text":"Unable to find lookup in mapping configs - \u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":" object with key-value pairs. Best for small, fixed sets (country codes, status values). A key matches only the exact value the row extracts -- case, spacing and any suffix count, so ","type":"text"},{"text":"In Transit","type":"text","marks":[{"type":"code_inline"}]},{"text":" matches neither ","type":"text"},{"text":"in transit","type":"text","marks":[{"type":"code_inline"}]},{"text":" nor ","type":"text"},{"text":"In Transit (Delayed)","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- and a save replaces the whole map, so send every key, not just a new one","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cached","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"_lookupCacheId","type":"text","marks":[{"type":"code_inline"}]},{"text":" referencing a LookupCache resource, with optional ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" JSON path to pull a specific field from the cached object","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Live","type":"text","marks":[{"type":"strong"}]},{"text":" (imports only) -- a request to the destination for each record (on an HTTP import, ","type":"text"},{"text":"method","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"relativeURI","type":"text","marks":[{"type":"code_inline"}]},{"text":", with an optional ","type":"text"},{"text":"body","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":"), so it adds one call per record. Transformation 2.0 lookups take only static and cached. An HTTP lookup's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" is a path into the response written without ","type":"text"},{"text":"$.","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- ","type":"text"},{"text":"results[0].id","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"ids","type":"text","marks":[{"type":"code_inline"}]},{"text":" for an array of ids -- the opposite of a Mapper 2.0 row's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":". A ","type":"text"},{"text":"$.","type":"text","marks":[{"type":"code_inline"}]},{"text":" prefix, ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":", or a dot path through an array (","type":"text"},{"text":"results.id","type":"text","marks":[{"type":"code_inline"}]},{"text":") resolves nothing, so the lookup misses every time. When the ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" finds two or more values, see ","type":"text"},{"text":"configuring-imports > Gotchas","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#gotchas","title":null}}]},{"text":" (gotcha 14)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A key missing from a static ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":" fails the record only when the lookup sets ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"Could not find value for static lookup: \u003cname> {Lookup Key: \u003ckey>} ...","type":"text","marks":[{"type":"code_inline"}]},{"text":"). With ","type":"text"},{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":", or with ","type":"text"},{"text":"allowFailures","type":"text","marks":[{"type":"code_inline"}]},{"text":" left out, the ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" is written (","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" writes an empty string), or the field is left out when there's none. A lookup can't pair a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" with an explicit ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":": the save refuses it (422 ","type":"text"},{"text":"Lookups with default set can not have allowFailures explicitly set to false.","type":"text","marks":[{"type":"code_inline"}]},{"text":"). A live HTTP lookup that matches nothing, or whose ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" resolves nothing, fails the record only with ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"batch_lookup_evaluation_failed","type":"text","marks":[{"type":"code_inline"}]},{"text":", source ","type":"text"},{"text":"internal","type":"text","marks":[{"type":"code_inline"}]},{"text":"); otherwise the record succeeds, with the ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" when ","type":"text"},{"text":"allowFailures","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":" and one is set, and without the field when there's none. A cache lookup follows the same ","type":"text"},{"text":"allowFailures","type":"text","marks":[{"type":"code_inline"}]},{"text":" rule; with ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":", a missing key fails the record with ","type":"text"},{"text":"lookupcache_evaluation_failed","type":"text","marks":[{"type":"code_inline"}]},{"text":". When a miss must stop the record, set ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" explicitly -- left out, the record goes through with the ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":", or without the field","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"7. Add conditionals where needed","type":"text"}]},{"type":"paragraph","content":[{"text":"conditional.when","type":"text","marks":[{"type":"code_inline"}]},{"text":" holds one value, and two settings share it: the composite-import gate -- ","type":"text"},{"text":"record_created","type":"text","marks":[{"type":"code_inline"}]},{"text":" (only on insert) or ","type":"text"},{"text":"record_updated","type":"text","marks":[{"type":"code_inline"}]},{"text":" (only on update) -- and the action when the source is empty: ","type":"text"},{"text":"extract_not_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" (leave the field out) or ","type":"text"},{"text":"fail_record_when_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" (fail the record with ","type":"text"},{"text":"\u003cgenerate>: Source field has no value","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Setting one replaces the other. Empty means null, missing or ","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"0","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" and whitespace are values and map through. With no condition, a missing or ","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" source leaves the field out, but a null source is written as null. ","type":"text"},{"text":"fail_record_when_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" can't share a row with a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the save refuses it).","type":"text"}]},{"type":"paragraph","content":[{"text":"A value that depends on another field is a Handlebars ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" with a block helper: ","type":"text"},{"text":"{{#if (eq record.status \"paid\")}}closed{{else}}open{{/if}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"{{#compare record.status \"==\" \"paid\"}}closed{{else}}open{{/compare}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"compare","type":"text","marks":[{"type":"code_inline"}]},{"text":" takes the operator as its second argument). For a fixed set of values, a static lookup with a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" does the same. Comparisons are exact and case-sensitive, so test the field that holds the value you compare with. ","type":"text"},{"text":"eq","type":"text","marks":[{"type":"code_inline"}]},{"text":" goes inside ","type":"text"},{"text":"#if","type":"text","marks":[{"type":"code_inline"}]},{"text":": as a block of its own (","type":"text"},{"text":"{{#eq a b}}...{{/eq}}","type":"text","marks":[{"type":"code_inline"}]},{"text":") it writes ","type":"text"},{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":". A helper the platform doesn't have (","type":"text"},{"text":"ifEquals","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ifCond","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"equal","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"compare","type":"text","marks":[{"type":"code_inline"}]},{"text":" without its operator, or an operator inside ","type":"text"},{"text":"{{#if}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"===","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"? :","type":"text","marks":[{"type":"code_inline"}]},{"text":") fails every record with ","type":"text"},{"text":"cannot_evaluate_handlebars","type":"text","marks":[{"type":"code_inline"}]},{"text":", though a save accepts such a row.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Schema reference","type":"text"}]},{"type":"paragraph","content":[{"text":"All Mapper 2.0 field definitions: ","type":"text"},{"text":"mappings.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/mappings.yml","title":null}}]},{"text":", ","type":"text"},{"text":"lookups.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/lookups.yml","title":null}}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Transformation 2.0 Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Transformation 2.0 reshapes data ","type":"text"},{"text":"on exports","type":"text","marks":[{"type":"strong"}]},{"text":" before it enters the pipeline. It wraps Mapper 2.0 syntax in a transform envelope with a mode selector.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Check the existing resource","type":"text"}]},{"type":"paragraph","content":[{"text":"Before modifying transforms, retrieve the current export to inspect any existing transform configuration -- search the account for it by name if you don't have its id.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Choose the mode","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"create","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- build a completely new record. Only mapped fields appear in output. Use when the output structure differs significantly from the source","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"modify","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- edit specific fields on the existing record. Unmapped fields pass through unchanged when the transform runs; rules you leave out of a save don't survive it (gotcha 9). Use for surgical adjustments (rename, add, remove a few fields)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"A grouped record takes ","type":"text","marks":[{"type":"strong"}]},{"text":"create","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" When the export groups rows (","type":"text"},{"text":"groupByFields","type":"text","marks":[{"type":"code_inline"}]},{"text":", or Group rows on a NetSuite saved search), each record is an array of its rows: in the default ","type":"text"},{"text":"record","type":"text","marks":[{"type":"code_inline"}]},{"text":" input context ","type":"text"},{"text":"$[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the first row and ","type":"text"},{"text":"$[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" every row (under ","type":"text"},{"text":"envelope","type":"text","marks":[{"type":"code_inline"}]},{"text":" the array is ","type":"text"},{"text":"$.rows","type":"text","marks":[{"type":"code_inline"}]},{"text":"; Handlebars names it ","type":"text"},{"text":"rows","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Only ","type":"text"},{"text":"create","type":"text","marks":[{"type":"code_inline"}]},{"text":" can reshape it -- header fields from ","type":"text"},{"text":"$[0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":", and a lines array from a ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry over ","type":"text"},{"text":"$[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose children read each row as ","type":"text"},{"text":"$.field","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the entry's ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" collapses, as in any composite object, and here the array is the record itself):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\"mode\": \"create\", \"mappings\": [\n {\"generate\": \"orderId\", \"dataType\": \"string\", \"extract\": \"$[0].order_id\", \"status\": \"Active\"},\n {\"generate\": \"items\", \"dataType\": \"objectarray\", \"status\": \"Active\", \"buildArrayHelper\": [{\n \"extract\": \"$[*]\",\n \"mappings\": [\n {\"generate\": \"itemCode\", \"dataType\": \"string\", \"extract\": \"$.item_code\", \"status\": \"Active\"},\n {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.quantity\", \"status\": \"Active\"}\n ]\n }]}\n]}","type":"text"}]},{"type":"paragraph","content":[{"text":"modify","type":"text","marks":[{"type":"code_inline"}]},{"text":" has no object to overlay, so it passes each array through untouched, and a ","type":"text"},{"text":"$.field","type":"text","marks":[{"type":"code_inline"}]},{"text":" path written for one object resolves nothing (in ","type":"text"},{"text":"create","type":"text","marks":[{"type":"code_inline"}]},{"text":" the record comes out ","type":"text"},{"text":"{}","type":"text","marks":[{"type":"code_inline"}]},{"text":") -- neither raises an error. Neither mode changes how many records a page carries: turning each row back into its own record is the export's preSavePage hook.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Write the mappings","type":"text"}]},{"type":"paragraph","content":[{"text":"Same Mapper 2.0 syntax: ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"dataType","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":", nested ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":", lookups. Everything described in the Mapper 2.0 section above applies here, including input context.","type":"text"}]},{"type":"paragraph","content":[{"text":"Headers.","type":"text","marks":[{"type":"strong"}]},{"text":" An HTTP export's or lookup's transform reads the response headers: ","type":"text"},{"text":"{{{headers.\u003cname>}}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" in either input context, or ","type":"text"},{"text":"$.headers['\u003cname>']","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"envelope","type":"text","marks":[{"type":"code_inline"}]},{"text":" (double braces HTML-escape the value). On a lookup, a field the transform writes reaches later steps only through the step's response mapping (","type":"text"},{"text":"data[0].\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":"). An API's request transform reads the request's parameters the same way, ","type":"text"},{"text":"{{pathParams.\u003ckey>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"{{queryParams.\u003ckey>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"{{headers.\u003ckey>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", but only the ones the API's request declares, in a test run as in a real call; write what later steps need onto the record.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Configure the transform envelope","type":"text"}]},{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"transform.type: \"expression\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"expression.version: \"2\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", then place ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"lookups[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" under ","type":"text"},{"text":"expression.rulesTwoDotZero","type":"text","marks":[{"type":"code_inline"}]},{"text":" with the chosen ","type":"text"},{"text":"mode","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Script alternative:","type":"text","marks":[{"type":"strong"}]},{"text":" Set ","type":"text"},{"text":"transform.type: \"script\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"script._scriptId","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"script.function","type":"text","marks":[{"type":"code_inline"}]},{"text":" for programmatic transforms when expression rules aren't sufficient.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Schema reference","type":"text"}]},{"type":"paragraph","content":[{"text":"All Transformation 2.0 field definitions: ","type":"text"},{"text":"transform.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/transform.yml","title":null}}]},{"text":", ","type":"text"},{"text":"mappings.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/mappings.yml","title":null}}]},{"text":", ","type":"text"},{"text":"lookups.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/lookups.yml","title":null}}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Mapper 1.0 Reference (NetSuite and Salesforce)","type":"text"}]},{"type":"paragraph","content":[{"text":"NetSuite DA (","type":"text"},{"text":"NetSuiteDistributedImport","type":"text","marks":[{"type":"code_inline"}]},{"text":"), Salesforce and ","type":"text"},{"text":"WrapperImport","type":"text","marks":[{"type":"code_inline"}]},{"text":" imports use the older flat mapping structure (","type":"text"},{"text":"NetSuiteHTTPImport","type":"text","marks":[{"type":"code_inline"}]},{"text":" uses Mapper 2.0). Two arrays within the ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" object -- the import's own ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" on Salesforce and Wrapper imports, ","type":"text"},{"text":"netsuite_da.mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" on NetSuite DA:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- body-level field mappings. Each entry has ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (source path) or ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":" (static value), ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" (target field ID), and optional ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"dataType","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"internalId","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"immutable","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"discardIfEmpty","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"conditional","type":"text","marks":[{"type":"code_inline"}]},{"text":". On NetSuite, ","type":"text"},{"text":"internalId: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" marks a reference to another record, so the value must be that record's internal id; a name or code there needs a lookup that returns the id","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mapping.lists[]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- one entry per destination sublist or child list: ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the NetSuite sublist id, e.g., ","type":"text"},{"text":"\"item\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", or the Salesforce child relationship name) and ","type":"text"},{"text":"fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (column mappings with the same properties as body fields, plus ","type":"text"},{"text":"isKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" for matching existing lines). Leave the entry's legacy ","type":"text"},{"text":"jsonPath","type":"text","marks":[{"type":"code_inline"}]},{"text":" unset; on NetSuite DA it breaks iteration, so only the first source element renders. Column extracts start at the record root: ","type":"text"},{"text":"line_items[*].sku","type":"text","marks":[{"type":"code_inline"}]},{"text":" gives one line per element, and a plain path repeats on every line","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Which existing line a mapped line writes (NetSuite DA).","type":"text","marks":[{"type":"strong"}]},{"text":" When the record already exists, is a transform (","type":"text"},{"text":"Created From","type":"text","marks":[{"type":"code_inline"}]},{"text":"), or is an item fulfillment, item receipt, invoice, cash sale or return authorization, each mapped line updates the one existing line whose ","type":"text"},{"text":"isKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" columns hold the same values. A column is a key only when marked ","type":"text"},{"text":"isKey","type":"text","marks":[{"type":"code_inline"}]},{"text":": mapping ","type":"text"},{"text":"orderline","type":"text","marks":[{"type":"code_inline"}]},{"text":" without it still matches on the item. With no ","type":"text"},{"text":"isKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" column, those transactions match on the item (","type":"text"},{"text":"item","type":"text","marks":[{"type":"code_inline"}]},{"text":", else ","type":"text"},{"text":"itemkey","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"itemname","type":"text","marks":[{"type":"code_inline"}]},{"text":"), a few other sublists on their own default key (a payment's or credit's applied transaction, ","type":"text"},{"text":"refnum","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and the rest -- a sales or purchase order's items among them -- on nothing. Two matching lines fail the record with ","type":"text"},{"text":"MULTIPLE_LINES_WITH_SAME_KEYS","type":"text","marks":[{"type":"code_inline"}]},{"text":". A line that matches none is added, which an item fulfillment refuses with ","type":"text"},{"text":"SSS_INVALID_SUBLIST_OPERATION","type":"text","marks":[{"type":"code_inline"}]},{"text":": its lines are its order's. So an update of a sales order whose sublist has no ","type":"text"},{"text":"isKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" column adds every mapped line again; key it on a value each line holds once (a line id the create wrote to a line field), and clean up the records already updated first, since their repeats share that id and fail with ","type":"text"},{"text":"MULTIPLE_LINES_WITH_SAME_KEYS","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A key updates lines; it never removes them.","type":"text","marks":[{"type":"strong"}]},{"text":" When you add one to stop repeated lines, tell the user that a line deleted at the source stays on the record. Mapping ","type":"text"},{"text":"celigo_replaceAllLines_\u003csublist id>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"celigo_replaceAllLines_item","type":"text","marks":[{"type":"code_inline"}]},{"text":") to ","type":"text"},{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":" removes the lines no mapped line matched; on an item fulfillment or receipt it sets their ","type":"text"},{"text":"itemreceive","type":"text","marks":[{"type":"code_inline"}]},{"text":" to false instead","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Mapper 1.0 is its own dialect.","type":"text","marks":[{"type":"strong"}]},{"text":" Extracts are bare dot-paths from the record root (","type":"text"},{"text":"email","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"customer.first_name","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"line_items[*].sku","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and constants go in ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":", which writes the same value on every record. Dotted and bracket-star ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths build nested destinations (","type":"text"},{"text":"customer.addresses[*].city","type":"text","marks":[{"type":"code_inline"}]},{"text":"), though NetSuite body fields stay flat. On Salesforce and HTTP imports (not checked on NetSuite DA):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handlebars names fields bare.","type":"text","marks":[{"type":"strong"}]},{"text":" An ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" holding ","type":"text"},{"text":"{{","type":"text","marks":[{"type":"code_inline"}]},{"text":" reads the record's fields without a prefix: ","type":"text"},{"text":"{{email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"{{#if mobile}}{{mobile}}{{else}}{{phone}}{{/if}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"{{split contact_name \" \" 0}}","type":"text","marks":[{"type":"code_inline"}]},{"text":". ","type":"text"},{"text":"{{record.email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"{{data.email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" render nothing, so the field is left out even when the record has the value -- unlike Mapper 2.0, which reads ","type":"text"},{"text":"{{record.email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Empty sources.","type":"text","marks":[{"type":"strong"}]},{"text":" A row's ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" is written when the source is ","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", null or missing: ","type":"text"},{"text":"{\"extract\": \"phone\", \"generate\": \"Phone\", \"default\": \"none\"}","type":"text","marks":[{"type":"code_inline"}]},{"text":". Without one, ","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" and null are written as they are and a missing source leaves the field out; ","type":"text"},{"text":"discardIfEmpty","type":"text","marks":[{"type":"code_inline"}]},{"text":" leaves the field out in all three cases. A fallback for a field that is sometimes empty is therefore a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":", never a ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":", which would replace every record's real value.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Converting a Mapper 1.0 import to 2.0.","type":"text","marks":[{"type":"strong"}]},{"text":" Translate every row in one save and remove ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":": an import that carries both runs only ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ignores the Mapper 1.0 rows, so a partial conversion silently drops whatever wasn't translated. Bare dot-path extracts become ","type":"text"},{"text":"$.","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths; a dotted or ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes nested ","type":"text"},{"text":"object","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" rows, and a ","type":"text"},{"text":"lists[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry an ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" row with a ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"discardIfEmpty","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"conditional.when: extract_not_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" (without it, Mapper 2.0 writes a null source as null); ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the date fields carry over. Mapper 2.0 has no ","type":"text"},{"text":"immutable","type":"text","marks":[{"type":"code_inline"}]},{"text":" and no ","type":"text"},{"text":"lookup_not_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"lookup_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"expression","type":"text","marks":[{"type":"code_inline"}]},{"text":" condition -- tell the user before dropping one. Salesforce, NetSuite DA and Wrapper imports support only Mapper 1.0, so never convert those.","type":"text"}]},{"type":"paragraph","content":[{"text":"NetSuite DA ","type":"text","marks":[{"type":"strong"}]},{"text":"update","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":", ","type":"text","marks":[{"type":"strong"}]},{"text":"addupdate","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" and ","type":"text","marks":[{"type":"strong"}]},{"text":"delete","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" need ","type":"text","marks":[{"type":"strong"}]},{"text":"netsuite_da.internalIdLookup","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" The lookup, not a mapped ","type":"text"},{"text":"internalid","type":"text","marks":[{"type":"code_inline"}]},{"text":", picks the record (on ","type":"text"},{"text":"update","type":"text","marks":[{"type":"code_inline"}]},{"text":" it overrides a mapped one). When the record already carries the internal id, use the expression form ","type":"text"},{"text":"[\"internalid\",\"is\",\"{{{id}}}\"]","type":"text","marks":[{"type":"code_inline"}]},{"text":" rather than dropping the lookup -- as long as every record carries it: on SuiteApp 2.0 an ","type":"text"},{"text":"addupdate","type":"text","marks":[{"type":"code_inline"}]},{"text":" record whose id is empty doesn't fall through to create, because the empty value is searched as ","type":"text"},{"text":"isempty","type":"text","marks":[{"type":"code_inline"}]},{"text":" and can match an existing record.","type":"text"}]},{"type":"paragraph","content":[{"text":"NetSuite lookups are different","type":"text","marks":[{"type":"strong"}]},{"text":" -- they query live NetSuite records using ","type":"text"},{"text":"recordType","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"searchField","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"resultField","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"operator","type":"text","marks":[{"type":"code_inline"}]},{"text":", unless their static ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":" answers first: a source value that is a key of ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":" returns its mapped value without a search, so one lookup can be a static table, a search, or both. Defined in ","type":"text"},{"text":"netsuite_da.lookups[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", referenced by ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":" in field mappings. To discover valid field IDs for ","type":"text"},{"text":"searchField","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"resultField","type":"text","marks":[{"type":"code_inline"}]},{"text":", read the record type's fields from the NetSuite connection's metadata — the returned field IDs are the exact values to use. A lookup's ","type":"text"},{"text":"expression","type":"text","marks":[{"type":"code_inline"}]},{"text":" (which overrides ","type":"text"},{"text":"searchField","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"operator","type":"text","marks":[{"type":"code_inline"}]},{"text":") renders against the whole source record, without ","type":"text"},{"text":"record.","type":"text","marks":[{"type":"code_inline"}]},{"text":"; a lookup used on a sublist row reaches the current line with ","type":"text"},{"text":"\u003carray>[*].\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., ","type":"text"},{"text":"{{{line_items[*].sku}}}","type":"text","marks":[{"type":"code_inline"}]},{"text":"). A bare line field is undefined at the record root, and a helper that receives one fails every record with ","type":"text"},{"text":"INVALID_HANDLE_BAR_EXPRESSION","type":"text","marks":[{"type":"code_inline"}]},{"text":". The mapping preview returns an empty sublist for ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" inside a block-helper argument, so validate those with a run.","type":"text"}]},{"type":"paragraph","content":[{"text":"Salesforce lookups","type":"text","marks":[{"type":"strong"}]},{"text":" follow the same Mapper 1.0 pattern but the lookup structure is simpler.","type":"text"}]},{"type":"paragraph","content":[{"text":"Sublist field discovery:","type":"text","marks":[{"type":"strong"}]},{"text":" For NetSuite ","type":"text"},{"text":"mapping.lists[].generate","type":"text","marks":[{"type":"code_inline"}]},{"text":", the sublist name (e.g., ","type":"text"},{"text":"\"item\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\"addressbook\"","type":"text","marks":[{"type":"code_inline"}]},{"text":") comes from the record type's field metadata on the NetSuite connection — sublists appear as field groups. For a Salesforce child list, read the sObject type's field metadata on the Salesforce connection for its relationship fields and child objects; the ","type":"text"},{"text":"lists[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry's ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the child relationship name. (","type":"text"},{"text":"distributed.relatedLists","type":"text","marks":[{"type":"code_inline"}]},{"text":" belongs to Salesforce listener exports, not imports.)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Schema reference","type":"text"}]},{"type":"paragraph","content":[{"text":"NetSuite Mapper 1.0: see ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the configuring-imports skill's ","type":"text"},{"text":"netsuitedistributed.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/netsuitedistributed.yml","title":null}}]},{"text":" Salesforce Mapper 1.0: see the import-level ","type":"text"},{"text":"mapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the configuring-imports skill's ","type":"text"},{"text":"base.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/base.yml","title":null}}]},{"text":"; its ","type":"text"},{"text":"salesforce.yml","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/references/schemas/salesforce.yml","title":null}}]},{"text":" holds the Salesforce settings and lookups","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Response Mapping Reference (Transformation 1.0)","type":"text"}]},{"type":"paragraph","content":[{"text":"Response mapping extracts fields from a lookup or import API response back into the original record. It lives on the flow's ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry, not on the resource itself -- but it's planned when building the resource. Changing one is an update to the flow, not to the import or lookup export.","type":"text"}]},{"type":"paragraph","content":[{"text":"Two sections, both plain arrays (never wrap them in an object like ","type":"text"},{"text":"\"fields\": {\"type\": [...]}","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- the API accepts that shape but silently erases the step's mappings):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"fields[]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- field-level extract/generate pairs using dot notation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"lists[]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- array mappings with ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" (target array name) and ","type":"text"},{"text":"fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (extract/generate pairs applied per array item)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"For lookup exports:","type":"text","marks":[{"type":"strong"}]},{"text":" the response envelope is ","type":"text"},{"text":"{\"statusCode\": 200, \"data\": [...result records], \"errors\": []}","type":"text","marks":[{"type":"code_inline"}]},{"text":", and every extract starts from ","type":"text"},{"text":"data","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- the lookup's records after its own transform and output filter, so name the transformed fields.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Single result","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"data[0].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or the equivalent ","type":"text"},{"text":"data.0.fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":"). ","type":"text"},{"text":"data[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" is simply the first record returned: rely on it only when the lookup can match at most one record (a unique key such as an id). When it can match several (a name, an email), also map ","type":"text"},{"text":"data.length","type":"text","marks":[{"type":"code_inline"}]},{"text":" and check it's 1 before anything writes on the strength of ","type":"text"},{"text":"data[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No match","type":"text","marks":[{"type":"strong"}]},{"text":" -- a lookup that finds nothing still succeeds: ","type":"text"},{"text":"data","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"data[0].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" merges nothing, and the record continues without the field (","type":"text"},{"text":"proceedOnFailure","type":"text","marks":[{"type":"code_inline"}]},{"text":" doesn't come into it). To act on a miss, map ","type":"text"},{"text":"data.length","type":"text","marks":[{"type":"code_inline"}]},{"text":" (a number, ","type":"text"},{"text":"0","type":"text","marks":[{"type":"code_inline"}]},{"text":" here) or ","type":"text"},{"text":"{{#if data.length}}found{{else}}missing{{/if}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" and filter or branch on it. To fail the record instead, set the lookup's ","type":"text"},{"text":"http.response.successPath","type":"text","marks":[{"type":"code_inline"}]},{"text":" to a path only a match returns (e.g., ","type":"text"},{"text":"results[0].id","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Grouped results","type":"text","marks":[{"type":"strong"}]},{"text":" -- a lookup that groups its results returns each one as an array of rows: ","type":"text"},{"text":"data: [[row, …], …]","type":"text","marks":[{"type":"code_inline"}]},{"text":". ","type":"text"},{"text":"data[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the first group and ","type":"text"},{"text":"data[0][0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" its first row's field; ","type":"text"},{"text":"data[0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" merges nothing. Map the whole group (","type":"text"},{"text":"data[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":") when you need every row. This is verified for HTTP lookups that set ","type":"text"},{"text":"groupByFields","type":"text","marks":[{"type":"code_inline"}]},{"text":"; check a NetSuite saved-search lookup's shape in a test run before relying on it.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multiple results, keep the whole array","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"extract: \"data\"","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"generate: \"\u003carrayField>\"","type":"text","marks":[{"type":"code_inline"}]},{"text":". The record gains an array of the result objects; downstream steps read it with normal JSON array paths (e.g. ","type":"text"},{"text":"$.\u003carrayField>[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" in Mapper 2.0) or fan out over it with one-to-many (","type":"text"},{"text":"pathToMany: \"\u003carrayField>\"","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multiple results, reshaped","type":"text","marks":[{"type":"strong"}]},{"text":" -- a ","type":"text"},{"text":"lists","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry whose field extracts use ","type":"text"},{"text":"data[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":". Produces one array item per result; the ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" marks the array to iterate:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"fields\": [],\n \"lists\": [\n {\n \"generate\": \"matchedOrders\",\n \"fields\": [\n { \"extract\": \"data[*].id\", \"generate\": \"orderId\" },\n { \"extract\": \"data[*].total\", \"generate\": \"amount\" }\n ]\n }\n ]\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" wildcard is only honored inside ","type":"text"},{"text":"lists[].fields[].extract","type":"text","marks":[{"type":"code_inline"}]},{"text":". In a top-level ","type":"text"},{"text":"fields[].extract","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"data[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" (and bare ","type":"text"},{"text":"data[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":") is silently ignored -- the field is never merged onto the record. Bare result-field names (","type":"text"},{"text":"fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" without the ","type":"text"},{"text":"data","type":"text","marks":[{"type":"code_inline"}]},{"text":" prefix) resolve to nothing in either section. On a one-to-many step the response mapping merges into each child, so map plain ","type":"text"},{"text":"fields","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"data[0].price","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"price","type":"text","marks":[{"type":"code_inline"}]},{"text":"); a ","type":"text"},{"text":"lists","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry isn't needed.","type":"text"}]},{"type":"paragraph","content":[{"text":"For imports:","type":"text","marks":[{"type":"strong"}]},{"text":" the envelope carries ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the destination-assigned record ID), ","type":"text"},{"text":"statusCode","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ignored","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"errors","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"_json","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the raw response body). Use ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" for a created record's ID and ","type":"text"},{"text":"_json.fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" for other response fields (e.g., ","type":"text"},{"text":"_json.output.1.content.0.text","type":"text","marks":[{"type":"code_inline"}]},{"text":" for AI model responses). Flow test runs never send imports, so there ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" comes from the saved ","type":"text"},{"text":"mockResponse","type":"text","marks":[{"type":"code_inline"}]},{"text":" or the placeholder response (","type":"text"},{"text":"1234567890","type":"text","marks":[{"type":"code_inline"}]},{"text":", numbered ","type":"text"},{"text":"1234567890-1","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"-2","type":"text","marks":[{"type":"code_inline"}]},{"text":" ... when a page has several records), never from the destination. An import's ","type":"text"},{"text":"responseTransform","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs before the response mapping and replaces ","type":"text"},{"text":"_json","type":"text","marks":[{"type":"code_inline"}]},{"text":" with its output: it takes only v1 rules (","type":"text"},{"text":"expression.version: \"1\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"\"2\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" is refused at save), which keep just the fields they generate, so response-map those names (at the envelope root or under ","type":"text"},{"text":"_json","type":"text","marks":[{"type":"code_inline"}]},{"text":"); ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" still resolves.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Schema reference","type":"text"}]},{"type":"paragraph","content":[{"text":"All response mapping field definitions: ","type":"text"},{"text":"response-mapping.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/response-mapping.yml","title":null}}]}]},{"type":"paragraph","content":[{"text":"\u003c!-- TIER:3 -->","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pre-Submit Checklist","type":"text"}]},{"type":"paragraph","content":[{"text":"Before submitting any mapping configuration, verify:","type":"text"}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Correct system identified","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" for Mapper 2.0, ","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" for Mapper 1.0; never mix (an import holding both runs only ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Lookups defined and referenced","type":"text","marks":[{"type":"strong"}]},{"text":" -- every ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a mapping has a corresponding entry in ","type":"text"},{"text":"lookups[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", and on an HTTP import (or another adaptor that keeps its own copy) the same entry in ","type":"text"},{"text":"http.lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":" or the adaptor's array; a lookup whose miss must fail the record sets ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Full resource PUT","type":"text","marks":[{"type":"strong"}]},{"text":" -- GET the complete resource first, modify only the mapping section, PUT the whole object back, with every existing row you didn't mean to change","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Response mapping on the flow","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"responseMapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" lives on ","type":"text"},{"text":"flow.pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", not on the import resource itself","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Mapper 2.0 and Transformation 2.0:","type":"text","marks":[{"type":"strong"}]}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"A ","type":"text","marks":[{"type":"strong"}]},{"text":"status","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" on every row","type":"text","marks":[{"type":"strong"}]},{"text":" -- the API rejects a row without one, and only ","type":"text"},{"text":"Active","type":"text","marks":[{"type":"code_inline"}]},{"text":" rows run. Give new rows ","type":"text"},{"text":"Active","type":"text","marks":[{"type":"code_inline"}]},{"text":"; when you re-send an existing tree, keep each row's stored ","type":"text"},{"text":"Draft","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"Inactive","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- rewriting it turns back on a row the user parked","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"No ","type":"text","marks":[{"type":"strong"}]},{"text":"date","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" dataType","type":"text","marks":[{"type":"strong"}]},{"text":" -- a date is a ","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":" row with ","type":"text"},{"text":"extractDateFormat","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"generateDateFormat","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"No dot notation in ","type":"text","marks":[{"type":"strong"}]},{"text":"generate","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- use nested ","type":"text"},{"text":"dataType: \"object\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" with child ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Extract paths start from root","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths always reference the top-level input, even in nested mappings; a grouped record's rows are ","type":"text"},{"text":"$[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"$[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":"; a key with dots in its name is bracketed (","type":"text"},{"text":"$['P.O. Number']","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Array mappings have ","type":"text","marks":[{"type":"strong"}]},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- required for all ","type":"text"},{"text":"*array","type":"text","marks":[{"type":"code_inline"}]},{"text":" dataTypes unless the row sets ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":"; an array row's own ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" is never read","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Composite object paths adjusted","type":"text","marks":[{"type":"strong"}]},{"text":" -- when ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" has both ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":", the entry's extract keeps ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"$.line_items[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":") and child extract paths drop it but keep the array's path (","type":"text"},{"text":"$.line_items.sku","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Nothing the save accepts but the run can't use","type":"text","marks":[{"type":"strong"}]},{"text":" -- no ","type":"text"},{"text":"arrayarray","type":"text","marks":[{"type":"code_inline"}]},{"text":", no JSONPath filter or script expression (","type":"text"},{"text":"[?(...)]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"[(...)]","type":"text","marks":[{"type":"code_inline"}]},{"text":"), no indexed ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" extract (","type":"text"},{"text":"$[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and no row with a source but no ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" outside the ","type":"text"},{"text":"rows [ ]","type":"text","marks":[{"type":"code_inline"}]},{"text":" root","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Mapper 1.0:","type":"text","marks":[{"type":"strong"}]}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"The 1.0 dialect","type":"text","marks":[{"type":"strong"}]},{"text":" -- bare dot-path extracts and bare field names in Handlebars (","type":"text"},{"text":"{{email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", never ","type":"text"},{"text":"{{record.email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":"), constants in ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":", a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" for a fallback on a sometimes-empty source, dotted or ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths for nested destinations (flat on NetSuite body fields), and ","type":"text"},{"text":"discardIfEmpty","type":"text","marks":[{"type":"code_inline"}]},{"text":" where an empty source must not overwrite the destination","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"NetSuite DA","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"update","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"addupdate","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"delete","type":"text","marks":[{"type":"code_inline"}]},{"text":" carry ","type":"text"},{"text":"netsuite_da.internalIdLookup","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"mapping.lists[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries leave the legacy ","type":"text"},{"text":"jsonPath","type":"text","marks":[{"type":"code_inline"}]},{"text":" unset","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Gotchas","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Existing imports may use Mapper 1.0 even for HTTP/RDBMS/FTP.","type":"text","marks":[{"type":"strong"}]},{"text":" Mapper 2.0 is the default for new imports, but many older imports across all adaptor types use Mapper 1.0. Always check the existing format before editing -- ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" means 2.0, ","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" means 1.0. Never mix.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract paths always reference the root of the input context.","type":"text","marks":[{"type":"strong"}]},{"text":" Even in deeply nested Mapper 2.0 mappings, ","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths start from the top level (the record in ","type":"text"},{"text":"record","type":"text","marks":[{"type":"code_inline"}]},{"text":" context, or the envelope in ","type":"text"},{"text":"envelope","type":"text","marks":[{"type":"code_inline"}]},{"text":" context), not the current nesting level.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Composite object collapses arrays to single objects.","type":"text","marks":[{"type":"strong"}]},{"text":" When ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" has both ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":", array brackets in the extract path are replaced with single objects in child mapping contexts. ","type":"text"},{"text":"$.orders[*].items[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"$.orders.items.fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" inside the mappings.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Response mapping uses Transformation 1.0 syntax, not 2.0.","type":"text","marks":[{"type":"strong"}]},{"text":" Don't use ","type":"text"},{"text":"rulesTwoDotZero","type":"text","marks":[{"type":"code_inline"}]},{"text":" structure in ","type":"text"},{"text":"responseMapping","type":"text","marks":[{"type":"code_inline"}]},{"text":". It uses simple extract/generate pairs with dot notation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NetSuite lookups query live data unless their ","type":"text","marks":[{"type":"strong"}]},{"text":"map","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" answers.","type":"text","marks":[{"type":"strong"}]},{"text":" A NetSuite import's ","type":"text"},{"text":"netsuite_da.lookups[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry searches NetSuite records at runtime (","type":"text"},{"text":"recordType","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"searchField","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"resultField","type":"text","marks":[{"type":"code_inline"}]},{"text":"), except for a source value that is a key of its static ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":", which returns the mapped value with no search -- so a value wrong in that table stays wrong whatever NetSuite holds.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"generate","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" must not use dot notation in Mapper 2.0.","type":"text","marks":[{"type":"strong"}]},{"text":" Build nested structures with ","type":"text"},{"text":"dataType: \"object\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" and child ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":". ","type":"text"},{"text":"\"generate\": \"customer.name\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" silently creates a field literally named ","type":"text"},{"text":"\"customer.name\"","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"An unnamed top-level ","type":"text","marks":[{"type":"strong"}]},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is the ","type":"text","marks":[{"type":"strong"}]},{"text":"rows [ ]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" structure, not a missing name.","type":"text","marks":[{"type":"strong"}]},{"text":" When the only top-level mapping is an ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" with no ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":", the mapped record is an array, and templates such as ","type":"text"},{"text":"http.body","type":"text","marks":[{"type":"code_inline"}]},{"text":" bind it as ","type":"text"},{"text":"rows","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of ","type":"text"},{"text":"record","type":"text","marks":[{"type":"code_inline"}]},{"text":". Keep it unnamed -- naming it turns the output into an object, and ","type":"text"},{"text":"{{#each rows}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" then renders nothing. Any other row with a source and no ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" is incomplete; the editor won't save it (\"One or more destination field values not entered\"). The API saves one anyway, so a tree written through the API can leave the user's next save in the editor failing until the row is completed or removed.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Transformation 2.0 \"modify\" passes through unmapped fields.","type":"text","marks":[{"type":"strong"}]},{"text":" \"Create\" mode only outputs explicitly mapped fields. Choose based on whether you want a clean slate or surgical edits -- except on a grouped record, where only ","type":"text"},{"text":"create","type":"text","marks":[{"type":"code_inline"}]},{"text":" works and ","type":"text"},{"text":"modify","type":"text","marks":[{"type":"code_inline"}]},{"text":" passes the array through unchanged (","type":"text"},{"text":"Choose the mode","type":"text","marks":[{"type":"link","attrs":{"href":"#2-choose-the-mode","title":null}}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PUT erases omitted fields on the parent resource.","type":"text","marks":[{"type":"strong"}]},{"text":" When updating mappings on an import or transform on an export, always GET the full resource first, modify the mapping/transform section, then PUT the complete object. No mapping, transform, or response-mapping field can be patched on its own, so every change takes this full PUT -- for a response mapping, a PUT of the whole flow. Arrays are saved exactly as sent: a ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":" array (or ","type":"text"},{"text":"rulesTwoDotZero.mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":") holding only the new row deletes every other row, so carry each existing row through, and keep the transform's ","type":"text"},{"text":"mode","type":"text","marks":[{"type":"code_inline"}]},{"text":". A ","type":"text"},{"text":"modify","type":"text","marks":[{"type":"code_inline"}]},{"text":" transform keeps unmapped record fields at run time; it doesn't keep rules you leave out of a save.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"[*]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" in response mapping only works inside ","type":"text","marks":[{"type":"strong"}]},{"text":"lists","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"data[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a top-level ","type":"text"},{"text":"fields[].extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" is silently ignored -- nothing is merged. For multiple lookup results use ","type":"text"},{"text":"extract: \"data\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (whole array) or a ","type":"text"},{"text":"lists","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry with ","type":"text"},{"text":"data[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" extracts.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"An import's mapping never reaches the next step.","type":"text","marks":[{"type":"strong"}]},{"text":" It shapes only the request the import sends: the mapped output, and anything preMap or postMap change, stay in this step. The next step receives the record as it arrived here plus what this step's response mapping (or postResponseMap hook) adds, so to pass a value downstream, response-map it.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Renaming a mapping row can blank a body field.","type":"text","marks":[{"type":"strong"}]},{"text":" On an HTTP import with both a mapping and an ","type":"text"},{"text":"http.body","type":"text","marks":[{"type":"code_inline"}]},{"text":" template, the template renders against the mapped record (unless ","type":"text"},{"text":"http.sendPostMappedData","type":"text","marks":[{"type":"code_inline"}]},{"text":" is false), so ","type":"text"},{"text":"{{record.\u003cname>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" must use the mapping's ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" names. Rename or remove a row and the template's reference renders empty while the record still succeeds. An HTTP lookup export's ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":" work the same way. An HTTP import's own ","type":"text"},{"text":"http.lookups[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" don't: their ","type":"text"},{"text":"body","type":"text","marks":[{"type":"code_inline"}]},{"text":" reads the record's fields at the top level (","type":"text"},{"text":"{{email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", not ","type":"text"},{"text":"{{record.email}}","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"configuring-imports > Gotchas","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#gotchas","title":null}}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSONPath filter and script expressions save but never run.","type":"text","marks":[{"type":"strong"}]},{"text":" The mapper doesn't evaluate them: ","type":"text"},{"text":"$.items[?(@.active)]","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"$.items[(@.length-1)]","type":"text","marks":[{"type":"code_inline"}]},{"text":" passes the save, then every record fails with ","type":"text"},{"text":"Eval [?(expr)] prevented in JSONPath expression.","type":"text","marks":[{"type":"code_inline"}]},{"text":" Pick the elements before the mapping -- in a preMap hook or the source's query -- and map the array as a whole.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Errors","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Error","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Mapping object must have status field present\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a mapping entry","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add a ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"Active","type":"text","marks":[{"type":"code_inline"}]},{"text":" for new rows)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"inactive_parent_active_child","type":"text","marks":[{"type":"code_inline"}]},{"text":" (no message text)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"An ","type":"text"},{"text":"Active","type":"text","marks":[{"type":"code_inline"}]},{"text":" row under an ","type":"text"},{"text":"Inactive","type":"text","marks":[{"type":"code_inline"}]},{"text":" parent","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-enable the parent in the same save, or leave the child non-","type":"text"},{"text":"Active","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Import silently creates field named ","type":"text"},{"text":"\"customer.name\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dot notation in a Mapper 2.0 ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use nested ","type":"text"},{"text":"dataType: \"object\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" with child ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Only the first sublist line is written","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A NetSuite DA ","type":"text"},{"text":"mapping.lists[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry has the legacy ","type":"text"},{"text":"jsonPath","type":"text","marks":[{"type":"code_inline"}]},{"text":" set","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Remove it; drive the lines from ","type":"text"},{"text":"\u003carray>[*].\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":" column extracts","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cannot set properties of undefined (setting '_hasStarNotation')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NetSuite DA: a row's ","type":"text"},{"text":"conditional.lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":" names no lookup on the import (on a sublist row the job can stall instead)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix the name or restore the lookup","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The mapping condition:\"record_updated\" not supported for the operation","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NetSuite DA: ","type":"text"},{"text":"when: record_updated","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an ","type":"text"},{"text":"add","type":"text","marks":[{"type":"code_inline"}]},{"text":" operation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use it only on ","type":"text"},{"text":"addupdate","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"update","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mapped fields missing in output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Using Mapper 2.0 syntax on a Mapper 1.0 import (or vice versa)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check existing format: ","type":"text"},{"text":"mappings[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" = 2.0, ","type":"text"},{"text":"mapping.fields[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" = 1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extract returns ","type":"text"},{"text":"null","type":"text","marks":[{"type":"code_inline"}]},{"text":" in nested mapping","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extract path relative to nesting level","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extract paths always start from root (","type":"text"},{"text":"$","type":"text","marks":[{"type":"code_inline"}]},{"text":"), not the current level","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mapped fields empty (the body goes out as ","type":"text"},{"text":"{}","type":"text","marks":[{"type":"code_inline"}]},{"text":"), yet the record succeeds","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"$.field","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a grouped record (an array of rows)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Address the rows as ","type":"text"},{"text":"$[0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"$[*].field","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A Transformation 2.0 transform on a grouping export changes nothing, or its records come out ","type":"text"},{"text":"{}","type":"text","marks":[{"type":"code_inline"}]},{"text":", with no error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"modify","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, or ","type":"text"},{"text":"$.field","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths, on a grouped record (an array of rows)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"create","type":"text","marks":[{"type":"code_inline"}]},{"text":" mode, with header fields from ","type":"text"},{"text":"$[0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the rows from a ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" over ","type":"text"},{"text":"$[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"Choose the mode","type":"text","marks":[{"type":"link","attrs":{"href":"#2-choose-the-mode","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Destination datatype is a required field\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dataType","type":"text","marks":[{"type":"code_inline"}]},{"text":" missing, or a value that isn't a Mapper 2.0 type, such as ","type":"text"},{"text":"date","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"arrayarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" (which saves, then fails here at run time)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use a listed type; a date is a ","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":" with date formats","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Array output is empty","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A primitive-array row reading its own ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries, or an indexed entry ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"$[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add ","type":"text"},{"text":"buildArrayHelper[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries that iterate (","type":"text"},{"text":"$.items[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":") or construct (child ","type":"text"},{"text":"mappings","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"buildArrayHelper\" or \"hardCodedValue\" must be present for object array datatype.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"An ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" row with only its own ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (JSONPath, Handlebars or a literal)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Move the source into a ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry, or set a constant as the row's ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"extract only supports json path or handlebars expression","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A literal in a ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set the constant as the row's ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003cgenerate>: You can't map number (source) to objectarray (destination)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"An ","type":"text"},{"text":"objectarray","type":"text","marks":[{"type":"code_inline"}]},{"text":" row's ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry sets ","type":"text"},{"text":"sourceDataType: \"number\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the save refuses it)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Leave ","type":"text"},{"text":"sourceDataType","type":"text","marks":[{"type":"code_inline"}]},{"text":" off an entry that builds objects","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"You can't map string (source) to object (destination)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A Handlebars ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an ","type":"text"},{"text":"object","type":"text","marks":[{"type":"code_inline"}]},{"text":" row rendered something other than a JSON object","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Render a JSON object, or build the object from child rows","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003cgenerate>: Source field has no value","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"extract_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fail_record_when_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a row whose source is null, missing or ","type":"text"},{"text":"\"\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Expected when the field is required; otherwise use ","type":"text"},{"text":"extract_not_empty","type":"text","marks":[{"type":"code_inline"}]},{"text":" or a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Eval [?(expr)] prevented in JSONPath expression.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A JSONPath filter or script expression in an ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (gotcha 13)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Select the elements before the mapping, and map the whole array","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The maximum size of the field: mappings.\u003cn>.extract in the Import document should be 1024 characters.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"An ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" longer than 1024 characters","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Split it into nested rows, a lookup, or a preMap hook","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unable to find lookup in mapping configs - \u003cname>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A row's ","type":"text"},{"text":"lookupName","type":"text","marks":[{"type":"code_inline"}]},{"text":" names no lookup the import keeps -- on an HTTP import, often one saved only in the top-level ","type":"text"},{"text":"lookups","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Define it in ","type":"text"},{"text":"http.lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or the adaptor's array) and in the top-level ","type":"text"},{"text":"lookups","type":"text","marks":[{"type":"code_inline"}]},{"text":", or fix the name","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Could not find value for static lookup: \u003cname> {Lookup Key: \u003ckey>} ...","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The value the row extracts isn't a key of the static ","type":"text"},{"text":"map","type":"text","marks":[{"type":"code_inline"}]},{"text":", and the lookup sets ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add the value as a key exactly as the message quotes it (case, spacing and suffixes count), and send the whole map with every existing key -- a save replaces it. Offer a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" only where one fixed fallback is right for every unmapped value: with it (and ","type":"text"},{"text":"allowFailures","type":"text","marks":[{"type":"code_inline"}]},{"text":" true or left out), each unmapped value goes out as the default, with a 200 and no error","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 ","type":"text"},{"text":"Lookups with default set can not have allowFailures explicitly set to false.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The save sets a lookup's ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" together with ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pick one: ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" and no ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" fails a miss; a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":", with ","type":"text"},{"text":"allowFailures","type":"text","marks":[{"type":"code_inline"}]},{"text":" true or left out, sends it","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"lookupcache_evaluation_failed","type":"text","marks":[{"type":"code_inline"}]},{"text":": \"\u003cfield>: The lookup cache could not be evaluated. Check the value in the key '\u003ckey>', and try again.\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The key isn't in the cache, and the lookup sets ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Load the key into the cache, or fix the value the row extracts, then retry the failed records","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"batch_lookup_evaluation_failed","type":"text","marks":[{"type":"code_inline"}]},{"text":": \"\u003cfield>: The batch lookup could not be evaluated. Check the value in the key '\u003ckey>', and try again.\" (source ","type":"text"},{"text":"internal","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A live HTTP lookup with ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" got nothing for that key: the search matched nothing, or the lookup's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" resolves nothing (","type":"text"},{"text":"$.","type":"text","marks":[{"type":"code_inline"}]},{"text":" prefix, ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":", a dot path through an array)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix the ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"results[0].id","type":"text","marks":[{"type":"code_inline"}]},{"text":", no ","type":"text"},{"text":"$.","type":"text","marks":[{"type":"code_inline"}]},{"text":") or the search, then retry the failed records","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A looked-up field is missing from the request, with no error","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The key isn't in the map or cache, or an HTTP lookup's ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" resolves nothing; the lookup leaves ","type":"text"},{"text":"allowFailures","type":"text","marks":[{"type":"code_inline"}]},{"text":" out, and there's no ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add the key or fix the ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":", and set ","type":"text"},{"text":"allowFailures: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" so the next miss fails the record; ","type":"text"},{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":" with a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]},{"text":" only where one fallback fits every miss","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"non_parsable_string","type":"text","marks":[{"type":"code_inline"}]},{"text":": \"\u003cgenerate>: String contains alpha characters\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A ","type":"text"},{"text":"number","type":"text","marks":[{"type":"code_inline"}]},{"text":" row's source holds letters or a thousands separator (","type":"text"},{"text":"\"1,250.00\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Map a non-numeric field as ","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":"; strip separators before the mapping (in the source or a preMap hook)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A conditional field holds ","type":"text"},{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of the value it should choose","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"{{#eq a b}}...{{/eq}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" used as a block: ","type":"text"},{"text":"eq","type":"text","marks":[{"type":"code_inline"}]},{"text":" returns a boolean","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"{{#if (eq a b)}}...{{else}}...{{/if}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"{{#compare a \"==\" b}}...{{/compare}}","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cannot_evaluate_handlebars","type":"text","marks":[{"type":"code_inline"}]},{"text":" on every record","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"An ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" uses a helper the platform doesn't have (","type":"text"},{"text":"ifEquals","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ifCond","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"compare","type":"text","marks":[{"type":"code_inline"}]},{"text":" without its operator, or an operator inside ","type":"text"},{"text":"{{#if}}","type":"text","marks":[{"type":"code_inline"}]},{"text":"; a save accepts these","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rewrite it with ","type":"text"},{"text":"{{#if (eq a b)}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"{{#compare a \"==\" b}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" (step 7)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A Mapper 1.0 Handlebars field is never written, though the record has the value","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"{{record.\u003cfield>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"{{data.\u003cfield>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a Mapper 1.0 ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":", which renders nothing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Name the field bare: ","type":"text"},{"text":"{{\u003cfield>}}","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Every record gets the same value in a field that should only be filled when empty","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A Mapper 1.0 ","type":"text"},{"text":"hardCodedValue","type":"text","marks":[{"type":"code_inline"}]},{"text":" used as a fallback","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Map the source with a ","type":"text"},{"text":"default","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A column whose name has dots maps nothing (","type":"text"},{"text":"$.P.O. Number","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and the record succeeds","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The dots are read as a nested path","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use bracket notation: ","type":"text"},{"text":"$['P.O. Number']","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Response mapping not applied","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"responseMapping","type":"text","marks":[{"type":"code_inline"}]},{"text":" placed on the import resource","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Move to ","type":"text"},{"text":"flow.pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry for that import","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lookup response field empty / never merged","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"data[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or a bare field name) in a top-level ","type":"text"},{"text":"fields[].extract","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"data[0].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" for one result; ","type":"text"},{"text":"extract: \"data\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" or a ","type":"text"},{"text":"lists","type":"text","marks":[{"type":"code_inline"}]},{"text":" entry with ","type":"text"},{"text":"data[*].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" extracts for many","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"data[0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" merges nothing from a grouping lookup","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Grouped results nest one level deeper (","type":"text"},{"text":"data: [[row, …], …]","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"data[0][0].field","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the first row, or map ","type":"text"},{"text":"data[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the whole group","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A value mapped on an import is missing in the next step","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The import's mapping shapes only its own request","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Response-map it from this step's response, or add it in a postResponseMap hook","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A body field goes out empty after a mapping edit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"http.body","type":"text","marks":[{"type":"code_inline"}]},{"text":" names a ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" that was renamed or removed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Point ","type":"text"},{"text":"{{record.\u003cname>}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" at the current ","type":"text"},{"text":"generate","type":"text","marks":[{"type":"code_inline"}]},{"text":" name","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Composite object paths return wrong data","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" brackets still in child extract paths (they map nothing), an element-relative child path (","type":"text"},{"text":"$.sku","type":"text","marks":[{"type":"code_inline"}]},{"text":" reads the record root), or no ","type":"text"},{"text":"[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":" on the entry's own extract (empty elements)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Entry extract ","type":"text"},{"text":"$.line_items[*]","type":"text","marks":[{"type":"code_inline"}]},{"text":"; child extracts ","type":"text"},{"text":"$.line_items.\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- arrays collapse to single objects inside ","type":"text"},{"text":"buildArrayHelper","type":"text","marks":[{"type":"code_inline"}]},{"text":" mappings","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Date values malformed in output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing date format configuration","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"extractDateFormat","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"generateDateFormat","type":"text","marks":[{"type":"code_inline"}]},{"text":" on date mappings","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PUT overwrites entire resource","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Partial JSON sent without GET first","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Always GET full resource, modify mapping section, PUT complete object","type":"text"}]}]}]}]}]},"metadata":{"date":"2026-11-02","name":"writing-mappings","author":"@skillopedia","source":{"stars":4,"repo_name":"ai","origin_url":"https://github.com/celigo/ai/blob/HEAD/skills/writing-mappings/SKILL.md","repo_owner":"celigo","body_sha256":"be06b845fdd8e9c0d3c3600f948a26cd637990492027e668cf312dca84bc87f8","cluster_key":"989c470f5bbeb72d239b64939ca965a52d31c56cb6b5b8b5fdbf975fec7af7e4","clean_bundle":{"format":"clean-skill-bundle-v1","source":"celigo/ai/skills/writing-mappings/SKILL.md","text_bytes":108572,"source_commit":"2f3853ed93eece259b8c78ee74ef19035dfae052","skills_sh_rank":6180,"attachment_count":6,"text_attachments":6,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/writing-mappings/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"integrations-apis","import_tag":"skills-sh-top20k-2026-11","description":"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 DA/Salesforce). Use when editing mappings[], transform{}, responseMapping, or lookup configurations."}},"renderedAt":1793671663091}

<!-- 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 , the source export's or a recent run's records, or a failing record's retry data -- before writing an . 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…