writing-scripts

Write Celigo JavaScript hook scripts -- preSavePage, preMap, postMap, postSubmit, postResponseMap, filter, transform, branching, handleRequest. Use when creating or editing scripts, choosing the right hook point, understanding input/output data shapes, or debugging script behavior.

By celigo · 1,073 installs

npx skills add celigo/ai --skill writing-scripts

Source repository · Upstream listing

<! TIER:1 Writing Scripts A script is a JavaScript function that runs at a specific hook point in the Celigo data pipeline. Scripts handle logic that expressions, filters, and visual mappings cannot complex conditionals, cross record calculations, API calls within the pipeline, and custom routing. Concerns when writing a script: Choosing the right hook point which function type matches what you're trying to accomplish Input/output contracts what options contains and what the function must return (array length rules are strict) Expression alternative filter, transform, and output filter have expression based alternatives that don't require a script; prefer expressions when possible Available modules scripts can import three built in modules: integrator api (call Celigo APIs), dayjs (date/time manipulation), and sjcl (Stanford JavaScript Crypto Library for hashing/encryption) One script, many functions a single script resource can contain multiple functions, each wired independently to different hook points Used across flows, APIs, and tools. Diagnosing a script is read only. Editing it, rewiring it, and running a flow to test it all write to the account or to the connected systems, and an edit to a shared script reaches every hook that names it make those changes when the user asked for them, not as part of explaining a failure. A question about why something fails or how to fix it asks for the cause and the change you propose; make the change once the user says to. Hook Points Every script function runs at a specific point in the pipeline. Choose based on when you need to act and what data you need access to. Data Pipeline Hooks Hook Runs on When Input Must return preParse File export After the file is downloaded, before it's parsed options.preParseData (the raw file text), fileMeta { preParseData, fileMeta } both, changed or not, for the parser to read preSavePage Export (source or lookup) Per page, after the export's transform and output filter options.data[] , errors[] , pageIndex , files[] , retryData{} { data[], errors[], abort, newErrorsAndRetryData[] } preMap Import Before field mapping options.data[] (unmapped records) Array matching data.length , in order: { data: <record } to send it, { errors } to fail it, or {} to skip it a bare record is skipped too postMap Import After field mapping, before submit options.preMapData[] , postMapData[] Array matching postMapData.length : { data } , { errors } , or {} to skip postSubmit Import After the destination responds options.preMapData[] , postMapData[] , responseData[] (one per record, below) responseData[] (same length, modified); the response mapping reads the json you return postAggregate Import (a file import that aggregates records) After the aggregated file is uploaded options.postAggregateData: { success, json, code, message, source } void preSavePage returns an object, never a bare array. Return { data, errors, abort, newErrorsAndRetryData } , passing the page's errors through. A hook that returns the array itself return options.data , or a filtered copy drops the page without an error: the run completes with 0 records, 0 errors and 0 pages (live verified 2026 10 21). A throw in preSavePage fails its whole page. When the hook throws on one record, every record on that page becomes a script error (source pre save page hook , each with its own retry data); the other pages run and the run still completes. The error count is therefore whole pages, not bad records: each failed page may hold a single bad record, and its errors share one message and one timestamp. postSubmit gets one response per submitted record , in order. Each options.responseData[i] carries: statusCode 200 on success, 422 for a data error errors[] { code, message, source } entries, empty on success ignored true when the record was skipped id the id the destination returned (in a test run, the saved mockResponse 's id or the placeholder's 1234567890 1 ; absent on a record that failed before it was sent) json the destination's parsed response body, where its own error detail sits ( headers holds the response headers) Return the same array, same length, with entries edited for example, an error message enriched from the body: A postSubmit error comes after the write. An error with source post submit hook belongs to a record the destination already accepted: the hook threw while handling its response. A retry sends the record again a duplicate, unless the destination refuses repeats and with the hook unchanged it fails the same way (live verified 2026 10 22). So when asked to retry such errors, say this before doing it: fix the hook first, and retry only records the destination doesn't already hold. preMap and postMap shape only the request this import sends. Later steps never see their changes: the next step receives the record as it entered this import, plus what this step's response mapping merges. To give later steps a value, compute it where they can see it preSavePage on the source export (every step sees it), a postResponseMap hook on this step (the steps after it see it), or the later import's own preMap or map it from this step's response with response mapping. An element that carries both data and errors isn't a warning: the record is sent, but it gets the error (with retry data), counts as failed and goes no further so retrying that error sends it again. preMap returns wrappers, not records. Return one element per input record, in order: { data: record } sends it, { errors: [{ code, message }] } fails it. An element without data {} , or the record itself, as in options.data.map((record) = { record.total = sum; return record }) skips that record silently: nothing is sent, no error is raised, and the record is counted as ignored ( ignored: true , stage preMapHook ). A return with more or fewer elements than options.data fails every record on the page with invalid extension response ("Extension result doesn't contain the same number of elements as the request object. Expected 2, got 1."), source pre map hook (live verified 2026 10 22). Amounts often arrive as strings, so convert them before adding: sum + line.amount concatenates ( "0120.0045.50" ), which a number mapping refuses with non parsable string ("String contains alpha characters"), though a one line order ( "0640.00" ) still parses. Write sum + Number(line.amount) ; parseInt drops the cents. Record Level Processors (on export or import) Hook When Input Must return filter Per record: an export's output filter (it sees the transformed record), or an import's input filter options.record boolean (true = process) inputFilter Per record on lookup exports options.record boolean (true = run the lookup; false = skip it, and the record continues without the lookup's data, counted as ignored) transform Per record reshaping of what an export returns source records, or a lookup's results (exports only) options.record Transformed record Only filter runs on both exports and imports; inputFilter (lookups) and transform are export only. Imports have no transform their response side slot is responseTransform , which reshapes the destination's response before response mapping. filter and transform have expression based alternatives. Only use a script when the logic is too complex for an expression (multi field conditionals, date math, external lookups). Flow Level Hook Hook Runs on When Input Must return postResponseMap Page processor (flow/API/tool) After response mapping merges results options.postResponseMapData[] , responseData[] per record, an import's response as postSubmit returned it, or a lookup's { data: [<results ], errors } . Records the import failed are left out unless the step has proceedOnFailure: true postResponseMapData[] (same length) Configured on the flow's pageProcessors[] entry, not on the export/import. Plan this hook when building the resource, but wire it at the flow level. postResponseMap sees failed records only with proceedOnFailure . By default a record the import failed is in neither array, so every responseData[i] belongs to a record the destination accepted. With proceedOnFailure: true on the page processor, failed records are included: the record lacks the fields response mapping would have added, and its responseData entry has statusCode 422 and an errors that is an object keyed "0" , "1" , … rather than an array, so errors.map throws there. A successful entry has no errors at all. Read them with Object.values(response.errors {}) (live verified 2026 10 22). Routing and Handlers Hook Runs on When Input Must return branching Router Per record routing decision options.record , settings number[] of zero based branch indices: one index ( [0] ) under first matching branch , where more than one fails the record with invalid script routing ; several ( [0, 2] ) only under all matching branches handleRequest API resource Incoming HTTP request (script mode API) options.method , headers (no Authorization ), queryString (the parsed query), body (a JSON or form body; absent otherwise), rawBody (a string). No pathParams , queryParams or sandbox { statusCode, headers?, body } , sent as returned (an array body too); no statusCode , or a null body , answers the caller 501, and a thrown error 422 contentBasedFlowRouter AS2 or VAN connection EDI message routing options.httpHeaders , mimeHeaders , rawMessageBody (AS2) { flowId, exportId } Quick Reference Hook Point Decision Matrix When you need to... Use hook Configured on Input / Output Fix a file's raw text before it's parsed (a header row, a preamble, a format) preParse Export (file) Receives the raw file text and its metadata, returns both Transform or filter a batch after retrieval preSavePage Export Receives pages of records, returns pages (with optional errors) Filter individual records before processing filter Export or import Receives single record, returns boolean (true = keep) Skip a lookup for some records inputFilter Export (lookup) Receives single record, returns boolean (false = no lookup call; the record goes on unenriched) Reshape the records an export or lookup returns transform Export (source or lookup) Receives single record, returns transformed record Change what an import sends, before field mapping preMap Import Receives unmapped records array, returns array (same length) Change what an import sends, after field mapping postMap Import Receives pre map + post map arrays, returns array (same length) Process API responses after submission postSubmit Import Receives pre map, post map, and response arrays, returns response array Handle results after file aggregation postAggregate Import (file) Receives aggregation result, returns void Post response processing (merge lookup/import results) postResponseMap Flow pageProcessors[] entry Receives merged records + response data, returns merged records (same length) Route records to branches branching Router in flow/tool Receives single record + settings, returns branch indices array Handle incoming HTTP requests (script mode API) handleRequest API resource Receives method, headers, query, body; returns { statusCode, headers?, body } Route EDI messages to flows contentBasedFlowRouter AS2 or VAN connection Receives HTTP/MIME headers + ra