Skip to main content

Overview

Trigger.dev is a background task platform built for long-running async work. Your task code runs in isolated, managed environments with no timeout limits. Trigger.dev handles scheduling, queuing, retries, and observability so you can focus on the logic inside your tasks. A typical flow looks like this:
  1. You write a task as an exported async function in your codebase
  2. In development, the CLI runs tasks locally in your Node.js process
  3. In production, the CLI builds and deploys your tasks as a Docker image
  4. When your application triggers a task, Trigger.dev queues and executes it
  5. Results, logs, and traces are visible in real time in the dashboard

The CLI

All interaction with Trigger.dev starts with the CLI, installed via npx trigger.dev@latest.
npx trigger.dev@latest login    # Authenticate with your account
npx trigger.dev@latest init     # Set up Trigger.dev in your project
npx trigger.dev@latest dev      # Run tasks locally in development
npx trigger.dev@latest deploy   # Build and deploy tasks to production
The CLI supports multiple profiles, so you can switch between Trigger.dev Cloud and self-hosted instances:
npx trigger.dev@latest login --profile staging -a https://trigger.example.com
npx trigger.dev@latest dev --profile staging
npx trigger.dev@latest deploy --profile staging

Dev mode

Running npx trigger.dev@latest dev starts a local worker that connects to Trigger.dev Cloud (or your self-hosted instance). Task scheduling and queuing happen on the Trigger.dev server, but task code executes locally in your Node.js process. Dev mode behavior:
  • Hot reload — Changes to files in your /trigger directory are detected automatically and a new worker version is registered
  • Local execution — Each task runs in a separate local process, so you can attach a debugger, add breakpoints, and inspect state
  • Same build system — Code is built with esbuild the same way as production, so dev and prod behavior is consistent
  • Auto-cancel — Running tasks are cancelled when you stop the dev server
Trigger.dev does not support offline dev mode. An internet connection is required to connect to the Trigger.dev server for scheduling.

Deployment

Running npx trigger.dev@latest deploy builds your task code and publishes it to Trigger.dev.

The build system

The build system is powered by esbuild:
  • Bundled by default — Your code and its dependencies are bundled and tree-shaken into a single output
  • ESM output — Output targets ESM for better tree-shaking and compatibility
  • Build extensions — Customize the build or the resulting Docker image using extensions (Prisma, Python, Playwright, FFmpeg, etc.)
You can inspect the build output without deploying using the --dry-run flag:
npx trigger.dev@latest deploy --dry-run
Configure the build in trigger.config.ts:
import { defineConfig } from "@trigger.dev/sdk";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";

export default defineConfig({
  project: "<your-project-ref>",
  dirs: ["./trigger"],
  build: {
    extensions: [
      prismaExtension({ schema: "prisma/schema.prisma" }),
    ],
  },
});

Environments

Trigger.dev supports dev, staging, and prod environments. Each environment has its own API key. Deploy to a specific environment with the --env flag:
npx trigger.dev@latest deploy --env staging
npx trigger.dev@latest deploy --env prod
For branch-level isolation, preview branch environments can be created for each feature branch, giving you fully isolated environments for testing.

The run lifecycle

When you trigger a task, Trigger.dev creates a run and processes it through the following stages: Each run goes through these states:
  • Queued — The run is waiting to be picked up by a worker
  • Executing — A worker is actively running the task
  • Waiting — The task is paused (waiting for a subtask, duration, or token)
  • Completed — The task finished successfully and returned output
  • Failed — The task threw an error; a retry may be scheduled based on the task’s retry config
  • Cancelled — The run was cancelled before or during execution
You can inspect run state using runs.retrieve:
import { runs } from "@trigger.dev/sdk";

const run = await runs.retrieve(runId);
console.log(run.status); // "COMPLETED", "EXECUTING", "FAILED", etc.
console.log(run.output); // The return value of your task

The Checkpoint-Resume System

Trigger.dev implements a Checkpoint-Resume System that lets tasks pause execution without holding compute resources. This enables:
  • Waiting for subtasks — A parent task can call childTask.triggerAndWait() and pause until the child completes
  • Timed delayswait.for({ seconds: 30 }) suspends the task for any duration
  • Human-in-the-loopwait.forToken() pauses until an external agent completes a token
  • No idle compute cost — Paused tasks don’t consume resources, and on Trigger.dev Cloud you’re not charged for wait time
When a task reaches a wait point, the system uses CRIU (Checkpoint/Restore In Userspace) to snapshot the entire task state — memory, CPU registers, and file descriptors — and stores it compressed on disk. When the wait resolves, the snapshot is restored in a new execution environment and the task continues from exactly where it left off.

Example: parent and child tasks

import { task, wait } from "@trigger.dev/sdk";

export const parentTask = task({
  id: "parent-task",
  run: async () => {
    console.log("Starting parent task");

    // Task is checkpointed here and resources are released
    const result = await childTask.triggerAndWait({ data: "some data" });

    console.log("Child task result:", result.output);

    // Task is checkpointed again for the duration of this wait
    await wait.for({ seconds: 30 });

    console.log("Resumed after 30 seconds");

    return "Parent task completed";
  },
});

export const childTask = task({
  id: "child-task",
  run: async (payload: { data: string }) => {
    console.log("Child task received:", payload.data);
    return "Child task result";
  },
});
The checkpoint flow looks like this:
On Trigger.dev Cloud, you are not charged for time spent waiting in a checkpointed state — only for active compute time.

Durable execution with idempotency keys

For complex workflows that coordinate multiple subtasks, Trigger.dev supports idempotency keys to prevent duplicate work on retries. When a subtask completes, its output is cached against the idempotency key. If the parent task retries and calls the same subtask again, the cached result is returned immediately.
import { task, idempotencyKeys } from "@trigger.dev/sdk";

export const processOrderWorkflow = task({
  id: "process-order-workflow",
  retry: { maxAttempts: 5 },
  run: async ({ orderId }: { orderId: string }) => {
    // Key is scoped to this specific run, across retries
    const idempotencyKey = await idempotencyKeys.create(orderId);

    // If this subtask already completed on a prior attempt, the cached result is returned
    const { chargeId } = await chargeCustomer
      .triggerAndWait({ orderId }, { idempotencyKey })
      .unwrap();

    const { trackingId } = await fulfillOrder
      .triggerAndWait({ orderId, chargeId }, { idempotencyKey })
      .unwrap();

    // Fire and forget — no need to wait
    await sendConfirmationEmail.trigger({ orderId, trackingId }, { idempotencyKey });

    return { success: true, trackingId };
  },
});
If sendConfirmationEmail fails and the workflow retries, chargeCustomer and fulfillOrder return their cached results. Only the failed step is re-executed.

Observability with OpenTelemetry

Every task run is traced using OpenTelemetry. Trigger.dev automatically captures spans for your task execution, subtask calls, and wait points. Logs emitted with logger are correlated to the current trace.
import { task, logger } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { url: string }) => {
    logger.info("Starting task", { url: payload.url });

    const data = await fetchData(payload.url);

    logger.info("Data fetched", { bytes: data.length });

    return { bytes: data.length };
  },
});
You can add additional OpenTelemetry instrumentation via trigger.config.ts to trace your database queries, HTTP calls, and third-party SDK calls:
import { defineConfig } from "@trigger.dev/sdk";
import { PrismaInstrumentation } from "@prisma/instrumentation";
import { AwsInstrumentation } from "@opentelemetry/instrumentation-aws-sdk";

export default defineConfig({
  project: "<your-project-ref>",
  instrumentations: [
    new PrismaInstrumentation(),
    new AwsInstrumentation(),
  ],
});
All traces and logs are visible in the run detail view in the Trigger.dev dashboard.

Next steps

Quickstart

Follow the step-by-step guide to run your first task.

Writing tasks

Explore all the options for defining tasks — queues, middleware, hooks, and more.

Wait & suspend

Learn all the ways tasks can pause — durations, events, subtasks, and tokens.

Configuration

Configure the build system, extensions, and runtime settings.

Build docs developers (and LLMs) love