Custom Action
API reference for the Action() constructor, the wfa.actionStep() invocation helper for embedding OOB steps, and the wfa.assignActionOutputs() output helper.
Custom actions are reusable units of work composed of sequential OOB steps. They are invoked from a Flow or Subflow via wfa.action() -- the same helper used for built-in action.core.* actions.
Action()
Defines a reusable custom action with typed inputs, typed outputs, and a body of sequential wfa.actionStep() calls.
Signature
Action<TInputs, TOutputs>(
config: ActionConfig<TInputs, TOutputs>,
body?: (params: {
inputs: TInputs,
outputs: TOutputs
}) => void
): Action<TInputs, TOutputs>
Config Parameters
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
$id | string / Now.ID[...] | - | Yes | Unique identifier |
name | string | - | Yes | Display name for the action |
internalName | string | - | No | Internal system name stored as internal_name in the record. If provided, this value is preserved on deploy. If omitted, Flow Designer auto-generates it from name on publish. Set this when an action has been renamed and the internal name has diverged from the display name |
description | string | - | No | Detailed description visible to flow developers |
annotation | string | - | No | Default annotation text when this action is used in a flow |
category | string | - | No | Grouping category (e.g., "incident_management", "provisioning") |
access | 'public' | 'package_private' | 'public' | No | Visibility scope |
protectionPolicy | 'read' | '' | '' | No | If 'read', the action body is read-protected in the runtime |
inputs | Record<string, Column> | - | Yes | Input parameter definitions using column types |
outputs | Record<string, Column> | - | Yes | Output parameter definitions using column types |
⚠️ access value: The valid scope-private value is 'package_private' (not 'private'). Other values fail at build time.
Body Function
The body is optional and receives a params object exposing the typed schemas:
params key | Type | Description |
|---|---|---|
inputs | TInputs | Typed input pills. Use wfa.dataPill(params.inputs.field, 'type') |
outputs | TOutputs | Output schema reference. Pass to assignActionOutputs to set the action's outputs |
The body contains only wfa.actionStep() calls and (optionally) one assignActionOutputs call. No flow logic, no nested custom actions.
wfa.actionStep()
Embeds an OOB step inside a custom action body.
Signature (typed step)
wfa.actionStep<TStep>(
step: TStep, // actionStep.* namespace
config: {
$id: string,
label?: string,
annotation?: string
},
inputs: StepParameters<TStep> & {
errorHandlingType?: 'stop_the_action' | 'dont_stop_the_action',
inputVariables?: ExtendedInputConfig, // only if step allows extended inputs
outputVariables?: Record<string, Column> // only if step allows extended outputs
}
): StepOutputs<TStep>
Signature (sys_id fallback)
When a step's typed definition isn't available (custom step from another app), pass the step's sys_id (or name) as a string. The return type allows arbitrary property access.
wfa.actionStep(
stepSysId: string,
config: { $id: string, label?: string },
inputs: Record<string, unknown> & { errorHandlingType?: ... }
)
Config Parameters
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
$id | string | - | Yes | Unique identifier for this step instance |
label | string | - | No | Display label for this step instance |
annotation | string | - | No | Additional notes or description |
Common Step Parameters
Supported by all wfa.actionStep() calls (passed alongside step-specific fields in the third argument):
| Parameter | Type | Default | Description |
|---|---|---|---|
errorHandlingType | choice | - | 'stop_the_action' halts on error; 'dont_stop_the_action' continues execution on error |
inputVariables | object | - | Named input variables (only allowed when the step's allowExtendedInputs is true; e.g., actionStep.script) |
outputVariables | object | - | Named output variables (only allowed when the step's allowExtendedOutputs is true; e.g., actionStep.script) |
Return Value
Returns the step's typed outputs. Capture as a const to chain into downstream steps via wfa.dataPill().
const created = wfa.actionStep(actionStep.createRecord, { $id: ... }, { ... });
// downstream:
wfa.actionStep(
actionStep.log,
{ $id: ... },
{ log_message: wfa.dataPill(created.record.number, "string"), log_level: "info" }
);
wfa.assignActionOutputs()
Assigns output values inside a custom Action body, mapping declared output names to static values, datapill references, or template literals. This is the recommended way to set action outputs based on step results and input values.
wfa.assignActionOutputs(params.outputs, values)
| Parameter | Type | Description |
|---|---|---|
params.outputs | Output schema | The output schema from params.outputs in the action body function. Provides type-safe key completion. |
values | { [K in keyof Outputs]?: Outputs[K] | string } | Partial record mapping output names to their values. Keys are constrained to declared output names — arbitrary keys produce a TypeScript error. |
Value Types
Output values support three formats:
| Format | Example | Use Case |
|---|---|---|
| Static string | 'test value' | Fixed values known at design time |
| Data pill | wfa.dataPill(step.record.field, 'string') | Dynamic reference to a step output or action input |
| Template literal | `prefix${wfa.dataPill(step.field, 'string')}` | Mix of static text and dynamic references |
Important notes:
- Placement: Call
wfa.assignActionOutputs()after allwfa.actionStep()calls in the action body. If usingwfa.errorEvaluation(), place output assignment after it. - Type safety: Output keys are constrained to the names declared in the action's
outputsconfiguration. TypeScript provides autocomplete (Ctrl+Space) for valid output names. - Partial assignment: Not all outputs need to be assigned — the
valuesparameter accepts a partial record. - Boolean values: Use
'1'for true and'0'for false when assigning boolean output values as static strings.
wfa.errorEvaluation()
Defines runtime error evaluation conditions for a custom Action. Conditions are evaluated in order — the first matching condition sets the action's status code and message. Use this to report conditional success or failure based on step results and input values.
wfa.errorEvaluation(conditions)
| Parameter | Type | Description |
|---|---|---|
conditions | ErrorEvaluationCondition[] | Array of conditions evaluated in order. The first matching condition's status is applied. |
ErrorEvaluationCondition
| Property | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display label for the condition (visible in Flow Designer). |
condition | string | Yes | Query-style condition string. Supports datapill references via template literals. |
status | { code, message } | Yes | The status to set when this condition matches. |
status.code | number | string | Yes | Status code. Can be a static number or a template expression with datapills. |
status.message | string | Yes | Status message. Can be a static string or a template expression with datapills. |
dontTreatAsError | boolean | No | When true, the action does not treat this condition as an error. Defaults to false. |
Condition Syntax
Condition strings use ServiceNow encoded query syntax. Operators are joined with ^ (AND) and ^OR (OR). Values can include datapill references via template literals.
// Simple equality check on step status code
condition: `${wfa.dataPill(step.__step_status__.code, 'integer')}=500`
// OR condition
condition: `${wfa.dataPill(step.__step_status__.code, 'integer')}=500^OR${wfa.dataPill(step.__step_status__.code, 'integer')}=501`
// ISNOTEMPTY check
condition: `${wfa.dataPill(params.inputs.incident, 'reference')}ISNOTEMPTY`
Important notes:
- Evaluation order: Conditions are evaluated in the order they appear in the array. The first matching condition wins — subsequent conditions are not evaluated.
- Placement: Call
wfa.errorEvaluation()after allwfa.actionStep()calls but beforewfa.assignActionOutputs(). - dontTreatAsError: When
true, the matching condition sets the status but the action is considered successful. Whenfalse(default), the action is treated as failed. - No match: If no condition matches, the action completes with its default status —
wfa.errorEvaluation()does not alter it. - Step vs action error handling:
errorHandlingTypeon individualwfa.actionStep()calls controls step-level behavior (stop or continue).wfa.errorEvaluation()sets the overall action status based on conditions evaluated after all steps complete.
DefaultActionOutputs
Every action invocation -- built-in (action.core.*) and custom -- exposes these system outputs in addition to the action's declared outputs:
| Field | Type | Description |
|---|---|---|
__action_status__.code | number | Status code of the action execution |
__action_status__.message | string | Status message describing the result |
__dont_treat_as_error__ | boolean | If true, an error result will not propagate as an error to the caller |
Useful for inspecting an action's runtime status without modeling it in the action's declared outputs.
Column Types
The inputs and outputs schemas of an Action() are defined using column types. Scalar columns are imported from @servicenow/sdk/core; the complex container types (FlowObject, FlowArray) are imported from @servicenow/sdk/automation alongside Action.
import {
StringColumn, IntegerColumn, BooleanColumn, DecimalColumn, FloatColumn,
DateColumn, DateTimeColumn, ReferenceColumn, ChoiceColumn
} from '@servicenow/sdk/core'
import { Action, FlowObject, FlowArray } from '@servicenow/sdk/automation'
All columns share label and mandatory; most also accept default and hint. The examples below are taken from the action test fixtures.
Common properties (all scalar columns)
| Property | Type | Required | Description |
|---|---|---|---|
label | string | No | Display label shown for the input/output in Flow Designer |
mandatory | boolean | No | Whether a value is required (defaults to false) |
default | matches the column's type | No | Default value applied when none is provided |
hint | string | No | Verbose description / help text for the field |
Each scalar column below documents the properties it accepts in addition to the common ones above.
Scalar columns (@servicenow/sdk/core)
StringColumn
Text values.
| Property | Type | Required | Description |
|---|---|---|---|
maxLength | number | No | Maximum number of characters allowed |
isFullUTF8 | boolean | No | Store as full UTF-8 (column type becomes string_full_utf8) |
input1: StringColumn({
label: 'inputOne',
mandatory: true,
default: 'John Doe',
hint: 'This is the string hint',
})
IntegerColumn
Whole numbers.
| Property | Type | Required | Description |
|---|---|---|---|
min | number | No | Minimum value |
max | number | No | Maximum value |
estimatedHours: IntegerColumn({
label: 'Estimated Hours',
mandatory: false,
min: 0,
max: 1000,
})
BooleanColumn
True/false values. default is a real boolean.
someOutput: BooleanColumn({
label: 'someOutput',
mandatory: true,
default: true,
hint: 'This is a boolean hint',
})
DecimalColumn
Fixed-precision decimal numbers.
| Property | Type | Required | Description |
|---|---|---|---|
scale | number | No | Number of decimal places |
amount: DecimalColumn({
label: 'Amount',
mandatory: false,
scale: 2,
})
FloatColumn
Floating-point numbers.
| Property | Type | Required | Description |
|---|---|---|---|
scale | number | No | Number of decimal places |
nativeFloat: FloatColumn({
label: 'Native Float Value',
mandatory: false,
scale: 3,
})
DateColumn
Date-only values.
date: DateColumn({ label: 'date' })
DateTimeColumn
Date and time values.
createdDate: DateTimeColumn({
label: 'Created Date',
mandatory: false,
})
ReferenceColumn
Reference to a record in another table. Set the target with referenceTable.
| Property | Type | Required | Description |
|---|---|---|---|
referenceTable | TableName | Yes | Target table the field references |
cascadeRule | 'none' | 'delete_no_workflow' | 'cascade' | 'delete' | 'restrict' | 'clear' | No | What happens to referencing records when the target is deleted |
useReferenceQualifier | 'simple' | 'dynamic' | 'advanced' | No | Type of reference qualifier applied to the field |
referenceQual | string | No | Filter the reference based on a condition or referenced value |
reference: ReferenceColumn({
label: 'reference',
referenceTable: 'sys_user',
})
ChoiceColumn
Choice list values. Choices can be supplied three ways: inline key: 'Label' strings, an object with { label, sequence } metadata, or dynamically sourced from another table via dynamicValueDefinitions.
| Property | Type | Required | Description |
|---|---|---|---|
choices | Record<string, string | { label, sequence?, hint?, inactive? }> | No | Choice value definitions (inline string or object metadata) |
dropdown | 'none' | 'dropdown_with_none' | 'suggestion' | 'dropdown_without_none' | No | Dropdown rendering behavior |
dynamicValueDefinitions | { type, table, field } | No | Source choices dynamically from another table's field |
// Inline string choices
status: ChoiceColumn({
label: 'Status',
mandatory: true,
choices: {
new: 'New',
in_progress: 'In Progress',
on_hold: 'On Hold',
completed: 'Completed',
cancelled: 'Cancelled',
},
default: 'new',
})
// Choices with label + sequence metadata
status: ChoiceColumn({
label: 'Status',
default: 'success',
choices: {
success: { label: 'Success', sequence: 1 },
failed: { label: 'Failed', sequence: 2 },
},
})
// Choices sourced dynamically from another table's field
priority: ChoiceColumn({
label: 'Priority',
mandatory: true,
dynamicValueDefinitions: {
type: 'choices_from_other_table',
table: 'incident',
field: 'state',
},
default: 1,
})
Complex container types (@servicenow/sdk/automation)
FlowObject
A nested object whose shape is declared with a fields map of column types. Each field can be any column type — including nested FlowObject / FlowArray.
| Property | Type | Required | Description |
|---|---|---|---|
fields | Record<string, Column | FlowObject | FlowArray> | Yes | Field definitions; each value is any column type, including nested complex types |
label | string | Yes | Display label for the object |
mandatory | boolean | No | Whether the object is required |
attributes | Record<string, string | number | boolean> | No | Supported dictionary attributes (sys_schema_attribute) |
userInfo: FlowObject({
label: 'User Information',
mandatory: true,
fields: {
firstName: StringColumn({ label: 'First Name', mandatory: true, maxLength: 100 }),
lastName: StringColumn({ label: 'Last Name', mandatory: true, maxLength: 100 }),
active: BooleanColumn({ label: 'Is Active' }),
},
})
FlowArray
An array of typed elements. The element shape is declared with elementType (any column type, including FlowObject). childName names the repeating child; maxRows caps the row count.
| Property | Type | Required | Description |
|---|---|---|---|
elementType | Column | FlowObject | Yes | Shape of each element. Cannot be another FlowArray |
childName | string | Yes | Name of the repeating child element |
label | string | Yes | Display label for the array |
mandatory | boolean | No | Whether the array is required |
hint | string | No | Hint or help text for the array |
maxRows | number | No | Maximum number of rows allowed |
// Array of strings
tags: FlowArray({
label: 'Tags',
hint: 'Enter the tags for children',
maxRows: 10,
childName: 'tag_child0',
elementType: StringColumn({
label: 'Tag',
maxLength: 50,
default: 'tag',
hint: 'Enter the tag',
}),
})