Cloudflare Limits
Cloudflare Workflows runs inside the Cloudflare Workers runtime. The following limits apply to all workflows deployed by AwaitStep.
Core Limits
| Limit | Value | Notes |
|---|---|---|
| Max steps per workflow | 100 | Counts step.do() calls. sleep and sleepUntil do not count. |
| Max CPU time per step | 15 minutes | Active JavaScript execution time. Awaiting I/O does not count. |
| Max wall time per step | 30 seconds | Real elapsed time per step.do() call, including I/O wait. |
| Max total workflow duration | 1 year | Clock time from first step to last. |
| Max concurrent instances | 1,000 | Per account. Includes all workflows on the account. |
| Max payload size | 1 MB | Applies to workflow input and each step's return value. |
WARNING
The 30-second wall time limit per step.do() call is the most commonly hit limit. If a single step makes a slow external API call, it can time out even if the CPU time limit is not reached. Design steps to be fast, or split long-running operations across multiple steps.
Step Limit (100 steps)
Every step.do() call counts against the 100-step limit. This includes steps generated by loops — each iteration of a loop node creates new steps at runtime.
Workaround for loop-heavy workflows:
If you need to process more than 100 items in a loop, batch them:
// Instead of one step per item (N steps):
for (const item of items) {
await step.do(`process_${item.id}`, async () => processItem(item))
}
// Process items in batches (ceil(N/10) steps):
const batches = chunk(items, 10)
for (const [i, batch] of batches.entries()) {
await step.do(`process_batch_${i}`, async () => {
return Promise.all(batch.map(processItem))
})
}You can also use a sub-workflow to offload processing to a separate workflow instance, each with its own 100-step budget.
Wall Time Limit (30s per step)
Each step.do() call has a maximum wall time of 30 seconds. If your step makes a network request that takes longer, it will time out.
Workarounds:
- Split the operation. If you are polling an external API, use a
sleep+looppattern instead of a single blocking call. - Use a webhook. Trigger the external operation and use
wait_for_eventto wait for a callback, rather than polling synchronously. - Use a sub-workflow. Offload the long-running step to a child workflow with its own step budget.
// Pattern: poll with sleep
// Step 1: Kick off the job
const job_result = await step.do('start_job', async () => {
return startExternalJob({ input: data })
})
// Step 2+: Poll until done (each poll is a separate step)
let status = 'pending'
let attempts = 0
while (status === 'pending' && attempts < 20) {
await step.sleep(`wait_${attempts}`, '30 seconds')
const poll_result = await step.do(`poll_${attempts}`, async () => {
return checkJobStatus(job_result.jobId)
})
status = poll_result.status
attempts++
}CPU Time Limit (15 min per step)
The 15-minute CPU limit applies to active JavaScript execution. Waiting for network I/O (fetch, etc.) does not count against this limit.
In practice, the 30-second wall time limit is usually reached first for I/O-bound steps.
Payload Size (1 MB)
The 1 MB limit applies to:
- The workflow input payload (passed at trigger time)
- The return value of each
step.do()call
Workarounds:
- Store large data in KV, R2, or D1 and pass a reference (ID or key) between steps instead of the data itself.
- Compress large payloads before storing them as step output.
// Instead of returning the full document (may exceed 1 MB):
return { document: largeDocumentString }
// Store in R2 and return a reference:
const key = `docs/${crypto.randomUUID()}`
await env.MY_BUCKET.put(key, largeDocumentString)
return { documentKey: key }Concurrent Instances (1,000)
Cloudflare allows a maximum of 1,000 concurrently running workflow instances per account. This limit spans all workflows on the account.
Workaround: If you expect high concurrency, consider rate-limiting workflow creation at the API layer, or batching work into fewer, longer-running workflows.
Max Duration (1 Year)
A single workflow instance can run for up to 1 year from start to completion. This includes time spent paused in sleep or wait_for_event states.
This is more than sufficient for most use cases, including long-running approval workflows, scheduled reminders, and multi-day batch processes.
Summary Cheatsheet
| What you want to do | Limit to watch |
|---|---|
| Process a large list | 100 steps (each loop iteration is a step) |
| Make a slow API call | 30s wall time per step |
| Return large data from a step | 1 MB payload |
| Run many workflows in parallel | 1,000 concurrent instances |
| Build a year-long reminder workflow | 1 year max duration |