Not yet assessed
Review the original instructions and requested permissions before installing.
No security review is available for this catalog entry yet.
Authors Trigger.dev backend tasks with retries, queues, schedules, concurrency, idempotency, and run metadata.
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 covered by separate skills.
Review the original instructions and requested permissions before installing.
No security review is available for this catalog entry yet.
How clearly the skill guides your agent, how complete its workflow is, and how you can check the outcome.
No quality assessment is available for this catalog entry yet.
Original instructions from the publisher’s 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
```ts
// /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.
```ts
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.
```ts
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.
```ts
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.
```ts
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"`.
```ts
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`).
```ts
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
```ts
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:
```ts
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
```ts
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/`.Files included alongside SKILL.md in the publisher’s repository.