Skip to content

Loop Task

Overview

The Loop task runs the tasks beneath it once for every item in a list. Use it to email each contact in a query result, sync a batch of records, or process every line item on an order.

A loop dispatches one iteration per pass. It does not fan the whole list out at once — see How a loop runs, which is the part that most affects how long a loop takes and what it does to anything it calls.

flowchart TD
    A[Loop over the list] -->|one iteration per pass| B[Body tasks for the current item]
    B --> C{More items?}
    C -->|yes| A
    C -->|no| D[Wait for all body work to finish]
    D --> E[done branch runs once]

When to use this task:

  • Send a personalised message to each contact in a list
  • Push each row of a query result to another system
  • Process every item on an order
  • Repeat a fixed number of times

What a loop gives you:

  • One iteration per item, in forward or reverse order
  • The current item in loop_value, and its position in loop_index
  • A separate done branch that runs once after everything finishes
  • Counts of what happened, on that done branch
  • Nesting, with each loop tracking its own position

What this task cannot do

You may want to… This task Do this instead
Stop the loop early No break input exists. Every item is dispatched. Put an If Statement at the top of the body so unwanted items do nothing.
Cap the number of iterations No max-iterations input exists. Shorten the list before it reaches the loop.
Skip empty or invalid items Every item is dispatched, including empty strings. First task in the body is an If Statement that tests loop_value.
Process the whole list at once Iterations are dispatched one per pass, deliberately. Nothing to change — this is a rate limiter, not a fault. See How a loop runs.
Read the current item on the done branch The loop is over; there is no current item. Use the totals (total_items, iterations_completed, …).
Change the list while it is running The list is fixed when the loop first runs. Build the final list before the loop.

An item that should not be processed still costs an iteration. Filtering inside the body is how you skip work, not how you skip the pass.

Quick Start

  1. Add Loop task to workflow
  2. Specify array to loop through
  3. Add tasks after Loop task
  4. Configure tasks to use loop item data
  5. Test with small dataset
  6. Save

Simple Example:

Loop through: {{task_43001_results_JSON}}
Access current item: {{task_29001_email}}

Configuring the Loop

The Loop task configuration panel. Loop Type is set to "For each item in a list", List Separator to "Comma", Direction to "Forward", and Children Task Execution Method to "All loop items are executed in parallel". A note explains that the separator splits the list into individual items, so with a comma and a value of 1,2,3 the loop iterates over 1, then 2, then 3. The Value field contains 1,2,3

The field labels in the builder do not use the key names. Loop Type is action, List Separator is delimiter, Value is loop_array, and Children Task Execution Method is execution_type.

Array Source

What you point Loop Array at depends on where the list comes from.

A delimited string — the default, and the simplest:

Loop Array: alice@example.com,bob@example.com,carol@example.com
Action:     items

An array from the trigger:

Loop Array: {{task_46001.JSON.customers}}
Action:     array

Rows from a sheet:

Loop Array: {{task_44001.JSON.rows}}
Action:     array

Rows from a MySQL Query — the one case with no native equivalent, because no other task returns an arbitrary filtered list of CRM records:

Loop Array: {{task_43001.JSON.results}}
Action:     array

A fixed number of repeats:

Loop Array: 5
Action:     value

Use .JSON for arrays, not the flattened form

{{task_46001.JSON.customers}} passes the array itself. The flattened form ({{task_46001_customers_0_name}}) reaches one item and is not what a loop wants.

Inputs

Builder fields

The Field column is the label as it appears in the task builder; Key is the name the value is stored under and referenced by.

Field Key Type Default Notes
Loop Type action select items Options: For each item in a list · For each item in a JSON array · Repeat a specific number of times
List Separator delimiter select , Options: Comma , · Semi Colon ; · Pipe Character |. Only shown when action is items
Direction direction select forward Options: Forward · Reverse. Only shown when action is items, array
Children Task Execution Method execution_type select asynchronous Options: All loop items are executed in parallel · Next loop item only executes after all children have completed
Value loop_array textarea –
Key Type Required Default What it does
loop_array string yes – The list to iterate. How it is read depends on action. An empty value is not an error: the loop completes with 0 iterations and goes straight to done.
action string no items How loop_array is interpreted. One of items, array, value — see below. Anything else fails the task with No valid action provided.
delimiter string no , Separator used when action is items. An empty value falls back to a comma.
direction string no forward forward starts at the first item; any other value walks the list from the end.
execution_type string no asynchronous synchronous makes each iteration wait for the previous one to finish. Any other value — including a missing or corrupt one — is treated as asynchronous.

action — how the list is read

Value loop_array holds Result
items Delimited text: alice@x.com,bob@y.com Split on delimiter. The default, and what most workflows use.
array A JSON array: [{"id":1},{"id":2}] Parsed as JSON. Not an array, or not valid JSON, fails the task.
value A number: 5 Counts 1..5. Must be a number greater than 0.

Outputs

Field names are exactly as the task emits them. Reference them as {{task_<ID>_<field>}}.

On every pass, available to the body tasks

Field Type Example Notes
run boolean true false when the configuration is invalid.
run_text string Loop value: alice@x.com Carries the current value.
loop_value string or object alice@x.com The current item. An object when action is array. This is the field body tasks use.
loop_index number 0 Position of the current item, zero-based.
loop_index_end number 2 Index of the last item — length - 1.
new_loop_index number or END 1 The position the loop will use on its next pass. Becomes the string END on the final pass. Internal bookkeeping; you rarely read it.
loop_array array ["a","b","c"] The parsed list.
action string items The action that was used.
delimiter string , The delimiter that was used.
direction string forward The direction that was used.

loop_index is not the same thing inside a nested loop

On an ordinary task, loop_index answers "which iteration of the loop above me am I part of". An inner Loop task keeps the outer loop's loop_index intact — that is what makes {{loop_value}} from the outer loop resolve correctly inside the inner loop's configuration. An inner loop's own position lives in its own output, not in that column.

On the done branch only

See Reading what the loop did below for how these are used.

Field Type Notes
total_items number How many items the loop was given. 0 for an empty list.
iterations_completed number Iterations that ran and did not fail. Deliberately not total_items minus errors — a loop that stopped early would otherwise report items it never dispatched as successful.
iterations_errored number Iterations in which a task failed.
tasks_run number Total tasks run across every iteration.

Rare

Field Type Notes
already_finished boolean true only when a loop that had already finished its list is run again — a retry after the final pass. It re-reports END and does not run the body a second time.

Running Tasks After the Loop

A loop on the BaseCloud canvas. A Webhook In trigger feeds a Loop task, which has two output ports. The upper port, labelled "done", leads to a Create PDF task and then an Email task. The lower port leads to a WhatsApp Business task, whose output curves back along the bottom of the canvas into the Loop node through a connector marked with a loop symbol

Two ports, and the difference matters. The lower port is the body — it runs once per item and feeds back into the loop, which is the return edge along the bottom. The upper done port runs once, after every item has been dispatched and the whole body has drained. Tasks that should run per item go on the body; tasks that should run once at the end go on done.

The Loop task has two outputs.

Output Runs
the main output (unlabelled) once per item, for every item
done once, after the whole loop has finished

Drag from the done handle — the lower one on the right of the node — to attach tasks that should run after every item has been processed. Use it for the things you only want to happen once: a summary email, a status write-back, a single notification.

done waits for all the work, not just the last item. If your loop runs asynchronously, several items are in flight at the same time; the done branch still waits for every one of them to finish before it runs.

Reading what the loop did

Tasks on the done branch can use these:

Field Description
task_[ID]_total_items How many items the loop was given
task_[ID]_iterations_completed How many finished without error
task_[ID]_iterations_errored How many had a task fail
task_[ID]_tasks_run Total tasks run across every iteration

Example — a summary after a bulk send:

1. Loop         - over a list of addresses
2.   Email      - Send to {{task_29001_loop_value}}   <- main output, runs per item
4. done → Email - "Sent {{task_29001_iterations_completed}} of
                   {{task_29001_total_items}}, {{task_29001_iterations_errored}} failed"

Only send the summary if nothing failed:

done → If Task - {{task_29001_iterations_errored}} equals 0
       True:  Email the success summary
       False: Create a Workflow Note to investigate

Things to know

  • Per-item fields such as {{task_29001_loop_value}} are not available on the done branch. The loop is over, so there is no current item. Use the totals above instead.
  • An empty list runs the done branch with total_items of 0, and does not run the main output at all. "Nothing to do" still reaches your notification step.
  • If the Loop task itself fails — for example the input is not valid JSON — neither output runs.
  • A Delay inside the loop body holds the done branch until that delay fires. A loop with a 30-day delay in it will not reach done for 30 days. That is expected, not a fault.
  • Nested loops each get their own done. An inner loop's done runs once per outer iteration; the outer loop's done waits for all of them.

Tasks Inside Loop

The current item is {{task_<ID>_loop_value}}. When the items are objects, reach their fields with the flattened form — {{task_29001_loop_value_email}} for {"email": "..."}.

Message each person in a list

Loop            over a comma-separated list of addresses
  └─ Email      to {{task_29001_loop_value}}

Update a record per item

Webhook In
  └─ Loop                  over {{task_46001.JSON.records}}, action: array
      └─ Match to Client   contact_email: {{task_29001_loop_value_email}}
          └─ Edit Client   update the matched client

Skip items that do not qualify

Loop                    over the list
  └─ If Statement       {{task_29001_loop_value_status}} equals "active"
      └─ Webhook Out    only runs for active items

Filtering happens inside the body — every item still costs an iteration. See What this task cannot do.

Space out calls to a rate-limited API

Loop                  over the list
  └─ Webhook Out      send {{task_29001_loop_value}}
      └─ Delay        1 second

A loop already dispatches one iteration per pass, so this is only needed when the body itself must slow down further.

How a loop runs

This section describes timing, and it is the part that decides how long a loop takes and how hard it hits whatever the body calls.

One iteration per pass

A loop dispatches one iteration each time the automation runner picks it up — roughly every five seconds in production. It does not dispatch the whole list at once.

That is deliberate. A loop over 1,000 items ramps up over roughly an hour and a half rather than pushing 1,000 subtrees into the queue at once, which matters when the body sends email or calls a rate-limited API.

Parallel or one at a time

The Children Task Execution Method setting decides whether the loop waits between items.

Builder option Key value What happens
All loop items are executed in parallel (default) asynchronous The loop starts an item, then moves on to the next without waiting for the first to finish. Items overlap.
Next loop item only executes after all children have completed synchronous The loop starts an item and waits until every task in it has finished before starting the next. Items run one at a time.

What "parallel" actually means

Automations run in passes: BaseCloud checks for waiting work every 5 seconds at most.

  • Inside an item, nothing waits for a pass. When an item starts, its tasks run straight through, one after another, in that same pass.
  • Between items, the loop does wait for a pass. A loop starts one item per pass, whichever option you choose.

So in parallel mode the items do not all start at the same moment. They start at least 5 seconds apart, and each runs alongside the ones already going.

5 seconds is the best case

A pass does not start until the previous one has finished, so when the automation queue is busy the gap between items stretches to however long a pass takes. A pass is also skipped entirely while the database is working through a very slow query. The timings below assume a quiet queue.

Three items, each with a 60-second Delay in the body, on a quiet queue:

Parallel One at a time
Item 1 starts at 0s, finishes at ~60s starts at 0s, finishes at ~60s
Item 2 starts at ~5s, finishes at ~65s starts at ~65s, finishes at ~125s
Item 3 starts at ~10s, finishes at ~70s starts at ~130s, finishes at ~190s
done runs after about 70 seconds after about 3 minutes 10 seconds

Parallel saves time by letting the slow parts overlap, not by starting everything together.

Which to choose

  • Parallel is the default and right for most loops — sending a message to each contact, updating each record. The whole loop takes roughly as long as its slowest item, plus at least 5 seconds for every item in the list.
  • One at a time is for when an item depends on the one before it having finished, when the order things happen in matters, or when a service you call cannot handle several requests at once.

When a parallel loop runs one at a time anyway

A loop whose items change a variable created outside the loop runs one item at a time, even when it is set to parallel. Every Loop Task's node carries a "Sequential" or "Parallel" badge showing how it will really run; hover it to see why, and the loop's panel names the task responsible.

This is not a setting being ignored — it is the only way that loop can be correct. Changing a variable means reading its value, adding to it, and writing it back. With items overlapping, two of them read the same value and the second write erases the first, so a summary built this way quietly comes out missing most of its items.

To get the speed back, use the Variable task's Append action instead. Append adds to a variable in one step, so items can safely overlap and the loop keeps running in parallel. Your setting is never changed: remove or change the task and the loop goes back to parallel on its own.

Only changing an outside variable does this. A variable created inside the loop belongs to that item alone, and appending or reading never causes it.

Worth knowing

  • A long list takes a while to get going, even in parallel. At one item per pass, 200 items take at least 1,000 seconds — around 17 minutes, longer on a busy queue — before the last one starts. A useful side effect: a big loop never hits an external service with all of its requests at the same instant.
  • In parallel, do not rely on the order items finish in. An item with less work to do can finish before one that started earlier.
  • Neither option hands you all the results at once. If a step needs every item to be finished, connect it to the done port — it runs once, after everything beneath the loop is complete.

When a loop reports complete

A loop is finished only when everything beneath it is finished — not when the last item was dispatched.

Situation What the loop is doing
More items to dispatch Waiting for its next pass
Every item dispatched, bodies still running Waiting for its subtree to drain
Everything beneath it finished Complete — the done branch runs

This is why a single slow task deep inside the body holds the whole loop open, and why a Delay in the body postpones done by that much. It is also why nesting works: an inner loop stays unfinished while its own body runs, so the outer loop waits for it.

Real-World Examples

Send a personalised email to each address on a list

Schedule
  └─ Loop         over "alice@example.com,bob@example.com,carol@example.com"
      └─ Email    to {{task_29001_loop_value}}

Three iterations, one email each, dispatched one per pass.

Process every line item on an order

Webhook In
  └─ Loop               over {{task_46001.JSON.items}}, action: array
      └─ Math Formula   {{task_29001_loop_value_quantity}} * {{task_29001_loop_value_price}}

Work through rows from a sheet

Schedule
  └─ Google Sheets       get_rows
      └─ Loop            over {{task_44001.JSON.rows}}, action: array
          └─ SMS         send to {{task_29001_loop_value_phone}}

Report once, after everything has finished

Schedule
  └─ Loop              over the list
      ├─ body ─────── Email    to {{task_29001_loop_value}}
      └─ done ─────── Email    "Sent {{task_29001_iterations_completed}} of
                                {{task_29001_total_items}}, {{task_29001_iterations_errored}} failed"

The done branch runs once, after every iteration has finished — see Running Tasks After the Loop.

Nested Loops

A loop inside a loop works, and each tracks its own position.

Webhook In
  └─ Loop  (outer)          over {{task_46001.JSON.orders}}, action: array
      └─ Loop  (inner)      over {{task_29001_loop_value_items}}, action: array
          └─ Webhook Out    send {{task_29002_loop_value}}

Two things to know:

  • The inner loop's configuration can still reference the outer loop's current item. That is what makes "for each order, loop over that order's items" resolve to the right order.
  • Each loop gets its own done branch. The inner one runs once per outer iteration; the outer one waits for all of them.

Nesting multiplies the passes

Ten orders with ten items each is a hundred iterations, dispatched one per pass. Keep the outer list short.

Best Practices

Performance

  1. Limit iterations - Process in batches (100-500 per run)
  2. Add delays - Rate limit API calls (0.5-1 second)
  3. Paginate large datasets - Multiple scheduled runs
  4. Filter before looping - Use SQL WHERE to reduce items
  5. Break early - Stop loop when condition met

Error Handling

  1. Try-catch in Code tasks - Handle errors per iteration
  2. Check success - Verify operations completed
  3. Log failures - Track which items failed
  4. Continue on error - Don't stop entire loop for one failure
  5. Retry logic - Re-process failed items later

Data Quality

  1. Validate before loop - Check array exists and isn't empty
  2. Handle missing fields - Provide defaults
  3. Check null values - Skip invalid items
  4. Deduplicate - Ensure unique items
  5. Sort order - Process in logical sequence

Maintainability

  1. Name loops clearly - "Loop through customers" not "Loop 1"
  2. Comment complex logic - Explain iteration purpose
  3. Keep simple - Max 5-10 tasks in loop
  4. Extract complex operations - Use Code task for calculations
  5. Test with small dataset - Verify logic before production

Troubleshooting

The done Branch Has Not Run

This is usually not a fault. done waits for every iteration's tasks to finish, so it will not run while any of them is still going.

Check, in this order:

  1. Is there a Delay inside the loop body? A 30-day delay means done will not run for 30 days, per item. This is the most common cause by far, and it is working as designed.
  2. Is a task in the body waiting on something external? A rate-limited API call backs off and retries; the loop waits for it.
  3. Does the loop node show as running on the canvas? Hover it. A loop that has dispatched everything and is waiting says so in its tooltip. Running means healthy.
  4. Only if the loop shows as failed is something actually wrong — check the loop's own output for a configuration or parsing error.

A loop can sit waiting for months and still be perfectly healthy. Nothing should ever be cleaned up, retried, or cancelled purely because it has been waiting a long time.

Loop Not Executing

Check:

  1. Array field exists? {{task_x_results_JSON}}
  2. Array empty? Check {{task_x_rows}} > 0
  3. Valid JSON format?
  4. Loop task enabled?

Debug:

Add Variable task before loop:
Store: {{task_43001_results_JSON}}
View in execution history

Can't Access Loop Item Data

Error: {{task_29001_email}} is empty

Check:

  1. Field name correct? Case-sensitive
  2. Loop task ID correct?
  3. Task is inside loop (after Loop task)?
  4. Array items have that field?

Debug: View execution history → Loop task → See item structure

Loop Running Too Long

Causes:

  • Too many items (1000s)
  • No delay between iterations
  • Slow external API calls
  • Complex tasks in loop

Solutions:

  • Add Max Iterations: 100
  • Add Delay task in loop
  • Process in batches
  • Optimize tasks

Loop Stuck on One Item

Check:

  • Infinite nested loop?
  • Task failing but not stopping?
  • Break condition never met?

Solution:

  • Add Max Iterations safety limit
  • Check task execution in history
  • Verify break conditions

Memory/Timeout Issues

Error: Workflow timeout or Memory limit

Causes:

  • Processing thousands of items
  • Large data in each iteration
  • Complex calculations

Solutions:

  • Reduce batch size
  • Paginate: Process 100 per run, multiple runs
  • Simplify tasks in loop
  • Use database queries instead of loading all data

Frequently Asked Questions

What's the maximum loop iterations?

No hard limit, but practical limits:

  • Recommended: 100-500 per workflow run
  • Maximum: 1000s possible but slower
  • Best practice: Batch process with multiple scheduled runs

Can I loop through a CSV?

Yes, first parse CSV to JSON array using Code task, then loop through result.

How do I count successful vs failed iterations?

Use Variable task to track:

let success = parseInt(input.task_41001_success_count) || 0;
if (input.task_x_success) success++;
return { success_count: success };

Can I loop through non-JSON data?

Loop requires JSON array format. Use Code task to convert other formats first.

How do I stop loop midway?

Set break condition:

Break When: {{task_9001_should_stop}} = true

Can I restart failed loop iterations?

Not automatically. Best practice:

  1. Log failed items to database
  2. Create separate workflow to retry failures

Do all tasks in loop run for each item?

Yes, unless you use If tasks for conditional execution.

Can I modify loop array while looping?

No, loop operates on snapshot of array at start.


  • MySQL Query - Provide arrays to loop through
  • Code Task - Transform data before/in loop
  • If Task - Conditional logic per iteration
  • Delay Task - Rate limiting in loops
  • Variable Task - Track loop progress