Skip to content

Error Handling ​

How errors work by default ​

When a step throws an error, the platform retries it automatically according to its retry configuration. After exhausting all retries, the error propagates up the call stack. If it reaches the top-level run() method, the workflow run is marked errored and halted.

Configuring retries on a step ​

Open any node's config drawer and set the retry fields:

FieldDefaultDescription
Retry Limit5Maximum number of retry attempts
Retry Delay10 secondsDelay between retries
Backoffconstantconstant, linear, or exponential
Timeout—Max duration for a single attempt

Generated retry config ​

typescript
const fetch_data = await step.do(
  'Fetch Data',
  {
    retries: {
      limit: 3,
      delay: '5 seconds',
      backoff: 'exponential',
    },
    timeout: '30 seconds',
  },
  async () => {
    const res = await fetch('https://api.example.com/data')
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    return await res.json()
  },
)

Try/Catch node ​

The Try/Catch node wraps a set of steps in a try/catch/finally block. Use it when you want to handle errors without stopping the entire workflow.

Structure ​

BranchDescription
trySteps to attempt
catchSteps to run if any step in the try branch throws
finallySteps that always run, regardless of success or failure

Generated code ​

typescript
try {
  const charge = await step.do('Charge Card', async () => {
    // ...
  })
  await step.do('Send Receipt', async () => {
    // ...
  })
} catch (err) {
  await step.do('Refund and Notify', async () => {
    // log the error, notify ops team, etc.
  })
} finally {
  await step.do('Release Lock', async () => {
    // always clean up
  })
}

TIP

The err variable inside the catch branch is available as err.message in expressions. Reference it with {{try_catch_node.error}} — the exact path depends on what the catch step returns.

NonRetryableError ​

Some errors should not be retried — for example, a 400 Bad Request from an API means the request itself is invalid and retrying will not help. Throw NonRetryableError to skip all remaining retry attempts and fail immediately.

typescript
import { NonRetryableError } from 'cloudflare:workers'

const result = await step.do('Call API', async () => {
  const res = await fetch('https://api.example.com/submit', {
    method: 'POST',
    body: JSON.stringify({ data }),
  })

  if (res.status === 400) {
    const body = await res.json()
    throw new NonRetryableError(`Invalid request: ${body.message}`)
  }

  if (!res.ok) {
    // 5xx errors: retryable
    throw new Error(`Server error: ${res.status}`)
  }

  return await res.json()
})

WARNING

NonRetryableError must be imported from cloudflare:workers. It is available inside any Step node's code editor — the import is injected automatically by the code generator.

Error propagation summary ​

ScenarioBehavior
Step throws a retryable errorPlatform retries up to the retry limit
Step throws NonRetryableErrorFails immediately, no retries
Step exhausts retriesError propagates to parent (Try/Catch or top level)
Try/Catch catch branch throwsError propagates to the next outer Try/Catch or top level
Uncaught error at top levelRun is marked errored