Loop Through Modules Dynamically

Prev Next

This pattern loads a different module for each step in a dynamic list. A Loop Initializer module contains a Data Workflow component that reads a list of module IDs, resolves the current one, and checks whether more remain. An Exclusive Gateway reads the result and routes the workflow back or ends it.

Apply this pattern when the number of modules is not known at design time. For example, use it when each participant in a list receives a different form.

You can run the entire workflow without end-user interaction by placing all Task nodes in the Automated swimlane instead of the Authenticated swimlane. Add a Timer Start node to  the workflow on a set schedule.

How the Loop Works

Each iteration follows four steps: initialize, present, check, and repeat.

Diagram

  1. The Loop Initializer task runs dwfLoop on first arrival (New Submission trigger). It also runs on each return visit (Edit trigger).

  2. dwfLoop reads moduleIds at the current counter position, writes the result to currentModuleId, increments counter, and computes hasNext.

  3. The Exclusive Gateway checks hasNext. If it equals yes, the workflow routes back to the Loop Initializer for the next iteration. If it equals no, the workflow ends.

What You Need

Before you begin, ensure you have the following:

  • A workflow with an Authenticated swimlane

  • A list of published module IDs to loop through

  • A module to serve as the Loop Initializer, built following the instructions in the next section

Configure the Loop Initializer Module

Create the Supporting Fields

Create a new module to serve as the Loop Initializer. Add four Hidden components to it. These fields carry state across iterations.

Property ID

Default Value

Purpose

moduleIds

Stores the list of module IDs to loop through (accepts a JSON array or a JSON array string)

counter

0

Tracks the current position in the list

currentModuleId

Holds the resolved module ID for the current iteration

hasNext

yes

Signals whether more modules remain. Set the default to yes so the first iteration proceeds.

The moduleIds field accepts either a native array or a JSON-encoded string like ["id1","id2","id3"]. The Data Workflow handles both formats automatically.

Configure dwfLoop

Add a Data Workflow component with Property ID dwfLoop. Build the following operators in order. Each group builds on the previous one.

Detect and normalize the module list

The following operators check whether moduleIds arrived as a JSON string. If it did, they parse it. Either way, both paths merge into a clean array for the next group.

  1. Add an  operator. This operator reads the moduleIds field at the start of each pass. Its output connects to the firstChar Get operator and the isStringBranch Branch Start operator.

    Configure the operator as follows:

    Setting

    Value

    Component

    moduleIds

    Required

    Yes

    Source

    Default

  2. Add a second Input operator. This operator reads the current counter value. Its output connects to the normalizeCounter Formula operator.

    Configure the operator as follows:

    Setting

    Value

    Component

    counter

    Required

    No

    Source

    Default

  3. Add a Get operator. This retrieves the first character of moduleIds so the next step can detect whether it arrived as a JSON string.

    Configure the operator as follows:

    Setting

    Value

    Label

    firstChar

    Path

    [0]

    Connect the output port (right) of the moduleIds Input operator to the input port (left) of the firstChar Get operator.

  4. Add a Formula operator. If the first character is [, moduleIds is a JSON string. The formula returns yes or no for the Branch Start.

    Configure the operator as follows:

    Setting

    Value

    Label

    isString

    Formula/Expression

    =IF(A="[","yes","no")

    Preserve Argument Type

    Default (Unchecked)

    Connect the output port (right) of the firstChar Get operator to the input port (left) of the isString Formula operator.

  5. Add a Branch Start operator. When the condition is true (the value is a JSON string), data flows through the upper output port (top-right) to the JSON Parse operator. When false, data flows through the lower output port (bottom-right) directly to Branch End.

    Configure the operator as follows:

    Setting

    Value

    Label

    isStringBranch

    Condition

    _arg="yes"

    Do Not Sanitize

    Checked

    Preserve Argument Type

    Checked

    Connect the output port (right) of the moduleIds Input operator to the input port (left) of the isStringBranch Branch Start operator. Connect the output port (right) of the isString Formula operator to the argument port (top) of the isStringBranch Branch Start operator.

  6. Add a JSON Parse operator. This converts the JSON string into a native array.

    Configure the operator as follows:

    Setting

    Value

    Label

    parseList

    Connect the upper output port (top-right) of the isStringBranch Branch Start operator to the input port (left) of the parseList JSON Parse operator.

  7. Add a Branch End operator. This merges both paths back into a single array output regardless of the original format.

    Configure the operator as follows:

    Setting

    Value

    Label

    mergeList

    Connect the output port (right) of the parseList JSON Parse operator to the input port (left) of the mergeList Branch End operator. Connect the lower output port (bottom-right) of the isStringBranch Branch Start operator to the second input port (left) of the mergeList Branch End operator.

Resolve the current module and advance the counter

The following operators count the total modules, resolve the module ID at the current counter position, increment the counter, and compute whether more modules remain.

  1. Add a Size operator. This counts the total number of module IDs in the list. Its output connects to the hasNext Formula operator.

    Configure the operator as follows:

    Setting

    Value

    Label

    size

    Connect the output port (right) of the mergeList Branch End operator to the input port (left) of the size Size operator.

  2. Add a Formula operator. On the first pass, the counter field is empty. This formula converts an empty value to 0 so the first module is read at index 0. Its output connects to the currentModuleId Get operator and the increment Formula operator.

    Configure the operator as follows:

    Setting

    Value

    Label

    normalizeCounter

    Formula/Expression

    =IF(A="",0,A)

    Preserve Argument Type

    Default (Unchecked)

    Connect the output port (right) of the counter Input operator to the input port (left) of the normalizeCounter Formula operator.

  3. Add a Get operator. The Get operator uses the normalized counter value as the index and retrieves the matching module ID from the array.

    Configure the operator as follows:

    Setting

    Value

    Label

    currentModuleId

    Path

    _arg

    Connect the output port (right) of the mergeList Branch End operator to the input port (left) of the currentModuleId Get operator. Connect the output port (right) of the normalizeCounter Formula operator to the argument port (top) of the currentModuleId Get operator.

  4. Add a Formula operator. This adds 1 to the current counter so the next iteration reads the following module in the list.

    Configure the operator as follows:

    Setting

    Value

    Label

    increment

    Formula/Expression

    =SUM(A,1)

    Preserve Argument Type

    Default (Unchecked)

    Connect the output port (right) of the normalizeCounter Formula operator to the input port (left) of the increment Formula operator.

  5. Add a Formula operator. If the incremented counter is less than the total module count, more modules remain and the formula returns yes. Otherwise it returns no.

    Configure the operator as follows:

    Setting

    Value

    Label

    hasNext

    Formula/Expression

    =IF(A<_arg,"yes","no")

    Preserve Argument Type

    Default (Unchecked)

    Connect the output port (right) of the increment Formula operator to the input port (left) of the hasNext Formula operator. Connect the output port (right) of the size Size operator to the argument port (top) of the hasNext Formula operator.

Configure the Outputs

Add three Output operators to write the results back to the module's fields.

  1. Add an Output operator. This writes the resolved module ID to the currentModuleId field.

    Configure the operator as follows:

    Setting

    Value

    Component

    currentModuleId

    Action

    value

    Connect the output port (right) of the currentModuleId Get operator to the input port (left) of this  operator.

  2. Add an Output operator. This writes the incremented counter back to the counter field so the next pass advances to the correct position.

    Configure the operator as follows:

    Setting

    Value

    Component

    counter

    Action

    value

    Connect the output port (right) of the increment Formula operator to the input port (left) of this Output operator.

  3. Add an Output operator. This writes yes or no to the hasNext field. The Exclusive Gateway reads this field to determine whether to loop or end.

    Configure the operator as follows:

    Setting

    Value

    Component

    hasNext

    Action

    value

    Connect the output port (right) of the hasNext Formula operator to the input port (left) of this Output operator.

The following image displays an example of a complete dwfLoop Data Workflow:

The complete dwfLoop Data Workflow in Unqork Module Builder, showing all 15 operators connected in sequence: two Input operators for moduleIds and counter, a Get and Formula for JSON string detection, a Branch Start routing to a JSON Parse and Branch End, followed by a Size operator, normalizeCounter Formula, currentModuleId Get, increment Formula, hasNext Formula, and three Output operators writing currentModuleId, counter, and hasNext.

Configure the Initializers

The Loop Initializer module needs two Initializer components to trigger dwfLoop at the right moments.

New Submission Initializer

Add an Initializer component with Property ID init. Configure the following settings:

Setting

Value

Trigger Type

New Submission

Set the output to trigger dwfLoop. This fires the first time an end-user reaches the Loop Initializer task, resolving the first module ID.

Edit Initializer

Add a second Initializer component with Property ID initEdit. Configure the following settings:

Setting

Value

Trigger Type

Edit

Set the output to trigger dwfLoop. This fires each time the workflow returns to the Loop Initializer task after a loop iteration. Because the submission already exists at that point, the task renders in edit mode and triggers this Initializer instead of the New Submission one.

The Edit Initializer is what advances the loop on each return visit. Without it, dwfLoop only runs once and the counter never advances.

Configure the Workflow

Add the Data Setup Task

Add a Task node in the Authenticated swimlane to collect or populate moduleIds before the loop begins. Configure the following settings:

Setting

Value

Screen Type

Static

Module

Select the module that captures or populates moduleIds

This task runs once before the loop. It gives end-users or an upstream process a chance to define the list of module IDs that dwfLoop will iterate through.

Add the Loop Initializer Task

Add a Task node in the Authenticated swimlane for the Loop Initializer module. Configure the following settings:

Setting

Value

Screen Type

Static

Module

Select the Loop Initializer module you built in the previous section

Save Data

ON

Validate Data

ON

This task runs dwfLoop on first arrival (New Submission) and on each return visit (Edit). The module runs the Data Workflow silently and writes currentModuleId, counter, and hasNext to the submission. No visible screen is shown to the end-user.

Configure the Exclusive Gateway

Add an Exclusive Gateway after the Loop Initializer task. Configure the following output links:

Loop  (continue): Set Condition to hasNext='yes' and connect this link back to the Loop Initializer task.

End path (stop): Set Condition to hasNext='no' and connect this link to your completion task or End Event.

The gateway evaluates the hasNext field that dwfLoop wrote on the most recent pass. A value of yes routes the workflow back through the loop. A value of no proceeds to the end.

Connect the Workflow

Wire the nodes in the following order:

  1. Start Event → Data Setup Task

  2. Data Setup Task → Loop Initializer Task

  3. Loop Initializer Task → Exclusive Gateway

  4. Exclusive Gateway (hasNext='yes') → Loop Initializer Task

  5. Exclusive Gateway (hasNext='no') → End Event

The following image displays an example of a complete module looping workflow:

A complete module looping workflow in Unqork Workflow Builder, showing a Start event connecting to a Data Setup task, then a Loop Initializer task, then an Exclusive Gateway. When hasNext equals yes, the gateway routes back to the Loop Initializer for the next iteration. When hasNext equals no, the gateway routes to the End event.

Test the Workflow

Open the workflow in Express View to verify the loop. On the first visit, the Loop Initializer task runs dwfLoop and resolves the first module ID. After each module completes, the Exclusive Gateway reads the hasNext field. If the value is yes, the gateway routes back to the Loop Initializer for the next iteration. When the final module is processed, dwfLoop sets hasNext to no and the gateway routes the workflow to the End Event or completion task.


Changelog

Date

Change

2026-09-18

Added Test the Workflow section. Removed Processing Task step — gateway routes directly to Loop Initializer. Added autonomous workflow note. Updated Mermaid diagram and workflow screenshot.

2026-09-18

Rewrote Steps 1 through 8 based on verified working implementation. Corrected DWF operator sequence (Branch Start/End replaces Decision; JSON Parse handles string moduleIds; counter normalized before use; hasNext computed by Formula). Removed dwfOperation and initRepeatLoop (replaced by Edit Initializer). Corrected Exclusive Gateway condition syntax to hasNext='yes'/'no'.

2026-09-17

Initial publication.