This document describes how loop nodes are implemented in our workflow builder, following ActivePieces' architecture.
Loop nodes allow iterating over arrays of items, executing a sequence of actions for each item in the array. Each loop node can contain nested actions, including other loops and routers.
export interface LoopOnItemsAction extends FlowAction {
type: FlowActionType.LOOP_ON_ITEMS
settings: {
items: string // Expression that evaluates to an array
}
firstLoopAction?: FlowAction // First action inside the loop
}- Standard step node with "Loop" action type
- Displays loop icon and configured array expression
- Width: 260px, Height: 70px
The loop implementation uses two specialized edge types:
- Connects the loop node to its first child action
- Features a curved path that goes right then down
- Includes an add button for adding the first action when loop is empty
- Creates the visual "loop back" connection
- Two-segment path with:
- Left curve from last action to return node
- Right-pointing arrow indicating loop direction
- Vertical line back to the loop's end
- Includes add button for adding actions after the last item
- Special invisible node that marks the end of loop execution
- Positioned to the left of the loop's child actions
- Acts as the target for the return edge
- Build Child Graph: Recursively build the graph for all actions inside the loop
- Calculate Bounding Box: Determine the total size of the child graph
- Horizontal Offset: Position child actions to the right of the loop node
- Return Node Positioning: Place return node to the left, centered vertically
VERTICAL_OFFSET_BETWEEN_LOOP_AND_CHILD = VERTICAL_SPACE_BETWEEN_STEPS * 1.5 + 2 * ARC_LENGTH
HORIZONTAL_SPACE_BETWEEN_NODES = 120pxThe implementation properly handles nested structures:
- Nested loops maintain proper spacing and alignment
- Routers inside loops follow standard branch layout
- Bounding box calculations account for all nested structures
M [sourceX] [startY]
v[verticalDistance/2]
arc-right-down
h[horizontalDistance]
arc-right
v[verticalDistance]
arrow-down (if not empty)
Complex two-segment path:
- Return Path: From last action to return node with left curve
- Arrow Segment: Horizontal line with right-pointing arrow
- Vertical Return: Line back to loop end position
- Shows a big add button inside the loop area
- Clicking creates the first action with
firstLoopActionproperty
- Plus buttons appear on edges between actions
- New actions are inserted in the action chain
- Plus button on the return edge
- Actions added maintain the loop structure
- Items Expression: JavaScript expression that evaluates to an array
- Examples:
[1, 2, 3, 4, 5]- Static arraytrigger.items- Array from trigger datapreviousStep.data.results- Array from previous step
- Create child graph from
firstLoopActionor empty graph - Calculate positioning offsets based on child graph size
- Create loop return node at calculated position
- Create subgraph end node for proper spacing
- Generate loop edges with proper data attributes
- Return merged graph with correct node ordering
verticalSpaceBetweenReturnNodeStartAndEnd: Uses child graph height + vertical spacing- Subgraph end Y position: Includes ARC_LENGTH for proper curve alignment
- Node ordering: [loopReturnNode, ...childNodes, subgraphEndNode]
- Expression Validation: Ensure loop items expression evaluates to an array
- Nested Structures: Test loops containing routers and other loops
- Empty State: Handle empty loops gracefully with clear UI
- Performance: Consider limits for very large arrays
ApLoopStartEdge.tsx: Loop start edge renderingApLoopReturnEdge.tsx: Loop return edge with arrowLoopSettings.tsx: Configuration panel for loop itemsgraphUtils.ts: ContainsbuildLoopChildGraphfunction