Iteration — processing lists of items
A Madoo workflow has no loops. To process many items you do not repeat the workflow and you do not draw a cycle: you give the workflow a list, and every node after the list runs once per item, in parallel. When you need one result from all the items — a table, a PDF with a page per item, a single video — an aggregator collects them back.
This page builds the idea up one step at a time. Each step adds one thing to the one before.
Step 1 — one item, one run
Section titled “Step 1 — one item, one run”Start with the simplest workflow: a product photo goes in, the background is removed, a new scene is generated, the image comes out.
Image input ──► Remove background ──► Place in scene ──► Image outputOne run, one photo, one result. Every node runs exactly once. To process a second photo you could run the workflow again — and for a handful of items that is fine. For a catalog it is not: you want one run that processes the whole list.
Step 2 — a list fans out
Section titled “Step 2 — a list fans out”Replace the single image input with a node that produces a list: for example a CSV input with one row per product and a column holding the photo.
CSV input (3 rows) ──► Remove background ──► Place in scene ──► Image output runs 3 times runs 3 times 3 imagesThe CSV input is an enumerator: it produces its values one per item. Every node downstream of it becomes an iterator: Madoo runs it once for each item, in parallel, so Remove background, Place in scene and the output each run three times. There is no loop to write and no counter to keep: the fan-out follows the connections.
Each column you map becomes an output port of the enumerator. Values of the same row stay together: row 2’s photo always travels with row 2’s name, price and language, however many nodes they pass through.
A value that does not come from the list is shared by every item. If Place in scene also receives a style prompt from a text input, all three iterations use that same prompt.
What you get. An output node inside the iterated branch produces one result per item. The run returns three images, each tagged with its item index, under the same output key.
Step 3 — collecting the results with an aggregator
Section titled “Step 3 — collecting the results with an aggregator”Often you want one result, not N. An aggregator waits for every iteration of the branch it is connected to and combines them into a single value. After it, the flow is one item again.
CSV input ──► … per item … ──► Generate CSV ──► CSV output (one file, one row per item) └────► Multi-page PDF ──► PDF output (one PDF, one page set per item)Pick the aggregator by the result you need:
| You want | Aggregator |
|---|---|
| A JSON array, one object per item (for a template list, an API response, another step) | Generate JSON (aggregate/json) |
| A spreadsheet, one row per item | Generate CSV (aggregate/csv) |
| One text joining every item’s text | Concatenate Text (aggregate/concat_text) |
| One PDF with the same page layout filled once per item | Multi-page PDF (aggregate/pdf) |
| One video or one audio track from per-item clips | Merge Videos / Merge Audio (aggregate/video_merge, aggregate/audio_merge) |
Specialized aggregators recompose the results of media techniques — transcripts, caption tracks, audio timelines, highlight plans — and are explained with those techniques.
A single workflow can do both: keep the per-item images and collect their descriptions into one CSV.
Step 4 — where lists come from
Section titled “Step 4 — where lists come from”Lists enter a workflow in two ways.
Lists you provide. Input nodes that enumerate what you give them when the run starts:
| Node | Produces one item per |
|---|---|
CSV Input (input/csv) |
row of a CSV file, with columns mapped to ports |
JSON Input (input/json) |
element of a JSON array, with properties mapped to ports |
Value List (input/value_list) |
entry of a typed list (texts, numbers, image links) |
Number Range (input/number_range) |
number from start to end by a step |
Data Input + Data Rows (input/data → enumerate/data_rows) |
row of a CSV, JSON or XLSX dataset, with typed columns |
The data can be fixed in the workflow or supplied when it runs, so the same workflow processes a different list every time.
Lists created during the run. Sometimes the list does not exist until an earlier step produces it: an AI answer that proposes five headlines, a JSON extracted from a document, the segments of a long video. Dynamic enumerators turn such a value into items:
| Node | Fans out over |
|---|---|
JSON Enumerator (enumerate/json) |
a JSON array produced upstream |
| CSV Enumerator, Value List Enumerator | CSV text or a list produced upstream |
| Number Range Enumerator | a range whose bounds are computed upstream |
| Video Segments, Audio Segments | consecutive parts of a long video or audio, known only at run time |
So a workflow can ask a model “propose five scenes for this product”, fan out over the five answers, generate one image per scene and collect them — all in one run.
A JSON value is not a list to fan out. When a node needs the whole array at once (a template’s repeating list,
a caption track, a configuration object), pass it as a single value with a JSON Value input
(input/json_value) or from the producing node directly. Use an enumerator only when you want one run of the
following nodes per element.
Values computed per item. A value that each item needs but the data does not hold — an amount, a price with VAT, a label — can be computed in two ways:
- a computed column of the CSV or JSON enumerator, when it depends only on the item’s own fields. A number
column takes a calculation (
round(qty * price, 2)); a text column takes a template ({{ name | string.upcase }} ({{ sku }})); - a Calculate node (
utility/calculate) after the enumerator, when the value also needs something outside the item (a VAT rate from an input, a total from another node). It runs once per item like any node that follows an enumerator.
Both use exact decimal arithmetic and stop the run with a reason instead of producing an empty or rounded-off value. Never let an AI node compute amounts.
Step 5 — skipping items
Section titled “Step 5 — skipping items”Not every item must go all the way through. A Filter (utility/filter) after a Predicate lets an item
continue only when a condition holds; a Quality Gate can stop an item whose result does not meet a policy.
An item that is stopped is skipped, not failed: the nodes after it do not run for that item and do not spend
credits, and the run still succeeds.
Skipped items leave a gap. Aggregators handle it — for example Generate JSON can leave the skipped item’s row out — so the collected result contains only the items that made it through. A photo shoot of eight pictures can produce a gallery of the six judged publishable.
Step 6 — two lists at once
Section titled “Step 6 — two lists at once”This is the part that needs care. When a node receives values from two different lists, Madoo runs it on every combination of their items.
Value List: 2 photos ─────┐ ├──► Translate text in image runs 2 × 2 = 4 timesValue List: 2 languages ──┘ (photo 1 · IT, photo 1 · EN, photo 2 · IT, photo 2 · EN)Each independent list is a dimension. A node iterates over the product of the dimensions that reach it.
Values that come from the same list never multiply: they stay aligned. If a product’s name and its photo both come from the same CSV row — even when the photo went through three processing nodes first and the name through a text template — they meet again as the same item, not as every name with every photo.
CSV input (3 rows) ──► photo ──► Remove background ──► Place in scene ──┐ └────────► name ──► Text template ─────────────────────────┴──► Image with caption runs 3 timesTwo rules follow:
- Different sources multiply. 3 products × 2 languages × 2 formats is 12 runs of every node that receives all three. That is exactly what you want for “every product in every language in every format” — and exactly what you do not want by accident.
- The same source aligns. Values traced back to the same list are paired item by item, whatever path they took.
An aggregator collects everything that reaches it: after it, all dimensions are closed and the flow is a single item again.
Step 7 — lists inside an item
Section titled “Step 7 — lists inside an item”An item can itself contain a list: a product with its features, a course participant with their modules, an invoice with its lines. You do not need a second fan-out for that. Pass the inner list as a JSON value to the node that uses it — a document template renders it with a repeating list, a text template can list it in a prompt. Fan out only over the items whose processing you want to run separately.
Cost, limits and estimates
Section titled “Cost, limits and estimates”The cost of an iterated branch is the cost of one item times the number of items. Estimate a run with the inputs you will actually send: when the list comes from the inputs, the estimate counts its items; when the list is created during the run, the paid part of the estimate is known only once the list exists.
Every node has a ceiling on how many iterations it may create (10,000 by default), and a run can set a lower one
(max_iterations_per_node) as a safety limit.
Common mistakes
Section titled “Common mistakes”- No aggregator, one file expected. Output nodes inside an iterated branch return one result per item. If the user expects one CSV, one PDF or one video, add the aggregator.
- An accidental product. Two lists from different sources feeding the same node multiply. If the values belong together, they must come from the same list (the same row, the same array element).
- Fanning out when a whole value was needed. A template’s repeating list, a caption track or a JSON document for one node is a single value: use a JSON Value input, not a JSON enumerator.
- Expecting loops. Madoo has no loops or cycles. Repetition is a list; refinement in rounds is a fixed number of explicit steps.