trigger-tasks

Bao gồm việc viết các tác vụ Trigger.dev ở backend với @trigger.dev/sdk: định nghĩa task() và schemaTask(), hàm run và ctx của nó, retries, waits, queues và…

npx skills add https://github.com/triggerdotdev/skills --skill trigger-tasks

Authoring Trigger.dev Tasks

Tasks are functions that can run for a long time with strong resilience to failure. Define them in files under your /trigger directory. Always import from @trigger.dev/sdk. Never import from @trigger.dev/sdk/v3 (deprecated alias) or @trigger.dev/core.

Setup

// /trigger/hello-world.ts
import { task } from "@trigger.dev/sdk";

export const helloWorld = task({
  id: "hello-world", // unique within the project
  run: async (payload: { message: string }, { ctx }) => {
    console.log(payload.message, "attempt", ctx.attempt.number);
    return { ok: true }; // must be JSON serializable
  },
});

The run function receives the payload and a second argument with ctx (run context), an abort signal, and a deprecated init output. The return value is the task output and must be JSON serializable.

Core patterns

1. Validate the payload with schemaTask

schema accepts a Zod / Yup / Superstruct / ArkType / valibot / typebox parser or a custom (data: unknown) => T function. A validation failure throws TaskPayloadParsedError and skips retrying.

import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";

export const createUser = schemaTask({
  id: "create-user",
  schema: z.object({ name: z.string(), age: z.number() }),
  run: async (payload) => ({ greeting: `Hi ${payload.name}` }),
});

2. Configure retries and abort early

The default maxAttempts is 3. Throw AbortTaskRunError to stop retrying immediately. Task-level retry overrides the config-file defaults.

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

export const charge = task({
  id: "charge",
  retry: { maxAttempts: 5, factor: 1.8, minTimeoutInMs: 500, maxTimeoutInMs: 30_000, randomize: true },
  run: async (payload: { amount: number }) => {
    if (payload.amount <= 0) throw new AbortTaskRunError("Invalid amount"); // no retry
    // work that may throw and retry
  },
});

For finer control, catchError: async ({ payload, error, ctx, retryAt }) => {...} can return { skipRetrying: true }, { retryAt: Date }, or undefined (use normal logic). retry.onThrow, retry.fetch, also exist for in-task retrying.

3. Trigger another task and handle the Result

From inside a task use yourTask.triggerAndWait(payload). The result is a Result object that you must check (ok), or .unwrap() to throw on failure.

export const parentTask = task({
  id: "parent-task",
  run: async () => {
    const result = await childTask.triggerAndWait({ data: "x" });
    if (result.ok) return result.output; // typed child output
    console.error("child failed", result.error);
    // or: const output = await childTask.triggerAndWait({ data: "x" }).unwrap();
  },
});

SubtaskUnwrapError carries runId, taskId, and cause. For fan-out use childTask.batchTriggerAndWait([{ payload: a }, { payload: b }]); the result has a .runs array, each entry { ok, id, output?, error?, taskIdentifier }.

4. Trigger from backend code with a type-only import

Outside a task, import the task type only and trigger by id. Do not import the task instance into backend bundles.

import { tasks } from "@trigger.dev/sdk";
import type { emailSequence } from "~/trigger/emails";

const handle = await tasks.trigger<typeof emailSequence>(
  "email-sequence",
  { to: "a@b.com", name: "Ada" },
  { delay: "1h" }
);

tasks.batchTrigger and batch.trigger([{ id, payload }]) cover batches. Trigger options include delay, ttl, idempotencyKey, idempotencyKeyTTL, debounce, queue, concurrencyKey, maxAttempts, tags, metadata, priority, region, and machine. Inspect runs with runs.retrieve, runs.cancel, and runs.reschedule.

5. Idempotency keys

idempotencyKeys.create(key, { scope }) returns a 64-char hashed key. A raw string key defaults to "run" scope (v4.3.1+); for once-ever behavior use scope: "global".

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

export const processOrder = task({
  id: "process-order",
  run: async (payload: { orderId: string; email: string }) => {
    const key = await idempotencyKeys.create(`confirm-${payload.orderId}`);
    await sendEmail.trigger({ to: payload.email }, { idempotencyKey: key });
  },
});

6. Waits and run metadata

wait.for({ seconds }) and wait.until({ date }) durably pause the run. metadata.* is readable and writable only inside run(); updates are synchronous and chainable (set, del, replace, append, remove, increment, decrement).

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

export const importer = task({
  id: "importer",
  run: async (payload: { rows: unknown[] }) => {
    metadata.set("status", "processing").set("total", payload.rows.length);
    await wait.for({ seconds: 5 });
    metadata.set("status", "complete");
  },
});

For human-in-the-loop, wait.createToken({ timeout, tags }) returns { id, url, publicAccessToken, ... }; resume with wait.forToken<T>(token: string | { id: string }) which returns { ok, output?, error? } (or .unwrap()), and complete it elsewhere with wait.completeToken(tokenId, output). Metadata max is 256KB and is not propagated to child tasks; push values to a parent with metadata.parent.* / metadata.root.*. (metadata.stream is deprecated since 4.1.0 in favor of streams.pipe().)

7. Scheduled (cron) tasks

import { schedules } from "@trigger.dev/sdk";

export const dailyReport = schedules.task({
  id: "daily-report",
  cron: { pattern: "0 5 * * *", timezone: "Asia/Tokyo" },
  run: async (payload) => {
    console.log("scheduled at", payload.timestamp, "next", payload.upcoming);
  },
});

The payload includes timestamp, lastTimestamp, timezone, scheduleId, externalId, and upcoming. Attach schedules dynamically with schedules.create({ task, cron, timezone?, externalId?, deduplicationKey }) (the dedup key is required and per-project), plus retrieve / list / update / activate / deactivate / del / timezones.

8. Queues and concurrency

Set queue: { concurrencyLimit } on a task, or share a queue across tasks:

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

export const emails = queue({ name: "emails", concurrencyLimit: 5 });

export const sendEmail = task({ id: "send-email", queue: emails, run: async () => {} });

At trigger time override with { queue: "queue-name" } and add concurrencyKey for per-tenant queues. Manage queues with queues.list / retrieve / pause / resume / overrideConcurrencyLimit / resetConcurrencyLimit.

9. trigger.config.ts essentials

import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "<project ref>",
  dirs: ["./trigger"],
  machine: "small-1x",
  retries: {
    enabledInDev: false,
    default: { maxAttempts: 3, factor: 2, minTimeoutInMs: 1000, maxTimeoutInMs: 10000, randomize: true },
  },
});

build.external controls which packages stay out of the bundle. Build extensions (additionalFiles, prismaExtension, puppeteer, playwright, ffmpeg, pythonExtension, aptGet, syncEnvVars, etc.) come from @trigger.dev/build. telemetry configures instrumentations and exporters. Each extension has its own setup doc, all bundled under @trigger.dev/sdk/docs/config/extensions/ (start with overview.mdx); read the one you need before wiring it up rather than guessing the API.

Logging

logger.debug / log / info / warn / error(message, dataRecord?) write structured logs; logger.trace(name, async (span) => {...}) adds a span. Module-level metrics use otel.metrics.getMeter(name).

Common mistakes

  1. CRITICAL: Treating the wait result as the output. triggerAndWait and wait.forToken return a Result object, not the raw output.

    • Wrong: const out = await childTask.triggerAndWait(p); use(out.foo);
    • Correct: const r = await childTask.triggerAndWait(p); if (r.ok) use(r.output.foo); (or .unwrap()).
  2. Wrapping triggerAndWait / batchTriggerAndWait / wait in Promise.all.

    • Wrong: await Promise.all([childTask.triggerAndWait(a), childTask.triggerAndWait(b)]);
    • Correct: await childTask.batchTriggerAndWait([{ payload: a }, { payload: b }]); (or a sequential for-loop).
  3. Importing the task instance into backend code.

    • Wrong: import { emailSequence } from "~/trigger/emails"; in a route handler.
    • Correct: import type { emailSequence } plus tasks.trigger<typeof emailSequence>("email-sequence", payload).
  4. Calling metadata.set/get outside run().

    • Wrong: setting metadata at module scope or in unrelated backend code (a no-op; get returns undefined).
    • Correct: call inside run() or a task lifecycle hook.
  5. Assuming child tasks inherit the parent's queue or metadata.

    • Wrong: expecting a subtask to share the parent's concurrencyLimit or see its metadata.
    • Correct: subtasks run on their own queue; pass metadata explicitly via { metadata: metadata.current() }, or push up with metadata.parent.*.
  6. Bundling native/WASM packages.

    • Wrong: leaving sharp, re2, sqlite3, or WASM packages in the default bundle.
    • Correct: add them to build.external in trigger.config.ts.
  7. Relying on a raw string idempotency key being global.

    • Wrong: trigger(p, { idempotencyKey: "welcome-email" }) expecting once-ever (true only in v4.3.0 and earlier).
    • Correct: await idempotencyKeys.create("welcome-email", { scope: "global" }).

References

Sibling skills:

  • trigger-realtime for subscribing to runs and triggering from the frontend with React hooks.
  • trigger-authoring-chat-agent and trigger-chat-agent-advanced for building AI chat agents.

Reference docs ship beside this skill in the same package, read them locally (no network), pinned to your installed version. The sources: frontmatter above lists every doc this skill draws from, all under @trigger.dev/sdk/docs/. Start with:

  • @trigger.dev/sdk/docs/tasks/overview.mdx
  • @trigger.dev/sdk/docs/triggering.mdx
  • @trigger.dev/sdk/docs/config/config-file.mdx

Version

This skill is bundled inside @trigger.dev/sdk and read directly from node_modules, so it always matches your installed SDK version (see the adjacent package.json). The full documentation for these APIs ships alongside it under @trigger.dev/sdk/docs/.

Thêm skills từ triggerdotdev

trigger-dev-tasks
triggerdotdev
Sử dụng kỹ năng này khi viết, thiết kế hoặc tối ưu hóa các tác vụ nền và quy trình làm việc của Trigger.dev. Điều này bao gồm việc tạo các tác vụ bất đồng bộ đáng tin cậy, triển khai AI…
official
trigger-authoring-chat-agent
triggerdotdev
Tác giả và chạy một tác nhân trò chuyện AI bền vững với chat.agent từ @trigger.dev/sdk/ai: vòng lặp chạy theo từng lượt, lý do bạn PHẢI trải rộng ...chat.toStreamTextOptions()…
official
trigger-agents
triggerdotdev
Các mẫu tác tử AI với Trigger.dev - điều phối, song song hóa, định tuyến, đánh giá-tối ưu hóa và có sự tham gia của con người. Sử dụng khi xây dựng các tác vụ dựa trên LLM…
official
trigger-config
triggerdotdev
Cấu hình các dự án Trigger.dev với trigger.config.ts. Sử dụng khi thiết lập các tiện ích mở rộng xây dựng cho Prisma, Playwright, FFmpeg, Python hoặc tùy chỉnh triển khai…
official
trigger-cost-savings
triggerdotdev
Phân tích các tác vụ, lịch trình và lần chạy của Trigger.dev để tìm cơ hội tối ưu hóa chi phí. Sử dụng khi được yêu cầu giảm chi tiêu, tối ưu hóa chi phí, kiểm toán mức sử dụng, điều chỉnh quy mô phù hợp…
official
trigger-realtime
triggerdotdev
Đăng ký các lần chạy tác vụ Trigger.dev theo thời gian thực từ frontend và backend. Sử dụng khi xây dựng chỉ báo tiến trình, bảng điều khiển trực tiếp, phản hồi AI/LLM phát trực tuyến,…
official
trigger-setup
triggerdotdev
Thiết lập Trigger.dev trong dự án của bạn. Sử dụng khi thêm Trigger.dev lần đầu, tạo trigger.config.ts, hoặc khởi tạo thư mục trigger.
official
trigger-tasks
triggerdotdev
Xây dựng các tác nhân AI, quy trình làm việc và tác vụ nền bền vững với Trigger.dev. Sử dụng khi tạo tác vụ, kích hoạt công việc, xử lý thử lại, lập lịch cron, hoặc…
official