For the complete documentation index, see llms.txt. This page is also available as Markdown.

Loop

Type: loop

Repeat a sequence of nested actions, either for a fixed number of iterations or until one of them fails.

This is how you capture multiple pages in a single browser request. Rather than sending a request per page, you nest your actions inside a loop and Gaffa runs them over and over, so a single request can paginate through a set of results, click through a numbered list, or keep interacting with dynamic content until it runs out.

Tips for using the loop action
  • Set timeout explicitly. The 20-second default covers the whole loop, not each iteration, and most pagination runs need considerably more than that.

  • Put capture actions before the action that navigates. A failure skips everything after it in that pass, so a capture placed after the click never runs on the final page.

  • Give each nested action a custom_id. Everything comes back in one flat list, and an id is the most reliable way to tell which step produced which output.

  • Handle cookie banners and consent popups outside the loop, with continue_on_fail: true. They only appear once, so there's no reason to retry them on every pass.

  • Set continue_on_fail: true on the loop if you have actions queued after it; otherwise, a normal pagination-ending stop the request.

  • Wait for the pagination control to exist before the loop starts. If the first iteration runs before the page has rendered, the click fails, and the loop exits on pass one.

  • Keep max_iterations close to the real page count. A high value combined with a bad selector means the browser keeps clicking until the timeout catches it.

Parameters

Name
Type
Required
Description

actions

action[]

The ordered list of actions to run on every iteration. Nested actions are written exactly like the actions in your request and support the full set of action types, with the exception of loop itself.

max_iterations

integer

The upper bound on how many times the loop can run. Also acts as the iteration count when iterations is not set. Default: 10 Min: 1 Max: 1,000

iterations

integer

A fixed number of times to run the nested actions. If you send this alongside max_iterations, the loop runs whichever of the two is lower. Min: 1 Max: 100

timeout

integer

The maximum amount of time the whole loop can run before it is cancelled, in milliseconds. This covers every iteration combined, not each one.

Default: 20,000 (20s)

stop_on_fail

boolean

Whether the loop should exit when a nested action fails. A nested action with its own continue_on_fail set to true does not trigger this. Default: true

continue_on_fail

boolean

Whether the loop itself is reported as a success when it exits early. Set it to false to pass the failure up to the parent action list, or true to treat an early exit as a clean finish. Default: false

See universal parameters.

How the loop ends

A loop stops for one of three reasons:

  • It reaches its iteration count.

  • A nested action fails while stop_on_fail is true.

  • It hits its timeout.

The second one is what makes pagination work. You don't need to know how many pages a site has. You point click at the "next" control, set a safe max_iterations, and when there is no next page left, the selector matches nothing, the click fails, and the loop exits.

What happens when a nested action fails

When a nested action fails without continue_on_fail: true of its own, the rest of that iteration is abandoned. Every action positioned after it is skipped for that pass, and stop_on_fail then decides what the loop does next: exit, or start the following iteration.

Setting continue_on_fail: true on a nested action changes this. The failure is treated as expected, so the remaining actions in that pass still run and the loop carries on regardless of stop_on_fail.

This is why capture actions belong before the action that navigates, never after it. A capture placed after the click that ends the loop never runs on the final page.

Nested action continue_on_fail

Loop stop_on_fail

Loop continue_on_fail

Result

true

either

either

The failure is ignored. The rest of the iteration runs and the loop continues.

false

true

false

The rest of the iteration is skipped. The loop exits as failed. The parent action list stops unless it has continue_on_fail: true.

false

true

true

The rest of the iteration is skipped. The loop exits successfully. The parent action list continues.

false

false

either

The rest of the iteration is skipped. The loop moves to the next iteration and eventually succeeds.

When a loop times out

A loop that hits its timeout is reported with action_timed_out and treated the same as any other failure, so its continue_on_fail decides whether the request carries on. Iterations already completed are kept, along with their outputs, and any actions finished during the final incomplete pass are recorded too. The iterations count only includes passes that ran to completion.

Actions that come after the loop

A pagination loop normally ends on a failed click, which is a successful run rather than a broken one. If you have further actions queued after the loop, leaving continue_on_fail as false means those actions never execute and the request returns an action_failed error. Set it to true when you want the request to carry on, for example to screenshot the final page once the loop is done:

Here the loop runs until the next-page link is gone, exits cleanly, and the screenshot is still taken. But with continue_on_fail set to false the screenshot would appear in the response marked action_cancelled instead.

Choosing max_iterations

Scenario
Suggested value

Known, fixed page count

The exact count

Unknown but bounded site

A conservative upper bound, for example 50 to 100

Open-ended, as a safety net

1,000, combined with a tight timeout

Set it high enough to cover the real page count, but not so high that a bad selector leaves the browser clicking for minutes. The timeout is your primary safety valve for open-ended loops, and requests are also bound by the maximum running time on your account, which varies by plan.

Usage

Paginate until the next button disappears

The pattern below is the one you'll reach for most often. Capture the page, click through to the next one, and let the failing click end the loop.

Note the order. Each iteration takes a snapshot of the page it is currently on and then navigates away. A capture placed after the click would never run on the final pass, because the click that ends the loop takes the rest of the iteration with it.

Dismiss banners, then paginate

Consent banners and cookie notices only appear once, so handle them outside the loop rather than on every pass. Give each one continue_on_fail: true so the request keeps going when the banner isn't there.

The three clicks before the loop each time out on this site because the banners don't appear, but continue_on_fail: true means the request carries on regardless. Inside the loop, a.page:has-text('Next page') matches the next-page link by its visible text, and the request finishes on page three after two iterations.

Click through numbered pagination

Not every site labels its next-page control. Where pagination is a row of numbers, a CSS sibling selector takes you from the active page to the next page.

#pagination span.active + span selects the span immediately after the currently active page indicator. On the last page, that sibling doesn't exist, the click fails, and the loop exits. The wait before the loop makes sure the pagination has rendered before the first iteration runs.

This site paginates without changing the URL, so actual_url in the response stays on the original address no matter how many pages you move through.

Response format

The loop is returned as a single action containing an iterations count and a nested actions array. Adding a custom_id to each nested action makes the result far easier to read, since it comes back on every entry unchanged:

Every action from every pass is recorded in execution order as a single flat list rather than grouped by iteration. Outputs sit on the individual nested actions, so reading the capture_dom entries, top to bottom, gives you page one, then page two, and so on.

Don't rely on counting entries to work out where one iteration ends and the next begins. A pass that failed partway through, or one interrupted by the loop's timeout records only the actions it completed, which leaves an uneven final group.

FAQs

When do I use the loop action?

Use it when you need more than one page from a single browser request. Common cases include paginated search results, numbered lists, and feeds where you repeat the same few actions until the content runs out.

How do I paginate a site when I don't know how many pages there are?

Point a click at the next-page control and leave stop_on_fail as true. When you reach the last page, the selector matches nothing, the click fails, and the loop exits on its own. Use max_iterations as a safety ceiling rather than an exact count.

How do I capture each page inside a loop?

Add a capture action such as capture_dom to the nested actions array, positioned before the action that navigates. It runs once per iteration, and each capture appears as a separate entry with its own output URL in the response.

Why does my request return action_failed when the loop did what I wanted?

Because the loop ended on a failed click, which is the normal way pagination finishes, and the loop's continue_on_fail defaults to false. Set it to true to treat a clean early exit as a success. Your captured pages are in the response either way.

What is the difference between iterations and max_iterations?

max_iterations is the ceiling and defaults to 10. iterations is an optional fixed count. Send both, and the loop runs whichever is lower. Send neither, and the loop runs 10 times.

Why did an action inside my loop not run?

An action is skipped when something before it in the same iteration failed. That happens regardless of stop_on_fail, which only controls whether the loop then exits or starts the next pass. To keep the remaining actions running, set continue_on_fail: true on the action that is failing.

How do I stop a loop from running too long?

Set the loop's timeout, which caps the total time across all iterations. Combine it with a realistic max_iterations and a timeout on each nested action so no single step can stall the whole run.

How do I tell which captured page came from which iteration?

Give each nested action a custom_id. It's returned unchanged on every entry, so you can read the flat list as repeating groups. Counting entries alone isn't reliable, because an iteration cut short by a failure or by the loop's timeout records only the actions it completed.

Can I put a loop inside another loop?

No. Nesting a loop inside another loop is rejected before the request runs, with an invalid_action_param error. For two-level pagination, such as a list of categories, each with its own paged results, send a separate request per category, with a single loop inside each.

Last updated