Overview
Every workflow function has access to a built-in utils object — a set of helper functions provided automatically by the workflow engine.
In this article, we'll be covering what utils contains, how each function works, and the most common patterns and pitfalls to be aware of.
Understand what utils is
utils is available in every workflow function alongside state, secrets, and variables. You do not import it, install it, or configure it — the workflow engine injects it automatically at execution time.
({ secrets, state, utils, variables }) = {
return { id: utils.parseGlobalId(state.input.accountId).id };
}New to workflow functions? Think of utils as a set of built-in tools that every function gets automatically — similar to how a spreadsheet gives you access to functions like SUM() or VLOOKUP() without any setup.
utils is available in all of the following function contexts:
- TypeScript code nodes
- Input mappers on any node type
- Output mappers on any node type
- Condition functions
- While loop conditions and iteration output mappers
- Parallel loop iteration output mappers
- Workflow output mappers
- Event skip execution mappers
- Webhook skip execution and synchronous result mappers
Reference available functions
| Function | What it does | Async? |
|---|---|---|
utils.fail(message, code, payload?) | Immediately fails the current node with a structured error | No |
utils.parseGlobalId(globalId) | Converts a gaiia Global ID (Base-58 string) into { id, type } | No |
utils.serializeGlobalId({ id, type }) | Converts a raw UUID + type back into a Global ID string | No |
utils.packages.dayjs_v1 | Full dayjs library (v1) with utc and timezone plugins pre-loaded | N/A |
Use utils.fail() to fail a node intentionally
Not every failure is a crash. Sometimes you want a node to fail — because data returned from an API is wrong, a required record doesn't exist, or a validation condition isn't met. utils.fail() lets you express that as a deliberate, structured failure rather than an unhandled exception.
For example: your workflow checks whether a customer account exists before activating service. If the account isn't found, utils.fail() lets you stop cleanly with a message like "Account not found" and an error code your team can act on — instead of an opaque crash.
Signature
utils.fail(message: string, code: string, payload?: any): never
message— A human-readable description of what went wrong. Appears in the execution view.code— A machine-readable string identifier for the error type (e.g.ACCOUNT_NOT_FOUND,MISSING_CREDENTIALS). Used by retry strategies and error links to route execution.payload(optional) — Any additional context to attach to the error, such as an API response body or intermediate values. Visible in the node's error details.
How the engine handles a failed node
Calling utils.fail() stops execution of the current function immediately — no code after the call runs. The node is marked as FAILED with the message, code, and optional payload you provided. The workflow engine then applies whichever error handling is configured on the node:
- Retry strategy — if the error code matches a configured retry rule, the node is retried automatically
- Error link — if an error link targets that code (or uses
*as a wildcard), execution continues from the linked node - No handling configured — the workflow execution fails
return utils.fail(...) vs utils.fail(...): Because utils.fail() always throws, TypeScript knows the function ends there. Many developers write return utils.fail(...) as a stylistic choice that signals intent and satisfies TypeScript's control flow analysis in if/else branches. Both work identically at runtime.
Basic example
if (!output.data?.account) {
return utils.fail(
'Account not found in the system.',
'ACCOUNT_NOT_FOUND',
{ response: output.data }
);
}
return output.data.account;Fail on a bad API response
const errorMessage = output.data?.error?.message;
if (errorMessage) {
return utils.fail(
`API request failed: ${errorMessage}`,
'API_ERROR',
output.data
);
}
return output.data;Surface a GraphQL error from a gaiia node
const firstError = output.response?.errors?.[0];
if (firstError) {
return utils.fail(firstError.message, firstError.code, output.response);
}
return output.response.data;Trigger a retry strategy with utils.fail()
An API call can return HTTP 200 but not yet contain the data your workflow needs. Using utils.fail() in an output mapper lets you force a retry when a technically successful response is logically incomplete.
const device = output.data?.devices?.find(d = d.serialNumber === 'ABC123');
if (!device) {
return utils.fail(
'Device ABC123 not found — may not be provisioned yet.',
'DEVICE_NOT_FOUND'
);
}
return device;When a retry strategy is configured for the DEVICE_NOT_FOUND code, the engine retries the node on a schedule. Once the device appears in the API response, the output mapper succeeds and execution continues normally.
Legacy: workflow.fail()
Older workflows may reference workflow.fail(). This is a deprecated predecessor to utils.fail(). Both produce the same result, but workflow.fail() will be removed in a future version. Always use utils.fail() in new workflows.
Convert between Global IDs and UUIDs
gaiia's GraphQL API uses Global IDs — Base-58 encoded strings that encode both a UUID and an entity type. These two helpers let you convert between the two formats.
utils.parseGlobalId(globalId)
Takes a Global ID string and returns { id, type } where id is the raw UUID. Use this when you need to pass a raw UUID to a REST API or query, but the workflow state contains a Global ID from a gaiia GraphQL response.
utils.parseGlobalId('account_mUDHyV4cZfFX2TXAxHB4vo');
// id → 'a950b166-8015-43f4-987a-b109cac3ee7c'
// type → 'Account'input: {
orderId: utils.parseGlobalId(state.input.orderId).id
}utils.serializeGlobalId({ id, type })
The reverse operation: takes a raw UUID and entity type and returns the Global ID string. Use this when an external system returns a UUID that you need to reference via gaiia's GraphQL API.
const globalId = utils.serializeGlobalId({
id: 'a950b166-8015-43f4-987a-b109cac3ee7c',
type: 'Account'
});
// → 'account_mUDHyV4cZfFX2TXAxHB4vo'
Handle dates and times with utils.packages.dayjs_v1
The dayjs library (v1) is pre-loaded and available as utils.packages.dayjs_v1. The utc and timezone plugins are already registered — no configuration is required.
const { dayjs_v1 } = utils.packages;
const tomorrow = dayjs_v1('2024-01-01').add(1, 'day').format('YYYY-MM-DD');
// → '2024-01-02'
const eastern = dayjs_v1.tz('2024-03-15 09:00:00', 'America/New_York');
const pacific =
[eastern.tz](http://eastern.tz/)('America/Los_Angeles').format('YYYY-MM-DDTHH:mm:ssZ');
return { tomorrow, pacific };
}Do not import dayjs directly inside a workflow function. Workflow functions are serialized to strings and stored in the database, which means module imports are not available at execution time. Always access dayjs through utils.packages.dayjs_v1.
Avoid common mistakes
Passing a non-string code to utils.fail()
Both message and code must be plain strings. If either value is undefined, null, or a number, the node fails with a generic SANDBOX_FUNCTION_UNEXPECTED_ERROR instead of your intended error, making the failure much harder to diagnose.
// Risky — if output.headers.code is undefined, the error is uninformative
return utils.fail('Request failed', output.headers.code);
// Safe — guard dynamic values with a fallback
const code = output.headers.code ?? 'REQUEST_FAILED';
return utils.fail('Request failed', code);Attempting to import external packages
Workflow functions are serialized at save time, so standard module imports are unavailable at execution time.
// Will not work
import dayjs from 'dayjs';
// Use the pre-loaded version instead
const { dayjs_v1 } = utils.packages;
Quick reference
// Fail the node with a structured error
if (!state.input.accountId) {
return utils.fail('accountId is required', 'MISSING_ACCOUNT_ID');
}
// Convert Global ID to raw UUID
const uuid = utils.parseGlobalId(state.input.accountId).id;
// Convert raw UUID back to Global ID
const globalId = utils.serializeGlobalId({ id: uuid, type: 'Account' });
// Date math with dayjs
const nextWeek = utils.packages.dayjs_v1().add(7,'days').format('YYYY-MM-DD');
return { uuid, globalId, nextWeek };
Related to