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
| Type | Constructs | Use For |
|---|---|---|
| Conditional | if, elseIf, else | Branching |
| Loops | forEach, skipIteration, exitLoop | Iteration |
| Control | endFlow | Flow 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
| Pattern | Flow Logic Construct | Use When |
|---|---|---|
| Route to different actions | if/elseIf/else | Different actions for different conditions |
| Process a list of records | forEach | Batch processing, multiple records |
| Stop processing early | exitLoop | Found target record, limit reached |
| Skip records in processing | skipIteration | Filtering, validation failed |
| Stop entire flow | endFlow | Early 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
- Use Template Literals -- Always wrap data pill conditions:
`${wfa.dataPill(...)}=value` - Single Equals for Comparison -- Use
=not==(ServiceNow encoded query format) - Check Empty Values -- Use
ISEMPTY/ISNOTEMPTYoperators for null, undefined, or empty string checks - Complex Conditions -- Use
^for AND,^ORfor OR:- AND:
`${wfa.dataPill(field1)}=1^${wfa.dataPill(field2)}=2` - OR:
`${wfa.dataPill(field1)}=1^OR${wfa.dataPill(field2)}=2`
- AND:
- Reference Field Comparisons -- Compare sys_id values using
.value, not display values - Avoid Deep Nesting -- Prefer
elseIfchains over nestedifstatements for readability - 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>0before 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/elseblocks evaluate in order. Once matched, subsequent blocks are skipped. elseIf/elsemust followif-- StandaloneelseIforelseis 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
forEachloops for per-item processing - Combine with
exitLoop/skipIterationfor 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
- Limit Record Processing -- Use
max_resultsinlookUpRecordsto prevent timeouts. Keep iterations under 100-200 records for optimal performance. - Use
skipIterationfor Filtering -- Skip items that don't meet criteria instead of wrapping the entire loop body in conditional logic. - Use
exitLoopfor Early Exit -- Stop loop when goal is achieved (e.g., finding first match). This improves performance by avoiding unnecessary iterations. - Consider Batch Actions -- For simple updates without complex logic,
updateMultipleRecordsis more efficient thanforEach+updateRecord. - Validate Loop Input -- Check that the array/records exist before looping to avoid flow errors on empty results.
Common Use Cases
| Scenario | Pattern | Notes |
|---|---|---|
| Bulk assignment | forEach + updateRecord | Consider updateMultipleRecords if simple |
| Team notifications | forEach + sendNotification/sendEmail | Personalize message per recipient |
| Find first available | forEach + if + exitLoop | Stop when first match found |
| Filter and process | forEach + if + skipIteration | Skip items not meeting criteria |
| Cascading updates | forEach + nested lookups + update | Update related records for each item |
Performance Considerations
forEach loop behavior:
- No hard technical limit --
forEachwill 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_resultsbased on expected workload - Use
skipIterationto filter early and reduce processing - Use
exitLoopwhen you find what you need - For very large datasets, use batch processing with multiple scheduled flow executions
Warning signs:
- ⚠️
lookUpRecordswithoutmax_resultsparameter - ⚠️
forEachloops with >200 iterations - ⚠️ Heavy operations (API calls, complex updates) within loops
- ⚠️ Nested
forEachloops 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 --
forEachprocesses 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.dataPillwith the item reference to access fields. - Break and Continue -- Use
exitLoop(likebreak) to stop the loop entirely;skipIteration(likecontinue) 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
skipIterationinstead) - ❌ Terminating entire flow (use
endFlowinstead)
Best Practices
-
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"}); -
Use with Conditionals --
exitLoopshould always be insideif/elseIfblocks. UnconditionalexitLoopmakes the loop pointless. -
exitLoopvsskipIteration:- 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)
- Use
-
exitLoopvsendFlow:exitLoop: Exits current loop, continues flow execution after loopendFlow: Terminates entire flow immediately
-
Nested Loops -- In nested loops,
exitLooponly exits the innermost loop. To exit multiple levels, use flags or multipleexitLoopstatements.
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 --
exitLooponly exits the currentforEachloop. 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 --
exitLoopdoes not return a value. Use conditional logic or update records to track exit reason. - Immediate Exit --
exitLooptakes effect immediately. Any code afterexitLoopin 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
-
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 timewfa.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}); -
Add Annotations -- Explain why iteration is skipped for better debugging and flow execution history understanding.
-
Use with Conditionals --
skipIterationshould always be insideif/elseIfblocks. UnconditionalskipIterationmakes the loop pointless. -
Multiple Skip Conditions -- Use multiple
ifstatements withskipIterationfor different skip reasons. Each can have its own annotation for clarity. -
skipIterationvsexitLoop:skipIteration: "Skip inactive users but process active ones" (continues loop)exitLoop: "Found the user we need, stop searching" (stops entire loop)
-
Performance Benefit --
skipIterationimproves 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 --
skipIterationskips the current iteration and immediately moves to the next item. The loop continues processing remaining items. - Immediate Effect -- Once
skipIterationexecutes, no further code in that iteration runs. Control jumps to the next iteration. - No Return Value --
skipIterationdoes not return a value. Use counters or logs to track skipped items if needed. - Nested Loops -- In nested loops,
skipIterationonly 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
-
Use Trigger Conditions First -- Filter at trigger level when possible to avoid unnecessary flow executions. This improves system performance and reduces flow execution logs.
-
Combined Conditions -- Use AND (
^) and OR (^OR) in trigger conditions instead of multipleendFlowchecks. -
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"}); -
Use Conditionally -- Always use
endFlowinside conditional blocks. UnconditionalendFlowat start of flow makes the trigger pointless. -
Handle Edge Cases -- Use
endFlowto 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 --
endFlowimmediately terminates the entire flow. No subsequent actions, logic, or loops will execute. - No Return Value --
endFlowdoes not return a value or set output variables. The flow simply stops. - Execution Status -- Flows that end via
endFlowshow as "Completed" in execution logs. Use annotations to distinguish. - Nested Contexts --
endFlowterminates the entire flow even when called from inside loops, conditionals, or nested logic. - Different from
exitLoop--exitLoopexits a loop but continues flow.endFlowterminates 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
- Independent Operations Only -- Only use for operations that don't depend on each other's results. Each block should be self-contained.
- No Nesting --
doInParallelcannot be nested inside anotherdoInParallelblock. This is enforced at build time. - Error Handling -- If one parallel block fails, other blocks continue executing. Use
tryCatchinside blocks for error handling. - Keep Blocks Simple -- Each parallel block should have a clear, single purpose. Complex logic makes debugging difficult.
- Avoid Shared State -- Don't modify the same flow variables or records across multiple parallel blocks.
Common Use Cases
| Scenario | Pattern | Notes |
|---|---|---|
| Multiple notifications | doInParallel + multiple sendNotification | Send to different users/groups simultaneously |
| Logging + updates | doInParallel + log + updateRecord | Log and update can happen in parallel |
| Multi-table updates | doInParallel + multiple updateRecord | Update unrelated tables at once |
| Parallel API calls | doInParallel + multiple REST actions | Execute 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
- Specific Error Handling -- Use catch blocks for specific error scenarios, not as a catch-all.
- Log Errors -- Always log in the catch block to track failures and aid debugging.
- Provide Fallbacks -- Catch blocks should provide meaningful fallback behavior, not just log and continue.
- Nested Try-Catch -- Can be nested for granular error handling at different levels.
- Don't Overuse -- Not every action needs try-catch. Use for operations with known failure modes.
Common Use Cases
| Scenario | Pattern | Notes |
|---|---|---|
| API call failures | tryCatch + REST action + fallback | Handle external service unavailability |
| Lookup validation | tryCatch + lookUpRecord + default | Provide default when record not found |
| Record creation | tryCatch + createRecord + log | Handle duplicate or validation errors |
| Integration errors | tryCatch + integration action + notification | Alert 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
tryandcatchmust 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
- Array.Object Only -- Only works with
FlowArray({ elementType: FlowObject(...) })variables. Other array types are not supported. - Declare Variables First -- Flow variables must be declared in the flow/subflow config before appending.
- Object Elements -- When appending array literals, each element must be an object or datapill, not primitives.
- Initialize Arrays -- Arrays start empty. No need to initialize before first append.
- Multiple Appends -- Can append to the same variable multiple times throughout the flow.
Common Use Cases
| Scenario | Pattern | Notes |
|---|---|---|
| Collect loop results | forEach + appendToFlowVariables | Build array from loop iterations |
| Aggregate data | Multiple appendToFlowVariables | Collect data from different sources |
| Build notification list | Conditional + appendToFlowVariables | Dynamically build recipient list |
| Accumulate records | lookUpRecords + forEach + append | Filter 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")}`
}
);
}
);
}
);