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.
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 exported functions, each wired independently to different hook points
Used across flows, APIs, and tools.
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.
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[]
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.
Routing and Handlers
Hook
Runs on
When
Input
Must return
branching
Router
Per-record routing decision
options.record
,
settings
number[]
(branch indices, e.g.,
[0, 2]
)
handleRequest
API resource
Incoming HTTP request (script-mode API)
options.method
,
headers
,
queryString
,
body
,
rawBody
{ statusCode, headers?, body }
contentBasedFlowRouter
AS2 connection
EDI message routing
options.httpHeaders
,
mimeHeaders
,
rawMessageBody
{ _flowId, _exportId }
Quick Reference
Hook Point Decision Matrix
When you need to...
Use hook
Configured on
Input / Output
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)
Filter records entering a lookup export
input_filter
Export (lookup)
Receives single record, returns boolean (true = include)
Reshape records before mapping
transform
Export or import
Receives single record, returns transformed record
Transform records before field mapping
preMap
Import
Receives unmapped records array, returns array (same length)
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 connection
Receives HTTP/MIME headers + raw body, returns
{ _flowId, _exportId }
Minimum Required Fields
A script resource needs only two fields:
name
-- descriptive name (convention:
<System> - <step> - <hookType>
, e.g.,
"Salesforce - getBatchRecords - postResponseMap"
)
content
-- the JavaScript source code as a string
See references/schemas/request.yml for the full create/update schema.
Related Skills
configuring-exports > Quick Reference -- export configuration, where
preSavePage
,
filter
,
transform
, and
input_filter
hooks are wired
configuring-imports > Quick Reference -- import configuration, where
preMap
,
postMap
,
postSubmit
, and
postAggregate
hooks are wired
building-flows > How to Build a Flow -- flow construction, where
postResponseMap
and
branching
hooks are wired
writing-handlebars > Quick Reference -- Handlebars expressions for dynamic values in scripts and hook configurations
<!-- TIER:2 -->
Common Options Available to All Hooks
Most hooks receive these context fields in
options
:
_flowId
,
_integrationId
,
_apiId
,
_parentIntegrationId
-- execution context IDs
_exportId
or
_importId
-- the step's resource ID
_connectionId
-- the connection in use
settings
-- custom settings in scope for the resource
testMode
-- boolean, whether running in test/preview mode
job
-- the current job object
Function Point Categories
Scripts run at twelve function points, grouped into four categories. The Hook Points tables above give each one's input/output contract; this is the mental model for which kind of point you're wiring and whether a non-script alternative exists.
Step-level pipeline hooks (on the export or import) --
preSavePage
,
preMap
,
postMap
,
postSubmit
,
postAggregate
Parent-level response hook (on the flow/API/tool
pageProcessors[]
entry, not the step) --
postResponseMap
Script-mode replacements for declarative slots --
filter
,
input_filter
,
transform
,
branching
Resource-specific function points --
contentBasedFlowRouter
(on an AS2 connection) and
handleRequest
(on a script-mode API)
Script-only points have no declarative equivalent:
postSubmit
,
postResponseMap
,
postAggregate
,
contentBasedFlowRouter
, and
handleRequest
. On those slots a script is the only option. The four script-mode slots (
filter
,
input_filter
,
transform
,
branching
) each hold either a declarative rule tree or a script -- never both -- so prefer the declarative path there unless the logic genuinely can't be expressed as rules (see Declarative vs Script Mode).
Declarative vs Script Mode
The four mode-switchable slots --
filter
,
input_filter
,
transform
, and
branching
-- hold a declarative rule tree or a script reference at any one moment, not both. Because the slot's contents change, switching modes is a two-part operation.
From script mode to declarative mode (the common direction -- prototype with a script, then clean up):
Clear the script from the slot. The slot reverts to declarative mode by default.
Author the declarative rule for that slot (rules-engine filter, Mapper 2.0 transform, or router input-filter rule).
From declarative mode to script mode (rarer -- the rules engine couldn't express what you need):
Wire a script into the slot. The declarative rules already there are replaced by the script reference automatically.
Wiring a script and clearing it are mirror operations on the same slot. Recognize the mode-swap in phrasing like "switch the filter to rules", "convert this transform back to expressions", or "use a script for this filter instead of rules".
Filter, transform, and output filter all have expression-based alternatives. Expressions are simpler to maintain and don't require a script resource. Use a script only when you need:
Multi-step logic or loops
Cross-record calculations (totals, deduplication)
External API calls via
integrator-api
Error handling with retry data
Access to
preMapData
alongside
postMapData
3. Check for existing scripts in the account
bash
celigo scripts list
celigo scripts get <id># content is only returned on individual GET
4. Create the script resource
Build the script with the correct function name matching the hook point. A single script can contain multiple functions.
See references/schemas/request.yml for the create/update schema and references/schemas/response.yml for the response shape.
Key fields:
name
-- descriptive name (convention:
<System> - <step> - <hookType>
, e.g.,
"Salesforce - getBatchRecords - postResponseMap"
)
content
-- the JavaScript source code
5. Wire the script to the resource
Wiring depends on the hook type:
Hook
Wiring pattern
Where
preSavePage
,
preMap
,
postMap
,
postSubmit
,
postAggregate
hooks.{hookType}: { _scriptId, function }
Export or import resource
filter
,
input_filter
,
transform
{field}: { type: "script", script: { _scriptId, function } }
Export or import resource
postResponseMap
hooks.postResponseMap: { _scriptId, function }
Flow
pageProcessors[]
entry
branching
routeRecordsUsing: "script"
+ script reference
Router in flow
handleRequest
script: { _scriptId, function }
+
type: "script"
API resource
contentBasedFlowRouter
as2.contentBasedFlowRouter: { _scriptId, function }
AS2 connection
Hook-based attachment (preSavePage, preMap, etc.) is additive -- adding a hook doesn't remove existing config. Replace-based attachment (filter, transform) replaces the existing filter/transform expression.
6. Test and iterate
bash
# Enable debug logging on the scriptceligo scripts enable-debug <script-id># Run the flow or API that triggers the scriptceligo flows run <flow-id>-y# Check debug logsceligo scripts debug-logs <script-id>--since30# Check execution logsceligo scripts debug-logs <script-id>--level error --limit20# Disable debug when doneceligo scripts disable-debug <script-id>
Available Modules
Scripts can import three built-in modules:
integrator-api
Call Celigo APIs from within the script -- run exports, read connections, trigger imports.
Script logic is runtime-dependent -- it only works against the specific shape of data it handles -- so a script is written and validated against a sample input. The sample comes from the step's recent runs, a
test
/
run
capture on the parent flow, or a JSON example you supply. A script written without sample data is written blind.
Treat authoring as a loop, not a one-shot:
Generate or edit the function against the sample input.
Run it against that sample.
Check the output for errors or obviously-wrong results.
Iterate -- refine and re-run until it passes.
A script that fails on the first pass isn't a failure; it's the first turn of the loop -- the runtime error and the code are both visible, so the next pass is informed by what went wrong. When no sample is available (the step has never run and no parent provided records), supply a JSON example before writing the script; a user-supplied sample plays the same validation role as captured runtime data.
Execution Logs and the Debug Window
Scripts write to a per-script execution log using standard
console
methods. What gets captured depends on the level:
console.error()
,
console.warn()
,
console.info()
, and
console.log()
are always captured -- no setup, no toggle (
info
and
log
are equivalent).
console.debug()
is gated: its output is persisted only while a time-bounded debug window is open on the script. When the window is closed,
console.debug()
still runs but its output is dropped.
"Debugging a script" here means exactly this -- turning on
console.debug()
capture for a window. It is not breakpoint-style debugging; there is no pausing or stepping through code. Open a window only when you need
console.debug()
output; for "why did this fail?" / "what errors happened?", the always-captured error/warn/info/log entries are usually enough.
The debug window is time-bounded and expires automatically -- it defaults to a short window (15 minutes) and is opened with
Because one script can carry many functions across many hook sites, an unfiltered log stream interleaves entries from every consumer. Filter aggressively when reading -- by flow (
--flow-id
), by time (
--since
/
--start-date
/
--end-date
), and by level (
--level
). The practical query is "logs for this script, in this flow, on this step, during this window."
<!-- TIER:3 -->
Pre-Submit Checklist
Before creating or updating a script, verify:
Hook point is correct -- the function name matches the hook type being wired (e.g.,
preSavePage
function for a
hooks.preSavePage
reference)
Return value matches contract -- batch hooks (
preMap
,
postMap
,
postSubmit
,
postResponseMap
) return arrays that match the input array length exactly
Error handling uses return pattern, not throw -- per-record errors use
{ errors: [...] }
return values, not thrown exceptions (which fail the entire page)
Expression alternative considered -- filter, transform, and output filter can use expressions; only use a script when expressions cannot handle the logic
content
field is included on PUT -- omitting
content
on update erases the code; always GET first, modify, then PUT
Debug mode is disabled after testing --
celigo scripts disable-debug <id>
to avoid log noise in production
Gotchas
Array length contracts are strict.
preMap
,
postMap
, and
postResponseMap
return arrays MUST match the input array length. Returning fewer or more elements fails the entire page silently or with cryptic errors.
abort: true
stops pagination, not the flow. In
preSavePage
, setting
abort: true
tells the export to stop generating new pages. It does NOT stop the flow or cancel processing of the current page's records.
Script
content
is not returned in list responses.
celigo scripts list
shows metadata only. You must
celigo scripts get <id>
to see the actual JavaScript code.
PUT erases
content
if omitted. Always GET the script first, modify, then PUT the complete object. The
set
command handles this automatically.
One script resource can contain multiple functions. A single script with both
preSavePage
and
preMap
functions can be wired to different resources by specifying the
function
name in each hook reference.
Throwing an exception fails the entire page. In batch hooks (preSavePage, preMap, postMap, postSubmit), an unhandled exception fails ALL records on that page, not just one. Use the error return pattern (
{ errors: [...] }
) for per-record errors.
postResponseMap
lives on the flow, not the resource. The hook is configured on the
pageProcessors[]
entry in the flow/API/tool, even though it processes export or import response data.
filter/transform scripts replace expression-based alternatives. Wiring a script filter replaces any existing expression filter. They cannot coexist on the same resource.
console.log()
output goes to script logs, not stdout. Use
celigo scripts debug-logs
to see output. Logs require debug mode to be enabled for debug-level messages.
Only
console.debug()
needs the debug window.
error
/
warn
/
info
/
log
are always captured;
debug
output is persisted only while a time-bounded debug window is open (
celigo scripts enable-debug
). A closed window silently drops
console.debug()
output.
Shared-script logs interleave across hook sites. One script can hold many functions used by many exports/imports, so its log stream mixes entries from every consumer. Filter by flow, level, and time when reading; each entry's
functionType
and
_resourceId
identify where it came from.
Clearing a script-mode filter/transform reverts the slot to declarative mode. The four mode-switchable slots (
filter
,
input_filter
,
transform
,
branching
) hold a rule tree or a script, never both -- removing the script drops the slot back to rules, and wiring a script replaces the rules.
Common Errors
Error / Symptom
Cause
Fix
"The number of elements in the return value must match the input"
Batch hook return array length differs from input
Ensure return array has exactly
data.length
(preMap) or
postMapData.length
(postMap) elements; use
{}
for skipped records
All records on a page fail with no per-record detail
Unhandled exception thrown in batch hook
Wrap logic in try/catch; return
{ errors: [...] }
per record instead of throwing
Script content is empty after update
PUT omitted the
content
field
Always GET first, modify, then PUT the complete object (or use
celigo scripts set
)
abort: true
set but flow keeps running
abort
only stops pagination; current page still processes
This is expected behavior; use error returns or filter to skip individual records
Script not executing / no logs
Script not wired to any resource, or debug mode not enabled
Verify
_scriptId
+
function
reference on the export/import/flow; enable debug with
celigo scripts enable-debug
"Function not found" or similar
function
name in hook reference doesn't match an exported function in the script
Check the function name matches exactly (case-sensitive) between the hook config and the script's
export
Filter always returns all/no records
Filter function returns truthy/falsy value instead of strict boolean
Return explicit
true
or
false
; avoid returning objects or undefined
postResponseMap
not firing
Hook wired on the import/export instead of the flow's
pageProcessors[]
entry
Move the hook config to the
pageProcessors[]
entry in the flow, not the resource
console.debug()
lines missing from logs
No debug window was open while the script ran
Open a window first (
celigo scripts enable-debug <id>
), then reproduce; error/warn/info/log don't require it
Log stream is a confusing mix of unrelated entries
Script is shared across many hooks/flows and the query is unfiltered