---
tags: [flow-logic, wfa, workflow-automation, flow-designer, conditional, loop, if-else, forEach, exit-loop, skip-iteration, end-flow, condition-syntax, doInParallel, parallel-execution, tryCatch, try-catch, error-handling, appendToFlowVariables, append-flow-variables, array-manipulation]
---

# 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](../api/flow/wfa-flow-logic-api.md).

---

## 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](../api/flow/wfa-flow-logic-api.md#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

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

```typescript fluent
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

| 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** -- `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

```typescript fluent
// 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.

   ```typescript fluent
   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

```typescript fluent
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.

   ```typescript fluent
   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

```typescript fluent
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:**

```typescript fluent
// 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.

   ```typescript fluent
   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

```typescript fluent
// 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, conditionals, or 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

| 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

```typescript fluent
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

| 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

```typescript fluent
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

```typescript fluent
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

| 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

```typescript fluent
// 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

```typescript fluent
// 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

```typescript fluent
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

```typescript fluent
// 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.)

```typescript fluent
// ❌ 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:**

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

**Comparison Between Data Pills:**

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

**Complex Conditions:**

```typescript fluent
// ✅ 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`:

```typescript fluent
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:

```typescript fluent
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:

```typescript fluent
// 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")}`
          }
        );
      }
    );
  }
);
```