trigger-tasks

Bangun agen AI, alur kerja, dan tugas latar belakang yang tahan lama dengan Trigger.dev. Gunakan saat membuat tugas, memicu pekerjaan, menangani percobaan ulang, menjadwalkan pekerjaan cron, atau…

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/.

Lebih banyak skill dari triggerdotdev

trigger-dev-tasks
triggerdotdev
Gunakan keterampilan ini saat menulis, mendesain, atau mengoptimalkan tugas latar belakang dan alur kerja Trigger.dev. Ini termasuk membuat tugas asinkron yang andal, mengimplementasikan AI…
official
trigger-authoring-chat-agent
triggerdotdev
Author and run a durable AI chat agent with chat.agent from @trigger.dev/sdk/ai: the per-turn run loop, why you MUST spread ...chat.toStreamTextOptions()…
official
trigger-agents
triggerdotdev
Pola agen AI dengan Trigger.dev - orkestrasi, paralelisasi, perutean, evaluator-optimizer, dan human-in-the-loop. Gunakan saat membangun tugas bertenaga LLM…
official
trigger-config
triggerdotdev
Konfigurasikan proyek Trigger.dev dengan trigger.config.ts. Gunakan saat menyiapkan ekstensi build untuk Prisma, Playwright, FFmpeg, Python, atau menyesuaikan deployment…
official
trigger-cost-savings
triggerdotdev
Analisis tugas, jadwal, dan proses di Trigger.dev untuk menemukan peluang pengoptimalan biaya. Gunakan saat diminta untuk mengurangi pengeluaran, mengoptimalkan biaya, mengaudit penggunaan, menyesuaikan ukuran…
official
trigger-realtime
triggerdotdev
Berlangganan ke proses tugas Trigger.dev secara real-time dari frontend dan backend. Gunakan saat membangun indikator progres, dasbor langsung, respons AI/LLM streaming,…
official
trigger-setup
triggerdotdev
Siapkan Trigger.dev di proyek Anda. Gunakan saat menambahkan Trigger.dev untuk pertama kali, membuat trigger.config.ts, atau menginisialisasi direktori trigger.
official
trigger-tasks
triggerdotdev
Covers writing backend Trigger.dev tasks with @trigger.dev/sdk: defining task() and schemaTask(), the run function and its ctx, retries, waits, queues and…
official