Skip to main content

For loops

For loops iterate over collections, allowing you to process each item individually.

Basic syntax

for item in collection {
// steps to execute for each item
}

Iterating over response data

action ProcessUsers {
get "/users"

for user in response.data {
store user -> users { key: .id }
}
}

Iterating over store data

action ProcessStoredData {
for customer in customers {
get concat("/customers/", customer.id, "/orders")
store response -> orders { key: .id }
}
}

Filtering with where

Add conditions to filter items:

action ProcessActiveUsers {
get "/users"

// Single condition
for user in response.data where .status == "active" {
store user -> activeUsers { key: .id }
}
}

Multiple conditions

action ProcessPremiumActiveUsers {
for user in users where .status == "active" and .tier == "premium" {
// Process premium active users
}
}

Comparison operators

// Equality
for item in items where .status == "pending" { }

// Inequality
for item in items where .status != "cancelled" { }

// Numeric comparisons
for item in items where .price > 100 { }
for item in items where .quantity >= 10 { }
for item in items where .discount < 0.5 { }
for item in items where .stock <= 0 { }

// String contains
for item in items where .email contains "@example.com" { }

// Type checking
for item in items where .tags is array { }

Complex conditions

for order in orders where (.status == "pending" or .status == "processing") and .total > 100 {
// Process high-value pending/processing orders
}

Nested loops

action ProcessOrderItems {
for order in orders {
for item in order.lineItems {
map item -> OrderItem {
orderId: order.id,
productId: item.productId,
quantity: item.quantity,
price: item.unitPrice
}
store item -> orderItems { key: concat(order.id, "-", item.productId) }
}
}
}

Variable scope

Loop variables are scoped to their block:

action ScopedVariables {
get "/users"

for user in response.users {
// user is available here

for order in user.orders {
// Both user and order are available

map order -> EnrichedOrder {
orderId: order.id,
userId: user.id,
userName: user.name
}
}
// order is no longer available
}
// user is no longer available
}

Accessing loop item properties

Use dot notation to access properties:

for user in users {
// Direct access
store user -> allUsers { key: .id }

// Nested access
validate user {
assume .profile.email is string
}

// In expressions
map user -> Output {
fullName: concat(.firstName, " ", .lastName),
domain: split(.email, "@")[1]
}
}

Iterating over paginated results

Combine pagination with iteration:

action FetchAllOrders {
get "/orders" {
paginate: offset(page, 100),
until: length(response.orders) == 0
}

// This runs after ALL pages are fetched
for order in response.orders {
store order -> orders { key: .id }
}
}

For processing each page separately:

action ProcessPagesSequentially {
get "/orders" {
paginate: offset(page, 100),
until: length(response.orders) == 0
}

// Pagination accumulates all results in response
// Then the for loop processes them
for order in response.orders {
match order {
{ status: "urgent" } -> {
get concat("/orders/", order.id, "/expedite")
},
_ -> continue
}
store order -> orders { key: .id }
}
}

Breaking out of loops

Use skip in match to skip to the next iteration:

for user in users {
match user {
{ status: "inactive" } -> skip,
{ status: "banned" } -> skip,
_ -> continue
}

// Only runs for active, non-banned users
store user -> activeUsers { key: .id }
}

Error handling in loops

Handle errors per-item:

for user in users {
get concat("/users/", user.id, "/details")

match response {
{ error: _ } -> {
// Log error and continue
store { userId: user.id, error: response.error } -> errors { key: user.id }
skip
},
_ -> continue
}

store response -> userDetails { key: user.id }
}

Performance considerations

Batch operations

Instead of individual requests:

// Less efficient: one request per user
for user in users {
get concat("/users/", user.id)
}

Consider batching if the API supports it:

// More efficient: batch request
post "/users/batch" {
body: { ids: users.map(.id) }
}

Parallel processing

For independent operations, consider parallel actions:

run [FetchOrders, FetchProducts, FetchCustomers] then MergeData

Complete example

mission OrderProcessing {
source API { auth: bearer, base: "https://api.example.com" }

store orders: file("orders")
store enrichedOrders: file("enriched-orders")
store errors: file("errors")

action ProcessOrders {
get "/orders" {
paginate: offset(page, 100),
until: length(response.data) == 0
}

for order in response.data where .status != "cancelled" {
// Validate order
validate order {
assume .id is string,
assume .total > 0,
assume .items is array
}

// Fetch customer details
get concat("/customers/", order.customerId)

match response {
{ error: _ } -> {
store { orderId: order.id, error: "Customer not found" } -> errors
skip
},
_ -> continue
}

// Enrich order with customer data
map order -> EnrichedOrder {
id: order.id,
total: order.total,
status: order.status,
customer: {
id: response.id,
name: response.name,
email: response.email
},
items: order.items
}

store order -> enrichedOrders { key: .id }
}
}

run ProcessOrders
}