Skip to content

Step Execution Options

These settings are defined in your TypeScript flow code and compiled into SQL migrations. They control how individual steps are executed, delayed, and retried. Set defaults at the flow level, override for specific steps. Step-level options are null by default, inheriting from flow-level settings.

new Flow({
slug: 'my_flow',
maxAttempts: 3, // max retry attempts before marking as failed
baseDelay: 1, // initial retry delay in seconds
timeout: 60 // visibility timeout in seconds
// Note: startDelay is step-level only, not available as a default at flow level
})

Type: number Default: 3

The maximum number of times a task will be attempted before being marked as permanently failed.

// Flow level
new Flow({ slug: 'my_flow', maxAttempts: 5 })
// Step level (overrides flow default)
.step({ slug: 'my_step', maxAttempts: 7 }, handler)

Type: number Default: 1

The initial delay (in seconds) before the first retry. pgflow uses exponential backoff, so subsequent retries will have increasingly longer delays.

// Flow level
new Flow({ slug: 'my_flow', baseDelay: 2 })
// Step level (overrides flow default)
.step({ slug: 'my_step', baseDelay: 10 }, handler)

Type: number Default: 60

The visibility timeout (in seconds) - how long a task remains invisible to other workers while being processed.

// Flow level
new Flow({ slug: 'my_flow', timeout: 120 })
// Step level (overrides flow default)
.step({ slug: 'my_step', timeout: 300 }, handler)

Type: number Default: 0

Initial delay (in seconds) before task execution.

When all steps can use the same configuration:

new Flow({
slug: 'my_flow',
maxAttempts: 3, // Default for all steps
baseDelay: 1, // Default for all steps
timeout: 60 // Default for all steps
})
.step({ slug: 'step1' }, handler1) // Uses flow defaults
.step({ slug: 'step2' }, handler2) // Uses flow defaults

Override flow defaults for specific steps that need different behavior:

new Flow({
slug: 'analyze_data',
maxAttempts: 3, // Flow defaults
baseDelay: 1,
timeout: 60
})
.step({
slug: 'fetch_data',
// Uses all flow defaults
}, fetchHandler)
.step({
slug: 'process_data',
maxAttempts: 5, // Override: more retries
timeout: 300 // Override: needs more time
// baseDelay uses flow default (1)
}, processHandler)
.step({
slug: 'call_api',
baseDelay: 10, // Override: longer initial delay
// maxAttempts and timeout use flow defaults
}, apiHandler)

pgflow uses exponential backoff for retries. The delay between attempts is calculated as:

delay = baseDelay * 2^attemptCount

Here’s how retry delays grow with different base delays:

AttemptDelay (baseDelay: 2s)Delay (baseDelay: 5s)Delay (baseDelay: 10s)
12s5s10s
24s10s20s
38s20s40s
416s40s80s
532s80s160s
664s160s320s
7128s320s640s

A task is marked as permanently failed when:

  • It has been attempted maxAttempts times
  • Each attempt resulted in an error
  • The task status changes from queued to failed
  • The error message from the last attempt is stored