--- title: "Loop Through Modules Dynamically" slug: "loop-through-modules-dynamically" updated: 2026-09-21T14:35:03Z published: 2026-09-21T14:35:03Z canonical: "docs.unqork.io/loop-through-modules-dynamically" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.unqork.io/llms.txt > Use this file to discover all available pages before exploring further. # Loop Through Modules Dynamically 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. > [!NOTE] > 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](https://cdn.us.document360.io/15548f06-2fba-434c-b0c1-5df88317dbd7/Images/Documentation/api_upload_20260921_142926_26db29468b82413a8b2a5c20f4f68ff4.svg?sv=2026-02-06&spr=https&st=2026-11-01T01%3A09%3A50Z&se=2026-11-01T02%3A09%3A50Z&sr=c&sp=r&sig=K9zRYcEd3OSBKh9hS1aJbR6yv%2BKj0AHAfVilmd%2BMwQk%3D) 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. | > [!NOTE] > 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.](https://cdn.us.document360.io/15548f06-2fba-434c-b0c1-5df88317dbd7/Images/Documentation/api_upload_20260921_142926_e9bc1a8e22ae4016968dca0cb9e87cef.webp?sv=2026-02-06&spr=https&st=2026-11-01T01%3A09%3A50Z&se=2026-11-01T02%3A09%3A50Z&sr=c&sp=r&sig=K9zRYcEd3OSBKh9hS1aJbR6yv%2BKj0AHAfVilmd%2BMwQk%3D) ### 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. > [!NOTE] > 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.](https://cdn.us.document360.io/15548f06-2fba-434c-b0c1-5df88317dbd7/Images/Documentation/api_upload_20260921_142926_d143fe88ef394acd982a635cb74cbaf0.webp?sv=2026-02-06&spr=https&st=2026-11-01T01%3A09%3A50Z&se=2026-11-01T02%3A09%3A50Z&sr=c&sp=r&sig=K9zRYcEd3OSBKh9hS1aJbR6yv%2BKj0AHAfVilmd%2BMwQk%3D) ## 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. | End-users, also known as Express Users, are the individuals accessing an application through Express View. In most cases, end-users are the customers using the product. The component fires when the page or called module loads without an existing submission ID. Dashboards and remote execute modules are great uses for this trigger. A Property ID is the unique field ID used by Unqork to track and link components in your module. JSON (JavaScript Object Notation) is an open standard file and data interchange format. Unqork uses JSON for submission (record) data. An array is a type of object that stores one or more data types. Data types supported in arrays include numbers, strings, and objects. A string is an object that represents a sequence of characters. Strings typically hold data represented in text form. An app versioning Merge combines changes from one branch or draft, into another branch, draft, or published version of an application. Label Text conveys what the input component is and what information it displays. Enter the purpose of the corresponding component or field. The location of an element in an array. Enter outputs components and actions you want the component to perform. Deteremines how the component triggers. Rendering is the process of loading components and displaying the final result on the front-end screen. End-users, also known as Express Users, are the individuals accessing an application through Express View. In most cases, end-users are the customers using the product. Express View is how your end-user views your application. Express View also lets you preview your applications to test your configuration and view the styling. This is also the view your end-users will see when interacting with your application. After configuring a module, click Preview in the Module Builder to interact with the module in Express View.