Overview
Trigger.dev runs background jobs, scheduled tasks, and long-running workflows outside of serverless function limits. Tasks can run for hours, not just seconds.
Installation
npm install @trigger.dev/sdk
npx trigger.dev@latest init
This creates trigger.config.ts and src/trigger/ directory.
Basic Task
// src/trigger/send-email.ts
import { task } from "@trigger.dev/sdk/v3";
export const sendEmailTask = task({
id: "send-email",
retry: {
maxAttempts: 3,
factor: 2,
minTimeoutInMs: 1_000,
maxTimeoutInMs: 30_000,
},
run: async (payload: { to: string; subject: string; body: string }) => {
const result = await emailService.send(payload);
return { messageId: result.id, sentAt: new Date().toISOString() };
},
});
Trigger from API Route
// src/app/api/send-email/route.ts
import { NextRequest, NextResponse } from "next/server";
import { sendEmailTask } from "@/trigger/send-email";
export async function POST(request: NextRequest) {
const payload = await request.json();
const handle = await sendEmailTask.trigger(payload);
return NextResponse.json({
jobId: handle.id,
status: "queued",
});
}
Scheduled Tasks (Cron)
// src/trigger/daily-digest.ts
import { schedules } from "@trigger.dev/sdk/v3";
export const dailyDigestTask = schedules.task({
id: "daily-digest",
cron: "0 9 * * 1-5", // 9am UTC weekdays
run: async () => {
const users = await db.from("users").select().eq("digest_enabled", true);
for (const user of users) {
await sendDigestEmail(user);
}
return { processed: users.length };
},
});
Long-Running Task with Wait
import { task, wait } from "@trigger.dev/sdk/v3";
export const importDataTask = task({
id: "import-data",
machine: { preset: "large-1x" }, // more memory for heavy tasks
run: async (payload: { fileUrl: string }) => {
// Download file
const data = await downloadFile(payload.fileUrl);
// Process in batches with pauses (avoid rate limits)
const batches = chunk(data, 100);
const results = [];
for (let i = 0; i < batches.length; i++) {
const batchResult = await processBatch(batches[i]);
results.push(...batchResult);
// Pause between batches
if (i < batches.length - 1) {
await wait.for({ seconds: 2 });
}
}
return { imported: results.length };
},
});
Batch Trigger
// Trigger multiple jobs at once
const handles = await sendEmailTask.batchTrigger(
users.map(user => ({
payload: {
to: user.email,
subject: "Weekly Report",
body: generateReport(user),
},
}))
);
console.log(`Triggered ${handles.runs.length} jobs`);
Wait for Another Task
import { task, runs } from "@trigger.dev/sdk/v3";
import { importDataTask } from "./import-data";
export const processAfterImport = task({
id: "process-after-import",
run: async (payload: { fileUrl: string }) => {
// Trigger dependent task and wait for it
const importHandle = await importDataTask.trigger({ fileUrl: payload.fileUrl });
// Wait for completion
const importResult = await runs.poll(importHandle.id, { pollIntervalMs: 5_000 });
if (importResult.status !== "COMPLETED") {
throw new Error(`Import failed: ${importResult.status}`);
}
// Continue with import results
return await processImportedData(importResult.output);
},
});
Concurrency Limits
export const apiSyncTask = task({
id: "api-sync",
concurrencyLimit: {
limit: 5, // max 5 concurrent runs
key: (payload) => payload.accountId, // per-account limit
},
run: async (payload: { accountId: string }) => {
// Only 5 syncs per account run simultaneously
},
});
Retry Configuration
export const webhookTask = task({
id: "process-webhook",
retry: {
maxAttempts: 5,
factor: 1.8,
minTimeoutInMs: 500,
maxTimeoutInMs: 60_000,
randomize: true, // jitter
},
run: async (payload) => {
// Automatically retried with exponential backoff on throw
const result = await externalApi.call(payload);
return result;
},
});
Context and Metadata
export const myTask = task({
id: "my-task",
run: async (payload, { ctx }) => {
// Access task metadata
console.log(`Run ID: ${ctx.run.id}`);
console.log(`Attempt: ${ctx.attempt.number}`);
console.log(`Task ID: ${ctx.task.id}`);
// Mark progress
await ctx.exportTelemetry.span("processing", async () => {
await doWork();
});
},
});
Trigger.dev Config
// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "proj_xxx",
runtime: "node",
logLevel: "log",
retries: {
enabledInDev: false, // don't retry in development
default: {
maxAttempts: 3,
minTimeoutInMs: 1_000,
maxTimeoutInMs: 10_000,
factor: 2,
},
},
dirs: ["./src/trigger"],
});
Checking Job Status
import { runs } from "@trigger.dev/sdk/v3";
const run = await runs.retrieve(runId);
console.log(run.status); // QUEUED | EXECUTING | COMPLETED | FAILED | CANCELED
console.log(run.output); // task return value (when COMPLETED)
When to Use Trigger.dev vs API Routes
| Scenario | Use | |----------|-----| | < 10 second response needed | API route | | Email sending, webhook processing | Trigger.dev task | | File processing, data import | Trigger.dev task | | Scheduled data sync | Trigger.dev cron | | Multi-step workflows | Trigger.dev task with waits | | Real-time streaming response | API route with streaming |