smithery.ai

trigger-tasks

Covers writing backend Trigger.dev tasks with @trigger.dev/sdk: defining task() and schemaTask(), the run function and its ctx, retries, waits, queues and concurrency, idempotency keys, run metadata, logging, triggering other tasks (and the Result shape), scheduled/cron tasks, and the essentials of trigger.config.ts. Load this whenever you are authoring or editing code inside a /trigger directory, defining a task, or writing backend code that triggers tasks. Realtime/React hooks and AI chat are…

First seen Mar 14, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,497 B
  • docs SUMMARY.md 224 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

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: "[email protected]", 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()).

  1. 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).

  1. 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).

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

  1. 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.*.

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

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