Skip to main content
Version: 4.8.1

Workflow Automation Flow Logic Guide

Flow logic constructs control how a flow executes -- branching, looping, and termination. They are invoked from inside a flow or subflow body via wfa.flowLogic.*.

For API signatures, parameter tables, and condition operator reference, see the Flow Logic API.


Overview​

TypeConstructsUse For
Conditionalif, elseIf, elseBranching
LoopsforEach, skipIteration, exitLoopIteration
ControlendFlowFlow termination

Condition syntax: Encoded query format -- use = not ==, ^ for AND, ^OR for OR. See the Flow Logic API → Condition Syntax Reference for the full operator catalog.

When to Use Which Construct​

PatternFlow Logic ConstructUse When
Route to different actionsif/elseIf/elseDifferent actions for different conditions
Process a list of recordsforEachBatch processing, multiple records
Stop processing earlyexitLoopFound target record, limit reached
Skip records in processingskipIterationFiltering, validation failed
Stop entire flowendFlowEarly termination on critical condition

Conditional: if / elseIf / else​

Conditional branching allows flows to execute different actions based on dynamic conditions evaluated at runtime.

When to Use​

  • Route flow execution based on field values (priority, status, category)
  • Implement business logic with multiple decision paths
  • Handle different scenarios based on trigger data or action results
  • Validate lookup results before processing

Best Practices​

  1. Use Template Literals -- Always wrap data pill conditions: `${wfa.dataPill(...)}=value`
  2. Single Equals for Comparison -- Use = not == (ServiceNow encoded query format)
  3. Check Empty Values -- Use ISEMPTY/ISNOTEMPTY operators for null, undefined, or empty string checks
  4. Complex Conditions -- Use ^ for AND, ^OR for OR:
    • AND: `${wfa.dataPill(field1)}=1^${wfa.dataPill(field2)}=2`
    • OR: `${wfa.dataPill(field1)}=1^OR${wfa.dataPill(field2)}=2`
  5. Reference Field Comparisons -- Compare sys_id values using .value, not display values
  6. Avoid Deep Nesting -- Prefer elseIf chains over nested if statements for readability
  7. Validate Lookup Results -- Check status='0' before using lookup results

Common Use Cases​

  • Priority-Based Routing -- Route incidents based on priority (1=critical → on-call, 2=high → senior, else → standard queue)
  • State Transition Logic -- Different actions for resolved (state=6) vs closed (state=7)
  • Approval Result Handling -- Check approval_state (approved, rejected, cancelled)
  • Lookup Result Validation -- Check Count>0 before processing results
  • Category-Based Processing -- Different workflows for hardware/software/service requests

Example​

wfa.flowLogic.if(
{
$id: Now.ID["check_priority"],
condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=1`,
annotation: "Check if priority is critical"
},
() => {
wfa.action(
action.core.updateRecord,
{ $id: Now.ID["escalate"] },
{
table_name: "incident",
record: wfa.dataPill(params.trigger.current, "reference"),
values: TemplateValue({ state: 2 })
}
);
}
);

wfa.flowLogic.elseIf(
{
$id: Now.ID["check_priority_2"],
condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=2`,
annotation: "Check if priority is high"
},
() => {
// Actions for priority 2 incidents
}
);

wfa.flowLogic.else(
{ $id: Now.ID["default_case"], annotation: "Handle all other priorities" },
() => {
// Default actions
}
);

Important Notes​

  • Condition Syntax -- Uses ServiceNow encoded query format (same as GlideRecord filters)
  • Field Types Matter -- Boolean uses true/false, choice fields use internal values (not labels)
  • Runtime Evaluation -- Conditions evaluated at runtime using current data pill values
  • Sequence Matters -- if/elseIf/else blocks evaluate in order. Once matched, subsequent blocks are skipped.
  • elseIf/else must follow if -- Standalone elseIf or else is invalid.

Integration Notes​

With Actions:

  • Conditional blocks can contain any actions (create, update, lookup, send)
  • Action outputs from previous steps can be used in condition evaluation

With Loops:

  • Conditionals can be used inside forEach loops for per-item processing
  • Combine with exitLoop/skipIteration for advanced loop control

Loops: forEach​

Iterate over arrays of records or data, executing actions for each item in the collection.

When to Use​

  • Process multiple records from lookUpRecords
  • Bulk operations on multiple items
  • Send notifications to multiple users/groups
  • Iterate over flow variable arrays

Best Practices​

  1. Limit Record Processing -- Use max_results in lookUpRecords to prevent timeouts. Keep iterations under 100-200 records for optimal performance.
  2. Use skipIteration for Filtering -- Skip items that don't meet criteria instead of wrapping the entire loop body in conditional logic.
  3. Use exitLoop for Early Exit -- Stop loop when goal is achieved (e.g., finding first match). This improves performance by avoiding unnecessary iterations.
  4. Consider Batch Actions -- For simple updates without complex logic, updateMultipleRecords is more efficient than forEach + updateRecord.
  5. Validate Loop Input -- Check that the array/records exist before looping to avoid flow errors on empty results.

Common Use Cases​

ScenarioPatternNotes
Bulk assignmentforEach + updateRecordConsider updateMultipleRecords if simple
Team notificationsforEach + sendNotification/sendEmailPersonalize message per recipient
Find first availableforEach + if + exitLoopStop when first match found
Filter and processforEach + if + skipIterationSkip items not meeting criteria
Cascading updatesforEach + nested lookups + updateUpdate related records for each item

Performance Considerations​

forEach loop behavior:

  • No hard technical limit -- forEach will process any number of records provided
  • Performance recommendation -- Keep iterations under 100-200 records per flow execution for optimal performance
  • Large loops can cause -- Flow execution timeouts, system resource constraints, slower processing

For large datasets:

  • Always specify max_results based on expected workload
  • Use skipIteration to filter early and reduce processing
  • Use exitLoop when you find what you need
  • For very large datasets, use batch processing with multiple scheduled flow executions

Warning signs:

  • ⚠️ lookUpRecords without max_results parameter
  • ⚠️ forEach loops with >200 iterations
  • ⚠️ Heavy operations (API calls, complex updates) within loops
  • ⚠️ Nested forEach loops with large outer datasets

Example​

// Lookup records
const results = wfa.action(
action.core.lookUpRecords,
{ $id: Now.ID["find_incidents"] },
{
table: "incident",
conditions: "active=true^priority=1",
max_results: 50
}
);

// Iterate over results
wfa.flowLogic.forEach(
wfa.dataPill(results.Records, "array.object"),
{ $id: Now.ID["process_incidents"], annotation: "Update each incident" },
incident => {
wfa.action(
action.core.updateRecord,
{ $id: Now.ID["update_incident"] },
{
table_name: "incident",
record: wfa.dataPill(incident.sys_id, "reference"),
values: TemplateValue({ state: 2 })
}
);
}
);

Important Notes​

  • Sequential Processing -- forEach processes items sequentially, not in parallel. Each iteration completes before the next begins.
  • Timeout Limits -- Very large loops (>200 items) may timeout. Use batching or multiple flows for large datasets.
  • Item Reference -- The loop item variable references the current iteration's data. Use wfa.dataPill with the item reference to access fields.
  • Break and Continue -- Use exitLoop (like break) to stop the loop entirely; skipIteration (like continue) to skip current iteration only.
  • Data Modification -- Modifying the looped array during iteration can cause unexpected behavior. Complete the loop before modifying source data.

exitLoop​

Immediately exits the current forEach loop and continues flow execution after the loop.

When to Use​

  • ✅ Finding first match in search
  • ✅ Goal achieved, remaining iterations unnecessary
  • ✅ Critical error prevents further processing
  • ❌ Filtering items (use skipIteration instead)
  • ❌ Terminating entire flow (use endFlow instead)

Best Practices​

  1. Add Annotations -- Explain why loop is exiting for better debugging and flow diagram readability.

    wfa.flowLogic.exitLoop({
    $id: Now.ID["exit"],
    annotation: "Found first available technician - stopping search"
    });
  2. Use with Conditionals -- exitLoop should always be inside if/elseIf blocks. Unconditional exitLoop makes the loop pointless.

  3. exitLoop vs skipIteration:

    • Use exitLoop: "Find first available technician and assign" (stop processing entirely)
    • Use skipIteration: "Process all high-priority incidents, skip low priority" (continue with next item)
  4. exitLoop vs endFlow:

    • exitLoop: Exits current loop, continues flow execution after loop
    • endFlow: Terminates entire flow immediately
  5. Nested Loops -- In nested loops, exitLoop only exits the innermost loop. To exit multiple levels, use flags or multiple exitLoop statements.

Example​

wfa.flowLogic.forEach(
wfa.dataPill(results.Records, "array.object"),
{ $id: Now.ID["search_loop"] },
record => {
// Check if we found the target record
wfa.flowLogic.if(
{
$id: Now.ID["check_match"],
condition: `${wfa.dataPill(record.number, "string")}=INC0001234`
},
() => {
// Found the target -- exit loop
wfa.flowLogic.exitLoop({ $id: Now.ID["exit_loop"] });
}
);
}
);

Important Notes​

  • Scope -- exitLoop only exits the current forEach loop. In nested loops, it exits only the innermost loop.
  • Flow Continues -- After exitLoop, flow execution continues with the next action after the loop.
  • No Return Value -- exitLoop does not return a value. Use conditional logic or update records to track exit reason.
  • Immediate Exit -- exitLoop takes effect immediately. Any code after exitLoop in the same iteration will not execute.

skipIteration​

Skips the current forEach iteration and continues with the next item in the loop.

When to Use​

  • Filter items based on criteria
  • Skip items that don't meet conditions
  • Skip already-processed items
  • Handle edge cases gracefully

Best Practices​

  1. Early Exit Pattern -- Place skip checks at the beginning of the loop for efficiency. This avoids executing unnecessary logic for items that will be skipped.

    wfa.flowLogic.forEach(items, { $id: Now.ID["loop"] }, item => {
    // Skip check first -- saves processing time
    wfa.flowLogic.if(
    {
    $id: Now.ID["check"],
    condition: `${wfa.dataPill(item.priority, "string")}=5`
    },
    () => {
    wfa.flowLogic.skipIteration({
    $id: Now.ID["skip"],
    annotation: "Skipping low priority item"
    });
    }
    );

    // Main processing only runs for non-skipped items
    });
  2. Add Annotations -- Explain why iteration is skipped for better debugging and flow execution history understanding.

  3. Use with Conditionals -- skipIteration should always be inside if/elseIf blocks. Unconditional skipIteration makes the loop pointless.

  4. Multiple Skip Conditions -- Use multiple if statements with skipIteration for different skip reasons. Each can have its own annotation for clarity.

  5. skipIteration vs exitLoop:

    • skipIteration: "Skip inactive users but process active ones" (continues loop)
    • exitLoop: "Found the user we need, stop searching" (stops entire loop)
  6. Performance Benefit -- skipIteration improves performance by avoiding unnecessary processing. Place checks early to maximize savings.

Example​

wfa.flowLogic.forEach(
wfa.dataPill(results.Records, 'array.object'),
{ $id: Now.ID['filter_loop'] },
(record) => {
// Skip if already assigned
wfa.flowLogic.if(
{
$id: Now.ID['check_assigned'],
condition: `${wfa.dataPill(record.assigned_to, 'string')}ISNOTEMPTY`
},
() => {
wfa.flowLogic.skipIteration({ $id: Now.ID['skip'] });
}
);

// Process unassigned records
wfa.action(action.core.updateRecord, { $id: Now.ID['assign'] }, { /* ... */ });
}
);

Important Notes​

  • Continues Loop -- skipIteration skips the current iteration and immediately moves to the next item. The loop continues processing remaining items.
  • Immediate Effect -- Once skipIteration executes, no further code in that iteration runs. Control jumps to the next iteration.
  • No Return Value -- skipIteration does not return a value. Use counters or logs to track skipped items if needed.
  • Nested Loops -- In nested loops, skipIteration only skips the current iteration of the innermost loop.

Flow Termination: endFlow​

Immediately terminates the entire flow execution. No subsequent actions or logic will execute.

When to Use​

Use endFlow when you need to terminate flow based on runtime conditions that cannot be determined at trigger time:

  • Conditions depend on lookup results (not available at trigger time)
  • Runtime validation needed (state can change after trigger fires)
  • Complex business logic that can't be expressed in trigger conditions
  • Duplicate prevention checks requiring database lookups
  • Permission validation requiring role/group membership checks

Prefer trigger conditions when possible:

// Less efficient: flow fires for all incidents, then endFlow filters out
wfa.flowLogic.if(
{
$id: Now.ID["check"],
condition: `${wfa.dataPill(params.trigger.current.priority, "string")}!=1`
},
() => {
wfa.flowLogic.endFlow({ $id: Now.ID["end"] });
}
);

// More efficient: filter at trigger level with condition "priority=1"

Best Practices​

  1. Use Trigger Conditions First -- Filter at trigger level when possible to avoid unnecessary flow executions. This improves system performance and reduces flow execution logs.

  2. Combined Conditions -- Use AND (^) and OR (^OR) in trigger conditions instead of multiple endFlow checks.

  3. Add Annotations -- Always explain why flow is ending. This helps with debugging and understanding flow execution history.

    wfa.flowLogic.endFlow({
    $id: Now.ID["end"],
    annotation: "Duplicate request detected - ending to prevent double processing"
    });
  4. Use Conditionally -- Always use endFlow inside conditional blocks. Unconditional endFlow at start of flow makes the trigger pointless.

  5. Handle Edge Cases -- Use endFlow to gracefully handle edge cases and prevent errors from invalid data or states.

Common Use Cases​

  • Duplicate Prevention -- Check if request/record already exists before processing
  • Permission Validation -- Verify user has required role/group membership
  • State Validation -- Re-check current record state (may have changed since trigger)
  • Dependency Check -- Verify parent/related records exist and are valid
  • Business Rules Enforcement -- Exit when business rules determine no action needed

Example​

// Check for error condition
wfa.flowLogic.if(
{
$id: Now.ID["check_error"],
condition: `${wfa.dataPill(result.status, "integer")}=1`
},
() => {
// Log error
wfa.action(
action.core.log,
{ $id: Now.ID["log_error"] },
{
log_level: "error",
log_message: "Critical error - terminating flow"
}
);

// Terminate flow
wfa.flowLogic.endFlow({ $id: Now.ID["end_flow"] });
}
);

Important Notes​

  • Immediate Termination -- endFlow immediately terminates the entire flow. No subsequent actions, logic, or loops will execute.
  • No Return Value -- endFlow does not return a value or set output variables. The flow simply stops.
  • Execution Status -- Flows that end via endFlow show as "Completed" in execution logs. Use annotations to distinguish.
  • Nested Contexts -- endFlow terminates the entire flow even when called from inside loops (forEach), conditionals (if/else), parallel blocks (doInParallel), or other nested logic.
  • Different from exitLoop -- exitLoop exits a loop but continues flow. endFlow terminates the entire flow execution.

Parallel Execution: doInParallel​

Executes multiple code blocks in parallel within a flow, allowing independent operations to run concurrently.

When to Use​

  • Execute independent actions simultaneously (notifications, logging, updates to different tables)
  • Improve flow performance when operations don't depend on each other
  • Send multiple notifications or emails at once
  • Update multiple unrelated records in parallel

Best Practices​

  1. Independent Operations Only -- Only use for operations that don't depend on each other's results. Each block should be self-contained.
  2. No Nesting -- doInParallel cannot be nested inside another doInParallel block. This is enforced at build time.
  3. Error Handling -- If one parallel block fails, other blocks continue executing. Use tryCatch inside blocks for error handling.
  4. Keep Blocks Simple -- Each parallel block should have a clear, single purpose. Complex logic makes debugging difficult.
  5. Avoid Shared State -- Don't modify the same flow variables or records across multiple parallel blocks.

Common Use Cases​

ScenarioPatternNotes
Multiple notificationsdoInParallel + multiple sendNotificationSend to different users/groups simultaneously
Logging + updatesdoInParallel + log + updateRecordLog and update can happen in parallel
Multi-table updatesdoInParallel + multiple updateRecordUpdate unrelated tables at once
Parallel API callsdoInParallel + multiple REST actionsExecute independent API calls concurrently

Example​

wfa.flowLogic.doInParallel(
{ $id: Now.ID["parallel_notifications"], annotation: "Send notifications in parallel" },
() => {
// Block 1: Notify assignee
wfa.action(
action.core.sendNotification,
{ $id: Now.ID["notify_assignee"] },
{
to: wfa.dataPill(params.trigger.current.assigned_to, "reference"),
subject: "New incident assigned",
body: "You have been assigned a new incident"
}
);
},
() => {
// Block 2: Notify manager
wfa.action(
action.core.sendNotification,
{ $id: Now.ID["notify_manager"] },
{
to: wfa.dataPill(params.trigger.current.assignment_group.manager, "reference"),
subject: "Team incident notification",
body: "New incident assigned to your team"
}
);
},
() => {
// Block 3: Log action
wfa.action(
action.core.log,
{ $id: Now.ID["log_parallel"] },
{
log_level: "info",
log_message: "Parallel notifications sent"
}
);
}
);

Important Notes​

  • Execution Order -- Parallel blocks may complete in any order. Don't assume a specific sequence.
  • No Return Values -- Parallel blocks cannot return values to each other. Use flow variables if you need to share data.
  • Build-Time Validation -- Nesting is detected at build time and will cause a compilation error.
  • Flow Designer Representation -- In Flow Designer, parallel blocks appear as separate branches under the parallel container.

Error Handling: tryCatch​

Provides try-catch error handling within flows, allowing graceful handling of action failures.

When to Use​

  • Handle potential action failures gracefully (API calls, lookups, external integrations)
  • Provide fallback logic when operations might fail
  • Log errors and continue flow execution
  • Implement retry logic with alternative approaches

Best Practices​

  1. Specific Error Handling -- Use catch blocks for specific error scenarios, not as a catch-all.
  2. Log Errors -- Always log in the catch block to track failures and aid debugging.
  3. Provide Fallbacks -- Catch blocks should provide meaningful fallback behavior, not just log and continue.
  4. Nested Try-Catch -- Can be nested for granular error handling at different levels.
  5. Don't Overuse -- Not every action needs try-catch. Use for operations with known failure modes.

Common Use Cases​

ScenarioPatternNotes
API call failurestryCatch + REST action + fallbackHandle external service unavailability
Lookup validationtryCatch + lookUpRecord + defaultProvide default when record not found
Record creationtryCatch + createRecord + logHandle duplicate or validation errors
Integration errorstryCatch + integration action + notificationAlert on integration failures

Example​

wfa.flowLogic.tryCatch(
{
$id: Now.ID["try_catch_lookup"],
annotation: "Handle lookup failure gracefully"
},
{
try: () => {
// Attempt to look up a record that might not exist
const lookup = wfa.action(
action.core.lookUpRecord,
{ $id: Now.ID["lookup_user"] },
{
table_name: "sys_user",
conditions: `sys_id=${wfa.dataPill(params.trigger.current.assigned_to, "string")}`
}
);

wfa.action(
action.core.log,
{ $id: Now.ID["lookup_success"] },
{
log_level: "info",
log_message: `User found: ${wfa.dataPill(lookup.Record.name, "string")}`
}
);
},
catch: () => {
// Handle lookup failure (record not found or error)
wfa.action(
action.core.log,
{ $id: Now.ID["lookup_failure"] },
{
log_level: "error",
log_message: "User lookup failed - using system user as fallback"
}
);

// Fallback: use system user
wfa.flowLogic.setFlowVariables(
{ $id: Now.ID["set_fallback"] },
params.flowVariables,
{ assignedUser: "system" }
);
}
}
);

Nested Try-Catch Example​

wfa.flowLogic.tryCatch(
{ $id: Now.ID["outer_try"], annotation: "Outer error handling" },
{
try: () => {
wfa.action(
action.core.log,
{ $id: Now.ID["outer_try_log"] },
{ log_level: "info", log_message: "Outer try block" }
);

// Nested try-catch for granular error handling
wfa.flowLogic.tryCatch(
{ $id: Now.ID["inner_try"] },
{
try: () => {
wfa.action(
action.core.updateRecord,
{ $id: Now.ID["risky_update"] },
{
table_name: "incident",
record: wfa.dataPill(params.trigger.current, "reference"),
values: TemplateValue({ state: 2 })
}
);
},
catch: () => {
wfa.action(
action.core.log,
{ $id: Now.ID["inner_catch_log"] },
{ log_level: "warn", log_message: "Update failed, continuing" }
);
}
}
);
},
catch: () => {
wfa.action(
action.core.log,
{ $id: Now.ID["outer_catch_log"] },
{ log_level: "error", log_message: "Outer catch block" }
);
}
}
);

Important Notes​

  • Both Handlers Required -- Both try and catch must be arrow functions.
  • Catch Executes on Error -- The catch block only runs if an error occurs in the try block.
  • Flow Continues -- After catch block completes, flow execution continues with subsequent logic.
  • No Error Object -- Unlike JavaScript, the catch block doesn't receive an error object. Use logging to track failures.

Array Operations: appendToFlowVariables​

Appends element(s) to array-typed flow variables, specifically Array.Object variables.

When to Use​

  • Build lists of items during flow execution (collecting records, accumulating data)
  • Append results from loops to a collection
  • Aggregate data from multiple sources
  • Build dynamic arrays for downstream processing

Best Practices​

  1. Array.Object Only -- Only works with FlowArray({ elementType: FlowObject(...) }) variables. Other array types are not supported.
  2. Declare Variables First -- Flow variables must be declared in the flow/subflow config before appending.
  3. Object Elements -- When appending array literals, each element must be an object or datapill, not primitives.
  4. Initialize Arrays -- Arrays start empty. No need to initialize before first append.
  5. Multiple Appends -- Can append to the same variable multiple times throughout the flow.

Common Use Cases​

ScenarioPatternNotes
Collect loop resultsforEach + appendToFlowVariablesBuild array from loop iterations
Aggregate dataMultiple appendToFlowVariablesCollect data from different sources
Build notification listConditional + appendToFlowVariablesDynamically build recipient list
Accumulate recordslookUpRecords + forEach + appendFilter and collect specific records

Example: Single Element Append​

// Flow config with Array.Object variable
Flow(
{
$id: Now.ID["collect_data_flow"],
name: "Collect Data Flow",
flowVariables: {
collectedItems: FlowArray({
label: "Collected Items",
mandatory: false,
elementType: FlowObject({
fields: {
name: StringColumn({ label: "Name" }),
id: IntegerColumn({ label: "ID" })
},
label: "Item",
mandatory: false
}),
childName: "item"
})
}
},
wfa.trigger(/* ... */),
(params) => {
// Append single element
wfa.flowLogic.appendToFlowVariables(
{
$id: Now.ID["append_single"],
annotation: "Add one item"
},
params.flowVariables,
{
collectedItems: { name: "Alice", id: 1 }
}
);
}
);

Example: Array Append​

// Append multiple elements at once
wfa.flowLogic.appendToFlowVariables(
{
$id: Now.ID["append_multiple"],
annotation: "Add multiple items"
},
params.flowVariables,
{
collectedItems: [
{ name: "Bob", id: 2 },
{ name: "Charlie", id: 3 },
{ name: "Dana", id: 4 }
]
}
);

Example: Append in Loop​

const results = wfa.action(
action.core.lookUpRecords,
{ $id: Now.ID["lookup"] },
{
table: "sys_user",
conditions: "active=true",
max_results: 50
}
);

wfa.flowLogic.forEach(
wfa.dataPill(results.Records, "array.object"),
{ $id: Now.ID["process_users"] },
(user) => {
// Filter: only append users with specific role
wfa.flowLogic.if(
{
$id: Now.ID["check_role"],
condition: `${wfa.dataPill(user.roles, "string")}CONTAINS admin`
},
() => {
wfa.flowLogic.appendToFlowVariables(
{ $id: Now.ID["append_admin"] },
params.flowVariables,
{
collectedItems: {
name: wfa.dataPill(user.name, "string"),
id: wfa.dataPill(user.sys_id, "string")
}
}
);
}
);
}
);

Example: Multi-Target Append​

// Append to multiple array variables in one call
wfa.flowLogic.appendToFlowVariables(
{
$id: Now.ID["append_multi_target"],
annotation: "Append to two arrays"
},
params.flowVariables,
{
collectedItems: [{ name: "Eve", id: 5 }],
otherArray: [{ field1: "value1", field2: 123 }]
}
);

Important Notes​

  • Type Safety -- TypeScript enforces that target variables are arrays at compile time.
  • Runtime Validation -- Build-time checks ensure array elements are objects or datapills, not primitives.
  • Array.Object Requirement -- Only FlowArray({ elementType: FlowObject(...) }) is supported. Primitive arrays (string[], integer[]) are not supported.
  • Empty Arrays -- Arrays start empty and grow with each append. No initialization needed.
  • Performance -- Appending in loops is efficient. No need to batch appends.

⚠️ JavaScript NOT Supported in Conditions​

Flow logic conditions (if, elseIf, else) do NOT support JavaScript functions like javascript:gs.daysAgoStart(30) or javascript:gs.beginningOfThisWeek().

Only use:

  • Data pill comparisons with static values
  • Data pills from trigger outputs
  • Data pills from action outputs
  • Encoded query operators (=, !=, <, >, ISEMPTY, etc.)
// ❌ WRONG - JavaScript function not supported in if
wfa.flowLogic.if(
{ $id: Now.ID["wrong"], condition: `${wfa.dataPill(params.trigger.current.sys_updated_on, "glide_date_time")}<javascript:gs.daysAgoStart(30)` },
() => { /* ... */ }
);

// ❌ WRONG - JavaScript function not supported in elseIf
wfa.flowLogic.if({ $id: Now.ID["a"], condition: "..." }, () => { /* ... */ });
wfa.flowLogic.elseIf(
{ $id: Now.ID["wrong_elseif"], condition: `${wfa.dataPill(params.trigger.current.due_date, "glide_date_time")}<javascript:gs.beginningOfThisWeek()` },
() => { /* ... */ }
);

// ✅ CORRECT - Data pill comparison in if/elseIf
wfa.flowLogic.if(
{ $id: Now.ID["check_p1"], condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=1` },
() => { /* ... */ }
);
wfa.flowLogic.elseIf(
{ $id: Now.ID["check_p2"], condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=2` },
() => { /* ... */ }
);

// ✅ CORRECT - Comparing data pills from action outputs
const lookup = wfa.action(action.core.lookUpRecords, { $id: Now.ID["lookup"] }, { /* ... */ });
wfa.flowLogic.if(
{ $id: Now.ID["check_count"], condition: `${wfa.dataPill(lookup.Count, "integer")}>0` },
() => { /* ... */ }
);

Note: JavaScript functions work in table action conditions (lookUpRecords, updateMultipleRecords), but NOT in flow logic conditions.


Using Data Pills in Conditions​

Template Literal Pattern:

// ✅ Use data pills directly in template literal
condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=1`

Comparison Between Data Pills:

// ✅ Compare two data pills
condition: `${wfa.dataPill(params.trigger.current.assigned_to, "string")}=${wfa.dataPill(params.trigger.sys_updated_by, "string")}`

Complex Conditions:

// ✅ Multiple data pills in one condition
condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=1^${wfa.dataPill(params.trigger.current.active, "string")}=true`

Complete Example​

End-to-end flow combining if, forEach, skipIteration, exitLoop, and else:

import { Flow, wfa, trigger, action } from "@servicenow/sdk/automation";

Flow(
{
$id: Now.ID["process_high_priority_incidents"],
name: "Process High Priority Incidents"
},

wfa.trigger(
trigger.record.created,
{ $id: Now.ID["incident_created"] },
{
table: "incident",
condition: "priority=1^active=true",
run_flow_in: "background"
}
),

params => {
// Lookup related problems
const problems = wfa.action(
action.core.lookUpRecords,
{ $id: Now.ID["find_problems"] },
{
table: "problem",
conditions: "active=true",
max_results: 50
}
);

// Check if any problems found
wfa.flowLogic.if(
{
$id: Now.ID["check_problems_found"],
condition: `${wfa.dataPill(problems.Count, "integer")}>0`
},
() => {
// Process each problem
wfa.flowLogic.forEach(
wfa.dataPill(problems.Records, "array.object"),
{ $id: Now.ID["process_problems"] },
problem => {
// Skip resolved problems
wfa.flowLogic.if(
{
$id: Now.ID["check_resolved"],
condition: `${wfa.dataPill(problem.state, "string")}=6`
},
() => {
wfa.flowLogic.skipIteration({ $id: Now.ID["skip_resolved"] });
}
);

// Link problem to incident
wfa.action(
action.core.updateRecord,
{ $id: Now.ID["link_problem"] },
{
table_name: "incident",
record: wfa.dataPill(params.trigger.current, "reference"),
values: TemplateValue({
problem_id: wfa.dataPill(problem.sys_id, "reference")
})
}
);

// Exit loop after first match
wfa.flowLogic.exitLoop({ $id: Now.ID["exit_loop"] });
}
);
}
);

// If no problems, escalate
wfa.flowLogic.else({ $id: Now.ID["no_problems_found"] }, () => {
wfa.action(
action.core.updateRecord,
{ $id: Now.ID["escalate"] },
{
table_name: "incident",
record: wfa.dataPill(params.trigger.current, "reference"),
values: TemplateValue({ state: 3 })
}
);
});

// Log completion
wfa.action(
action.core.log,
{ $id: Now.ID["log_completion"] },
{
log_level: "info",
log_message: "Flow completed successfully"
}
);
}
);

Advanced Patterns​

Nested If Statements​

if statements can be nested within other if blocks for complex conditional logic:

wfa.flowLogic.if(
{
$id: Now.ID["check_active"],
condition: `${wfa.dataPill(params.trigger.current.active, "boolean")}=true`,
annotation: "Check if record is active"
},
() => {
// Nested if within outer if
wfa.flowLogic.if(
{
$id: Now.ID["check_priority"],
condition: `${wfa.dataPill(params.trigger.current.priority, "string")}=1`,
annotation: "Check if priority is critical"
},
() => {
wfa.action(
action.core.log,
{ $id: Now.ID["log"] },
{
log_level: "info",
log_message: "Active and critical priority incident detected"
}
);
}
);
}
);

Nested ForEach Loops​

forEach loops can be nested to process multi-dimensional data:

// Outer loop: process each incident
wfa.flowLogic.forEach(
wfa.dataPill(incidents.Records, "array.object"),
{ $id: Now.ID["process_incidents"], annotation: "Process each incident" },
incident => {
// Inner loop: process related problems for each incident
wfa.flowLogic.forEach(
wfa.dataPill(incident.related_problems, "array.object"),
{
$id: Now.ID["process_problems"],
annotation: "Process related problems"
},
problem => {
wfa.action(
action.core.log,
{ $id: Now.ID["log_problem"] },
{
log_level: "info",
log_message: `Processing problem ${wfa.dataPill(problem.number, "string")} for incident ${wfa.dataPill(incident.number, "string")}`
}
);
}
);
}
);