Skip to main content
When you trigger a task, the run waits in a queue and runs start in trigger order as capacity allows. Concurrency is what decides how much capacity there is. By default, concurrency is only limited by your environment concurrency limit. If you need more control (for example, to limit concurrency or share limits across multiple tasks), you can set a concurrency limit on the task, or declare a named limit and share it, as described below. Controlling concurrency is useful when you have a task that can’t be run concurrently, or when you want to limit the number of runs to avoid overloading a resource. It’s important to note that only actively executing runs count towards concurrency limits. Runs that are delayed or waiting in a queue do not consume concurrency slots until they begin execution.

Use cases

Default concurrency

By default, all tasks have an unbounded concurrency limit, limited only by the overall concurrency limits of your environment.
Your environment has a base concurrency limit and a burstable limit (default burst factor of 2.0x the base limit). Individual tasks and limits are capped by the base concurrency limit, not the burstable limit. For example, if your base limit is 10, your environment can burst up to 20 concurrent runs, but any single limit can allow at most 10 concurrent runs. If you’re a paying customer you can request higher burst limits by contacting us.

Setting task concurrency

Set the concurrency option on a task to limit how many of its runs execute at once. { total: n } caps the task outright:
/trigger/one-at-a-time.ts
This is useful if you need to control access to a shared resource, like a database or an API that has rate limits. There are two ways to bound a task, and you can combine them:
  • total caps every run of the task together, whether or not runs use a concurrencyKey.
  • perKey caps each concurrencyKey pool separately; runs triggered without a key share one pool.
/trigger/per-user.ts
The queue: {"{ concurrencyLimit: n }"} option keeps working but is deprecated in favor of concurrency. Its single number means “per key when runs pass a concurrencyKey, whole queue when they don’t” — the concurrency shape says which you mean explicitly.

Sharing a limit between tasks

Declare a named limit with concurrencyLimit() and put it in each task’s concurrency. Every task holding the limit draws from the same pools:
/trigger/limits.ts
A task’s concurrency takes a single item or an array: at most one inline shape (which caps that task alone) plus up to two named limits. A run starts only when every limit it holds has capacity, and it occupies a slot in each while it executes:
/trigger/summarize.ts
Names you declare with concurrencyLimit() are 1-122 characters using only letters, numbers, underscores and hyphens.

Setting limits when you trigger a run

The trigger-time concurrency option takes limit names and replaces the task’s declared named limits for that run. The task’s inline limit always applies:
app/api/report/route.ts
Pass an empty array to run with only the task’s inline limit.

Concurrency keys and per-tenant limits

If you’re building an application where you want to run tasks for your users, you might want a separate limit for each of your users (or orgs, projects, etc.). You can do this by passing a concurrencyKey when you trigger. Each unique key value gets its own pool under every perKey bound the run holds:
app/api/report/route.ts