Full code examples for Trigger.dev SDK v4. All tasks use @trigger.dev/sdk.
import { task } from "@trigger.dev/sdk";
export const processData = task({
id: "process-data",
retry: {
maxAttempts: 3,
factor: 2,
minTimeoutInMs: 5000,
maxTimeoutInMs: 30_000,
},
run: async (payload: { userId: string; data: any[] }) => {
console.log(`Processing ${payload.data.length} items for user ${payload.userId}`);
return { processed: payload.data.length };
},
});import { schedules } from "@trigger.dev/sdk";
export const dailyReport = schedules.task({
id: "daily-report",
cron: "0 9 * * *", // 9am UTC every day
run: async () => {
console.log("Running daily report");
// task logic here
return { status: "done" };
},
});Common cron expressions:
"*/30 * * * *"— every 30 minutes"0 * * * *"— every hour"0 */8 * * *"— every 8 hours"0 9 * * *"— 9am daily"0 8 * * 1"— every Monday at 8am
import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";
export const validatedTask = schemaTask({
id: "validated-task",
schema: z.object({
name: z.string(),
videoId: z.string(),
publishedAt: z.string(),
}),
run: async (payload) => {
// payload is fully typed and validated before run() is called
return { message: `Processing ${payload.name}` };
},
});import { tasks } from "@trigger.dev/sdk";
import type { processData } from "./trigger/tasks.js";
// Single trigger — fire and forget
const handle = await tasks.trigger<typeof processData>("process-data", {
userId: "123",
data: [{ id: 1 }, { id: 2 }],
});
// Batch trigger — up to 1,000 items, 3MB per payload
const batchHandle = await tasks.batchTrigger<typeof processData>("process-data", [
{ payload: { userId: "123", data: [{ id: 1 }] } },
{ payload: { userId: "456", data: [{ id: 2 }] } },
]);export const parentTask = task({
id: "parent-task",
run: async (payload) => {
// Fire and forget — don't wait for result
await childTask.trigger({ data: "value" });
// Trigger and wait — returns a Result object, NOT the raw output
const result = await childTask.triggerAndWait({ data: "value" });
if (result.ok) {
console.log("Output:", result.output); // the return value of childTask.run()
} else {
console.error("Failed:", result.error);
}
// Unwrap shorthand — throws on failure instead of checking result.ok
const output = await childTask.triggerAndWait({ data: "value" }).unwrap();
// Batch trigger and wait
const results = await childTask.batchTriggerAndWait([
{ payload: { data: "item1" } },
{ payload: { data: "item2" } },
]);
for (const run of results) {
if (run.ok) console.log("Success:", run.output);
}
},
});
export const childTask = task({
id: "child-task",
run: async (payload: { data: string }) => {
return { processed: payload.data };
},
});Never wrap
triggerAndWait,batchTriggerAndWait, orwait.*inPromise.all— it is not supported and will cause unexpected behavior.
await processVideo.trigger(
{ videoId: "abc123", title: "My Video" },
{
idempotencyKey: `video-abc123`, // same key = same run, no duplicate
}
);Use idempotency keys whenever the same item could be triggered more than once — e.g., when a scheduled task polls a feed and an item might appear in two consecutive windows.
import { task, wait } from "@trigger.dev/sdk";
export const taskWithWaits = task({
id: "task-with-waits",
run: async (payload) => {
// Wait for a duration
await wait.for({ seconds: 30 });
await wait.for({ minutes: 5 });
await wait.for({ hours: 1 });
// Wait until a specific date
await wait.until({ date: new Date("2025-01-01") });
// Wait for an external signal (e.g., human approval)
await wait.forToken({
token: "approval-token",
timeoutInSeconds: 3600,
});
return { status: "completed" };
},
});Waits longer than 5 seconds are automatically checkpointed — they do not consume compute time while waiting.
Consolidate multiple rapid triggers into a single execution:
// Leading mode (default) — executes with the FIRST payload, delays on subsequent triggers
await myTask.trigger(
{ userId: "123" },
{
debounce: {
key: "user-123-update",
delay: "5s",
},
}
);
// Trailing mode — executes with the LAST payload after the delay window
await myTask.trigger(
{ data: "latest-value" },
{
debounce: {
key: "trailing-example",
delay: "10s",
mode: "trailing",
},
}
);export const resilientTask = task({
id: "resilient-task",
retry: {
maxAttempts: 10,
factor: 1.8, // exponential backoff multiplier
minTimeoutInMs: 500,
maxTimeoutInMs: 30_000,
randomize: false,
},
run: async (payload) => {
// If this throws, Trigger.dev will retry automatically
return { done: true };
},
});The standard pattern for automations that poll for new items and process each one:
// check-task.ts — runs on a schedule, lightweight
import { schedules } from "@trigger.dev/sdk";
import { processItem } from "./process-item.js"; // note: .js extension required
export const checkTask = schedules.task({
id: "check-task",
cron: "0 */8 * * *", // every 8 hours
run: async () => {
const items = await fetchNewItems(); // poll your source
for (const item of items) {
await processItem.trigger(
{ id: item.id, data: item },
{ idempotencyKey: `item-${item.id}` } // prevent duplicates
);
}
return { dispatched: items.length };
},
});
// process-item.ts — handles heavy work per item
import { task } from "@trigger.dev/sdk";
export const processItem = task({
id: "process-item",
run: async (payload: { id: string; data: any }) => {
// Do the heavy lifting: LLM calls, API requests, output posting
return { processed: payload.id };
},
});// ❌ This is Trigger.dev v2 syntax — DO NOT USE
client.defineJob({
id: "job-id",
run: async (payload, io) => { /* ... */ },
});Always use task(), schedules.task(), or schemaTask() from @trigger.dev/sdk.