Repeating work
Twelve minutes. So far each flow has done one thing once. This one does the same thing to every item in a list — which is where automation starts paying for itself.
From here on the tutorials import a finished template and take it apart, rather than having you place fifteen nodes by hand. Reading a working flow and changing it teaches more per minute than transcribing a node list.
What you need
- Tutorial 3 finished
The flow you are building

Import it
- In the Flow Designer, open Start with an example on the home screen (or the Templates gallery).
- Find Copy Files and import it.
- Open the imported flow.
The loop
Three nodes make the loop, and the shape is worth learning because it recurs everywhere.
Label — "Loop Start" marks a point in the flow you can jump back to. On its own it
does nothing.
ForEach — "For Each File" is the interesting one. It takes a list from msg and has
two output ports:
| Port | Fires when | Wired to |
|---|---|---|
| 1 | There is another item — it puts that item in msg and emits here | the work you want done per item |
| 2 | The list is exhausted | whatever comes after the loop |
Its configuration reads:
| Property | Value | Meaning |
|---|---|---|
| Input | Message → files_to_copy | the list to walk |
| Output | Message → current_file | where to put the current item |
| Index | Message → current_index | where to put the position, 0-based |
GoTo — "Loop Back" jumps back to Loop Start after each item is handled.
So the cycle is: Label → ForEach → do the work → GoTo back to Label → ForEach … until
ForEach runs out of items and emits on port 2 instead, which leads to Stop.
Robomotion flows are graphs, not nested blocks — there is no "inside the loop" region on
the canvas. ForEach remembers its position between visits, so jumping back to it with a
GoTo is what advances the list. Once you have seen it once it reads naturally, and the
same Label/GoTo pair is how Go to Label works
generally.
The work inside the loop
Two nodes, and the first is doing something worth noticing:
var p = msg.current_file;
var lastSlash = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
msg.dest_path = msg.destination_folder + '/' + p.substring(lastSlash + 1);
return msg;
It takes the full source path of the current file, strips off everything before the last
slash to get the bare filename, and joins that to the destination folder. It checks both
/ and \ so the same flow works on Linux and Windows.
Then Copy File (FileSystem → Copy) does the actual work, reading
msg.current_file and msg.dest_path.
The detail that matters in production
Select Copy File and look at Continue On Error. It is switched on.
That means one unreadable file does not abort the run — the loop carries on with the next item. For a batch job that is almost always what you want: copying 99 of 100 files and reporting the one failure beats copying none.
The same option is on Create Dest Dir, so a destination folder that already exists is not treated as a failure.
This is the first appearance of a theme the next tutorial is entirely about: a flow that works is not the same as a flow that keeps working.
Run it
Run the flow. It asks for a destination folder, defaulting to a dest directory beside the
fixtures. Accept it and the files are copied.
Then look at Branch On Cancel, a Function node with two outputs:
return (msg.destination_folder && msg.destination_folder.trim()) ? [msg, null] : [null, msg];
A Function node with multiple outputs returns an array, one slot per port. Put the
message in a slot to emit there, null to stay silent. Here: a real folder goes out port 1
into the copying; an empty answer goes out port 2 straight to Stop.
So pressing Cancel ends the flow cleanly instead of copying files into nowhere. Handling the empty answer is the kind of thing that separates a flow that demos from a flow you can leave running.
Try changing it
- Add a third entry to
msg.files_to_copyin Seed Sources and re-run. The loop does not change — that is the point. - Add a Debug node between
For Each FileandBuild Dest Path, then run. Watchcurrent_fileandcurrent_indexchange on each pass. This is the clearest possible view of a loop executing. - Turn Continue On Error off on
Copy File, point one entry at a file that does not exist, and re-run. Compare what happens.
Next
Handling failure → — what to do when a step fails outright, instead of just continuing past it.