When you trigger a task, the run is placed into a queue. Trigger.dev then picks runs off the queue and executes them according to the concurrency limits you configure.
By default each task gets its own queue with no concurrency limit other than the overall limit for your environment. Only actively executing runs count towards concurrency — runs that are waiting at a waitpoint release their slot until they resume.
Default concurrency
All tasks start with an unbounded concurrency limit, capped by your environment’s overall limit.
Environments have a base concurrency limit and a burstable limit (default burst factor: 2x the base
limit). Individual queues are limited by the base limit, not the burstable limit. For example, if
your base limit is 10, the environment can burst to 20 concurrent runs, but any single queue can
have at most 10 concurrent runs at once.
Setting task concurrency
Limit how many runs of a task can execute simultaneously using queue.concurrencyLimit:
/trigger/one-at-a-time.ts
import { task } from "@trigger.dev/sdk";
export const oneAtATime = task({
id: "one-at-a-time",
queue: {
concurrencyLimit: 1,
},
run: async (payload: any) => {
// Only one run of this task will execute at a time
},
});
This is useful when you need to control access to a shared resource like a database or an API with rate limits.
Sharing concurrency between tasks
Define a named queue and assign it to multiple tasks. All tasks sharing the queue will respect the same concurrency limit:
import { task, queue } from "@trigger.dev/sdk";
export const myQueue = queue({
name: "my-queue",
concurrencyLimit: 1,
});
export const task1 = task({
id: "task-1",
queue: myQueue,
run: async (payload: { message: string }) => {
// ...
},
});
export const task2 = task({
id: "task-2",
queue: myQueue,
run: async (payload: { message: string }) => {
// ...
},
});
In this example, task1 and task2 share the same queue, so only one of them can run at a time across both tasks.
Overriding the queue at trigger time
You can override the default queue when triggering a run. This is useful for high-priority runs that need more concurrency:
/trigger/override-concurrency.ts
import { task, queue } from "@trigger.dev/sdk";
const paidQueue = queue({
name: "paid-users",
concurrencyLimit: 10,
});
export const generatePullRequest = task({
id: "generate-pull-request",
queue: {
concurrencyLimit: 1,
},
run: async (payload: any) => {
// ...
},
});
import { generatePullRequest } from "~/trigger/override-concurrency";
export async function POST(request: Request) {
const data = await request.json();
if (data.branch === "main") {
// Use the high-concurrency paid-users queue for main branch
const handle = await generatePullRequest.trigger(data, {
queue: "paid-users",
});
return Response.json(handle);
} else {
// Use the default queue (concurrency of 1)
const handle = await generatePullRequest.trigger(data);
return Response.json(handle);
}
}
Per-tenant queuing with concurrencyKey
Use concurrencyKey to create a separate queue for each unique value. This is useful when you want isolated concurrency limits per user, organization, or any other entity:
import { generatePullRequest } from "~/trigger/override-concurrency";
export async function POST(request: Request) {
const data = await request.json();
if (data.isFreeUser) {
const handle = await generatePullRequest.trigger(data, {
queue: { name: "free-users", concurrencyLimit: 1 },
// Creates a separate "free-users" queue per userId
concurrencyKey: data.userId,
});
return Response.json(handle);
} else {
const handle = await generatePullRequest.trigger(data, {
queue: { name: "paid-users", concurrencyLimit: 10 },
// Creates a separate "paid-users" queue per userId
concurrencyKey: data.userId,
});
return Response.json(handle);
}
}
Concurrency and waits
When a run reaches a waitpoint (such as wait.for(), triggerAndWait(), or wait.forToken()), it is checkpointed and transitions to a WAITING state. At this point, the run releases its concurrency slot back to the queue and environment.
This means:
- Only actively executing runs count towards concurrency limits.
- Runs in the
WAITING state do not hold a slot — you can have far more waiting runs than your concurrency limit.
- When a waiting run resumes, it must re-acquire a slot before continuing.
Parent and subtask concurrency
When a parent task triggers and waits for a subtask on a different queue, the parent checkpoints and releases its slot. This prevents deadlocks:
import { task } from "@trigger.dev/sdk";
export const parentTask = task({
id: "parent-task",
queue: {
concurrencyLimit: 1,
},
run: async (payload: any) => {
// The parent checkpoints here, releasing its slot
// so other runs in the queue can proceed
await subtask.triggerAndWait(payload);
// Resumes after subtask completes and re-acquires a slot
},
});
export const subtask = task({
id: "subtask",
run: async (payload: any) => {
// ...
},
});
Managing queues with the SDK
Import the queues namespace to manage queues programmatically:
import { queues } from "@trigger.dev/sdk";
Listing queues
import { queues } from "@trigger.dev/sdk";
const allQueues = await queues.list();
// With pagination
const pagedQueues = await queues.list({ page: 1, perPage: 20 });
Retrieving a queue
import { queues } from "@trigger.dev/sdk";
// By queue ID
const queueById = await queues.retrieve("queue_1234");
// By type and name
const taskQueue = await queues.retrieve({ type: "task", name: "my-task-id" });
const customQueue = await queues.retrieve({ type: "custom", name: "my-custom-queue" });
The returned queue object includes:
{
id: "queue_1234",
name: "my-task-id",
type: "task", // "task" or "custom"
running: 5, // Currently executing runs
queued: 10, // Runs waiting to execute
paused: false, // Whether the queue is paused
concurrencyLimit: 10,
concurrency: {
current: 10, // Effective limit
base: 10, // Default from code
override: null, // Override value (if set)
}
}
Pausing and resuming queues
Pausing a queue prevents new runs from starting. Currently executing runs continue to completion.
import { queues } from "@trigger.dev/sdk";
// Pause
await queues.pause("queue_1234");
await queues.pause({ type: "task", name: "my-task-id" });
// Resume
await queues.resume("queue_1234");
await queues.resume({ type: "custom", name: "my-custom-queue" });
Overriding concurrency limits
Temporarily change a queue’s concurrency limit at runtime:
import { queues } from "@trigger.dev/sdk";
// Set a new limit
await queues.overrideConcurrencyLimit("queue_1234", 5);
await queues.overrideConcurrencyLimit({ type: "task", name: "my-task-id" }, 20);
// Reset to the value defined in code
await queues.resetConcurrencyLimit("queue_1234");
await queues.resetConcurrencyLimit({ type: "task", name: "my-task-id" });