RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/Cursor rules/triggerdotdev/trigger.dev

Cursor rule

.cursor/rules/writing-tasks.mdc

Guidelines for writing Trigger.dev tasks

Cursor rules

Quality

64/100

Scores the file, not the repository.

Length

4,289 words

80 headings · 63 code blocks

Repository

16k

— · pushed 0 days ago

Last changed

3 days ago

First indexed 3 days ago.
triggerdotdev/trigger.dev/.cursor/rules/writing-tasks.mdcRawGitHub
1---
2globs: **/trigger/**/*.ts, **/trigger/**/*.tsx,**/trigger/**/*.js,**/trigger/**/*.jsx
3description: Guidelines for writing Trigger.dev tasks
4alwaysApply: false
5---
6# How to write Trigger.dev tasks
7 
8## Overview of writing a Trigger.dev task
9 
101. Run the CLI `init` command: `npx trigger.dev@latest init`.
112. Create a Trigger.dev task.
123. Set up any environment variables.
134. Run the Trigger.dev dev command: `npx trigger.dev@latest dev`.
14 
15## Essential requirements when generating task code
16 
171. You MUST import from `@trigger.dev/sdk` (NEVER `@trigger.dev/sdk/v3`)
182. You MUST NEVER use `client.defineJob`
193. YOU MUST `export` every task, including subtasks
204. If you are able to generate an example payload for a task, do so.
21 
22## 🚨 CRITICAL INSTRUCTIONS FOR AI LANGUAGE MODELS 🚨
23 
24As an AI language model, you MUST NOT generate any of the following code patterns, as they are DEPRECATED and will BREAK the application:
25 
26```ts
27// ❌ NEVER GENERATE THIS CODE - IT WILL BREAK THE APPLICATION
28 
29client.defineJob({ // ❌ BREAKS APPLICATION
30 id: "job-id", // ❌ BREAKS APPLICATION
31 name: "job-name", // ❌ BREAKS APPLICATION
32 version: "0.0.1", // ❌ BREAKS APPLICATION
33 trigger: eventTrigger({ // ❌ BREAKS APPLICATION
34 name: "job.trigger", // ❌ BREAKS APPLICATION
35 schema: z.object({ // ❌ BREAKS APPLICATION
36 // Input schema here // ❌ BREAKS APPLICATION
37 }), // ❌ BREAKS APPLICATION
38 }), // ❌ BREAKS APPLICATION
39 integrations: { // ❌ BREAKS APPLICATION
40 // Integrations here // ❌ BREAKS APPLICATION
41 }, // ❌ BREAKS APPLICATION
42 run: async (payload, io) => { // ❌ BREAKS APPLICATION
43 // Job logic goes here // ❌ BREAKS APPLICATION
44 return { // ❌ BREAKS APPLICATION
45 // Return job results // ❌ BREAKS APPLICATION
46 }; // ❌ BREAKS APPLICATION
47 }, // ❌ BREAKS APPLICATION
48});
49```
50 
51Instead, you MUST ALWAYS generate ONLY this pattern:
52 
53```ts
54// ✅ ALWAYS GENERATE THIS EXACT PATTERN
55 
56import { task } from "@trigger.dev/sdk";
57 
58//1. You need to export each task, even if it's a subtask
59export const helloWorld = task({
60 //2. Use a unique id for each task
61 id: "hello-world",
62 //3. The run function is the main function of the task
63 run: async (payload: { message: string }) => {
64 //4. Write your task code here. Code here runs for a long time, there are no timeouts
65 },
66});
67```
68 
69## Correct Task implementations
70 
71A task is a function that can run for a long time with resilience to failure:
72 
73```ts
74import { task } from "@trigger.dev/sdk";
75 
76export const helloWorld = task({
77 id: "hello-world",
78 run: async (payload: { message: string }) => {
79 console.log(payload.message);
80 },
81});
82```
83 
84Key points:
85- Tasks must be exported, even subtasks in the same file
86- Each task needs a unique ID within your project
87- The `run` function contains your task logic
88 
89### Task configuration options
90 
91#### Retry options
92 
93Control retry behavior when errors occur:
94 
95```ts
96export const taskWithRetries = task({
97 id: "task-with-retries",
98 retry: {
99 maxAttempts: 10,
100 factor: 1.8,
101 minTimeoutInMs: 500,
102 maxTimeoutInMs: 30_000,
103 randomize: false,
104 },
105 run: async (payload) => {
106 // Task logic
107 },
108});
109```
110 
111#### Queue options
112 
113Control concurrency:
114 
115```ts
116export const oneAtATime = task({
117 id: "one-at-a-time",
118 queue: {
119 concurrencyLimit: 1,
120 },
121 run: async (payload) => {
122 // Task logic
123 },
124});
125```
126 
127#### Machine options
128 
129Specify CPU/RAM requirements:
130 
131```ts
132export const heavyTask = task({
133 id: "heavy-task",
134 machine: {
135 preset: "large-1x", // 4 vCPU, 8 GB RAM
136 },
137 run: async (payload) => {
138 // Task logic
139 },
140});
141```
142 
143Machine configuration options:
144 
145| Machine name | vCPU | Memory | Disk space |
146| ------------------- | ---- | ------ | ---------- |
147| micro | 0.25 | 0.25 | 10GB |
148| small-1x (default) | 0.5 | 0.5 | 10GB |
149| small-2x | 1 | 1 | 10GB |
150| medium-1x | 1 | 2 | 10GB |
151| medium-2x | 2 | 4 | 10GB |
152| large-1x | 4 | 8 | 10GB |
153| large-2x | 8 | 16 | 10GB |
154 
155#### Max Duration
156 
157Limit how long a task can run:
158 
159```ts
160export const longTask = task({
161 id: "long-task",
162 maxDuration: 300, // 5 minutes
163 run: async (payload) => {
164 // Task logic
165 },
166});
167```
168 
169### Lifecycle functions
170 
171Tasks support several lifecycle hooks:
172 
173#### init
174 
175Runs before each attempt, can return data for other functions:
176 
177```ts
178export const taskWithInit = task({
179 id: "task-with-init",
180 init: async (payload, { ctx }) => {
181 return { someData: "someValue" };
182 },
183 run: async (payload, { ctx, init }) => {
184 console.log(init.someData); // "someValue"
185 },
186});
187```
188 
189#### cleanup
190 
191Runs after each attempt, regardless of success/failure:
192 
193```ts
194export const taskWithCleanup = task({
195 id: "task-with-cleanup",
196 cleanup: async (payload, { ctx }) => {
197 // Cleanup resources
198 },
199 run: async (payload, { ctx }) => {
200 // Task logic
201 },
202});
203```
204 
205#### onStart
206 
207Runs once when a task starts (not on retries):
208 
209```ts
210export const taskWithOnStart = task({
211 id: "task-with-on-start",
212 onStart: async (payload, { ctx }) => {
213 // Send notification, log, etc.
214 },
215 run: async (payload, { ctx }) => {
216 // Task logic
217 },
218});
219```
220 
221#### onSuccess
222 
223Runs when a task succeeds:
224 
225```ts
226export const taskWithOnSuccess = task({
227 id: "task-with-on-success",
228 onSuccess: async (payload, output, { ctx }) => {
229 // Handle success
230 },
231 run: async (payload, { ctx }) => {
232 // Task logic
233 },
234});
235```
236 
237#### onFailure
238 
239Runs when a task fails after all retries:
240 
241```ts
242export const taskWithOnFailure = task({
243 id: "task-with-on-failure",
244 onFailure: async (payload, error, { ctx }) => {
245 // Handle failure
246 },
247 run: async (payload, { ctx }) => {
248 // Task logic
249 },
250});
251```
252 
253#### handleError
254 
255Controls error handling and retry behavior:
256 
257```ts
258export const taskWithErrorHandling = task({
259 id: "task-with-error-handling",
260 handleError: async (error, { ctx }) => {
261 // Custom error handling
262 },
263 run: async (payload, { ctx }) => {
264 // Task logic
265 },
266});
267```
268 
269Global lifecycle hooks can also be defined in `trigger.config.ts` to apply to all tasks.
270 
271## Correct Schedules task (cron) implementations
272 
273```ts
274import { schedules } from "@trigger.dev/sdk";
275 
276export const firstScheduledTask = schedules.task({
277 id: "first-scheduled-task",
278 run: async (payload) => {
279 //when the task was scheduled to run
280 //note this will be slightly different from new Date() because it takes a few ms to run the task
281 console.log(payload.timestamp); //is a Date object
282 
283 //when the task was last run
284 //this can be undefined if it's never been run
285 console.log(payload.lastTimestamp); //is a Date object or undefined
286 
287 //the timezone the schedule was registered with, defaults to "UTC"
288 //this is in IANA format, e.g. "America/New_York"
289 //See the full list here: https://cloud.trigger.dev/timezones
290 console.log(payload.timezone); //is a string
291 
292 //If you want to output the time in the user's timezone do this:
293 const formatted = payload.timestamp.toLocaleString("en-US", {
294 timeZone: payload.timezone,
295 });
296 
297 //the schedule id (you can have many schedules for the same task)
298 //using this you can remove the schedule, update it, etc
299 console.log(payload.scheduleId); //is a string
300 
301 //you can optionally provide an external id when creating the schedule
302 //usually you would set this to a userId or some other unique identifier
303 //this can be undefined if you didn't provide one
304 console.log(payload.externalId); //is a string or undefined
305 
306 //the next 5 dates this task is scheduled to run
307 console.log(payload.upcoming); //is an array of Date objects
308 },
309});
310```
311 
312### Attach a Declarative schedule
313 
314```ts
315import { schedules } from "@trigger.dev/sdk";
316 
317// Sepcify a cron pattern (UTC)
318export const firstScheduledTask = schedules.task({
319 id: "first-scheduled-task",
320 //every two hours (UTC timezone)
321 cron: "0 */2 * * *",
322 run: async (payload, { ctx }) => {
323 //do something
324 },
325});
326```
327 
328```ts
329import { schedules } from "@trigger.dev/sdk";
330 
331// Specify a specific timezone like this:
332export const secondScheduledTask = schedules.task({
333 id: "second-scheduled-task",
334 cron: {
335 //5am every day Tokyo time
336 pattern: "0 5 * * *",
337 timezone: "Asia/Tokyo",
338 },
339 run: async (payload) => {},
340});
341```
342 
343### Attach an Imperative schedule
344 
345Create schedules explicitly for tasks using the dashboard's "New schedule" button or the SDK.
346 
347#### Benefits
348- Dynamic creation (e.g., one schedule per user)
349- Manage without code deployment:
350 - Activate/disable
351 - Edit
352 - Delete
353 
354#### Implementation
3551. Define a task using `⁠schedules.task()`
3562. Attach one or more schedules via:
357 - Dashboard
358 - SDK
359 
360#### Attach schedules with the SDK like this
361 
362```ts
363const createdSchedule = await schedules.create({
364 //The id of the scheduled task you want to attach to.
365 task: firstScheduledTask.id,
366 //The schedule in cron format.
367 cron: "0 0 * * *",
368 //this is required, it prevents you from creating duplicate schedules. It will update the schedule if it already exists.
369 deduplicationKey: "my-deduplication-key",
370});
371```
372 
373## Correct Schema task implementations
374 
375Schema tasks validate payloads against a schema before execution:
376 
377```ts
378import { schemaTask } from "@trigger.dev/sdk";
379import { z } from "zod";
380 
381const myTask = schemaTask({
382 id: "my-task",
383 schema: z.object({
384 name: z.string(),
385 age: z.number(),
386 }),
387 run: async (payload) => {
388 // Payload is typed and validated
389 console.log(payload.name, payload.age);
390 },
391});
392```
393 
394## Correct implementations for triggering a task from your backend
395 
396When you trigger a task from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable. You can find the value on the API keys page in the Trigger.dev dashboard.
397 
398### tasks.trigger()
399 
400Triggers a single run of a task with specified payload and options without importing the task. Use type-only imports for full type checking.
401 
402```ts
403import { tasks } from "@trigger.dev/sdk";
404import type { emailSequence } from "~/trigger/emails";
405 
406export async function POST(request: Request) {
407 const data = await request.json();
408 const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
409 to: data.email,
410 name: data.name,
411 });
412 return Response.json(handle);
413}
414```
415 
416### tasks.batchTrigger()
417 
418Triggers multiple runs of a single task with different payloads without importing the task.
419 
420```ts
421import { tasks } from "@trigger.dev/sdk";
422import type { emailSequence } from "~/trigger/emails";
423 
424export async function POST(request: Request) {
425 const data = await request.json();
426 const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
427 "email-sequence",
428 data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
429 );
430 return Response.json(batchHandle);
431}
432```
433 
434### batch.trigger()
435 
436Triggers multiple runs of different tasks at once, useful when you need to execute multiple tasks simultaneously.
437 
438```ts
439import { batch } from "@trigger.dev/sdk";
440import type { myTask1, myTask2 } from "~/trigger/myTasks";
441 
442export async function POST(request: Request) {
443 const data = await request.json();
444 const result = await batch.trigger<typeof myTask1 | typeof myTask2>([
445 { id: "my-task-1", payload: { some: data.some } },
446 { id: "my-task-2", payload: { other: data.other } },
447 ]);
448 return Response.json(result);
449}
450```
451 
452## Correct implementations for triggering a task from inside another task
453 
454### yourTask.trigger()
455 
456Triggers a single run of a task with specified payload and options.
457 
458```ts
459import { myOtherTask, runs } from "~/trigger/my-other-task";
460 
461export const myTask = task({
462 id: "my-task",
463 run: async (payload: string) => {
464 const handle = await myOtherTask.trigger({ foo: "some data" });
465 
466 const run = await runs.retrieve(handle);
467 // Do something with the run
468 },
469});
470```
471 
472If you need to call `trigger()` on a task in a loop, use `batchTrigger()` instead which can trigger up to 500 runs in a single call.
473 
474### yourTask.batchTrigger()
475 
476Triggers multiple runs of a single task with different payloads.
477 
478```ts
479import { myOtherTask, batch } from "~/trigger/my-other-task";
480 
481export const myTask = task({
482 id: "my-task",
483 run: async (payload: string) => {
484 const batchHandle = await myOtherTask.batchTrigger([{ payload: "some data" }]);
485 
486 //...do other stuff
487 const batch = await batch.retrieve(batchHandle.id);
488 },
489});
490```
491 
492### yourTask.triggerAndWait()
493 
494Triggers a task and waits for the result, useful when you need to call a different task and use its result.
495 
496```ts
497export const parentTask = task({
498 id: "parent-task",
499 run: async (payload: string) => {
500 const result = await childTask.triggerAndWait("some-data");
501 console.log("Result", result);
502 
503 //...do stuff with the result
504 },
505});
506```
507 
508The result object needs to be checked to see if the child task run was successful. You can also use the `unwrap` method to get the output directly or handle errors with `SubtaskUnwrapError`. This method should only be used inside a task.
509 
510### yourTask.batchTriggerAndWait()
511 
512Batch triggers a task and waits for all results, useful for fan-out patterns.
513 
514```ts
515export const batchParentTask = task({
516 id: "parent-task",
517 run: async (payload: string) => {
518 const results = await childTask.batchTriggerAndWait([
519 { payload: "item4" },
520 { payload: "item5" },
521 { payload: "item6" },
522 ]);
523 console.log("Results", results);
524 
525 //...do stuff with the result
526 },
527});
528```
529 
530You can handle run failures by inspecting individual run results and implementing custom error handling strategies. This method should only be used inside a task.
531 
532### batch.triggerAndWait()
533 
534Batch triggers multiple different tasks and waits for all results.
535 
536```ts
537export const parentTask = task({
538 id: "parent-task",
539 run: async (payload: string) => {
540 const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
541 { id: "child-task-1", payload: { foo: "World" } },
542 { id: "child-task-2", payload: { bar: 42 } },
543 ]);
544 
545 for (const result of results) {
546 if (result.ok) {
547 switch (result.taskIdentifier) {
548 case "child-task-1":
549 console.log("Child task 1 output", result.output);
550 break;
551 case "child-task-2":
552 console.log("Child task 2 output", result.output);
553 break;
554 }
555 }
556 }
557 },
558});
559```
560 
561### batch.triggerByTask()
562 
563Batch triggers multiple tasks by passing task instances, useful for static task sets.
564 
565```ts
566export const parentTask = task({
567 id: "parent-task",
568 run: async (payload: string) => {
569 const results = await batch.triggerByTask([
570 { task: childTask1, payload: { foo: "World" } },
571 { task: childTask2, payload: { bar: 42 } },
572 ]);
573 
574 const run1 = await runs.retrieve(results.runs[0]);
575 const run2 = await runs.retrieve(results.runs[1]);
576 },
577});
578```
579 
580### batch.triggerByTaskAndWait()
581 
582Batch triggers multiple tasks by passing task instances and waits for all results.
583 
584```ts
585export const parentTask = task({
586 id: "parent-task",
587 run: async (payload: string) => {
588 const { runs } = await batch.triggerByTaskAndWait([
589 { task: childTask1, payload: { foo: "World" } },
590 { task: childTask2, payload: { bar: 42 } },
591 ]);
592 
593 if (runs[0].ok) {
594 console.log("Child task 1 output", runs[0].output);
595 }
596 
597 if (runs[1].ok) {
598 console.log("Child task 2 output", runs[1].output);
599 }
600 },
601});
602```
603 
604## Correct Metadata implementation
605 
606### Overview
607 
608Metadata allows attaching up to 256KB of structured data to a run, which can be accessed during execution, via API, Realtime, and in the dashboard. Useful for storing user information, tracking progress, or saving intermediate results.
609 
610### Basic Usage
611 
612Add metadata when triggering a task:
613 
614```ts
615const handle = await myTask.trigger(
616 { message: "hello world" },
617 { metadata: { user: { name: "Eric", id: "user_1234" } } }
618);
619```
620 
621Access metadata inside a run:
622 
623```ts
624import { task, metadata } from "@trigger.dev/sdk";
625 
626export const myTask = task({
627 id: "my-task",
628 run: async (payload: { message: string }) => {
629 // Get the whole metadata object
630 const currentMetadata = metadata.current();
631
632 // Get a specific key
633 const user = metadata.get("user");
634 console.log(user.name); // "Eric"
635 },
636});
637```
638 
639### Update methods
640 
641Metadata can be updated as the run progresses:
642 
643- **set**: `metadata.set("progress", 0.5)`
644- **del**: `metadata.del("progress")`
645- **replace**: `metadata.replace({ user: { name: "Eric" } })`
646- **append**: `metadata.append("logs", "Step 1 complete")`
647- **remove**: `metadata.remove("logs", "Step 1 complete")`
648- **increment**: `metadata.increment("progress", 0.4)`
649- **decrement**: `metadata.decrement("progress", 0.4)`
650- **stream**: `await metadata.stream("logs", readableStream)`
651- **flush**: `await metadata.flush()`
652 
653Updates can be chained with a fluent API:
654 
655```ts
656metadata.set("progress", 0.1)
657 .append("logs", "Step 1 complete")
658 .increment("progress", 0.4);
659```
660 
661### Parent & root updates
662 
663Child tasks can update parent task metadata:
664 
665```ts
666export const childTask = task({
667 id: "child-task",
668 run: async (payload: { message: string }) => {
669 // Update parent task's metadata
670 metadata.parent.set("progress", 0.5);
671
672 // Update root task's metadata
673 metadata.root.set("status", "processing");
674 },
675});
676```
677 
678### Type safety
679 
680Metadata accepts any JSON-serializable object. For type safety, consider wrapping with Zod:
681 
682```ts
683import { z } from "zod";
684 
685const Metadata = z.object({
686 user: z.object({
687 name: z.string(),
688 id: z.string(),
689 }),
690 date: z.coerce.date(),
691});
692 
693function getMetadata() {
694 return Metadata.parse(metadata.current());
695}
696```
697 
698### Important notes
699 
700- Metadata methods only work inside run functions or task lifecycle hooks
701- Metadata is NOT automatically propagated to child tasks
702- Maximum size is 256KB (configurable if self-hosting)
703- Objects like Dates are serialized to strings and must be deserialized when retrieved
704 
705## Correct Realtime implementation
706 
707### Overview
708 
709Trigger.dev Realtime enables subscribing to runs for real-time updates on run status, useful for monitoring tasks, updating UIs, and building realtime dashboards. It's built on Electric SQL, a PostgreSQL syncing engine.
710 
711### Basic usage
712 
713Subscribe to a run after triggering a task:
714 
715```ts
716import { runs, tasks } from "@trigger.dev/sdk";
717 
718async function myBackend() {
719 const handle = await tasks.trigger("my-task", { some: "data" });
720 
721 for await (const run of runs.subscribeToRun(handle.id)) {
722 console.log(run); // Logs the run every time it changes
723 }
724}
725```
726 
727### Subscription methods
728 
729- **subscribeToRun**: Subscribe to changes for a specific run
730- **subscribeToRunsWithTag**: Subscribe to changes for all runs with a specific tag
731- **subscribeToBatch**: Subscribe to changes for all runs in a batch
732 
733### Type safety
734 
735You can infer types of run's payload and output by passing the task type:
736 
737```ts
738import { runs } from "@trigger.dev/sdk";
739import type { myTask } from "./trigger/my-task";
740 
741for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
742 console.log(run.payload.some); // Type-safe access to payload
743
744 if (run.output) {
745 console.log(run.output.result); // Type-safe access to output
746 }
747}
748```
749 
750### Realtime Streams
751 
752Stream data in realtime from inside your tasks using the metadata system:
753 
754```ts
755import { task, metadata } from "@trigger.dev/sdk";
756import OpenAI from "openai";
757 
758export type STREAMS = {
759 openai: OpenAI.ChatCompletionChunk;
760};
761 
762export const myTask = task({
763 id: "my-task",
764 run: async (payload: { prompt: string }) => {
765 const completion = await openai.chat.completions.create({
766 messages: [{ role: "user", content: payload.prompt }],
767 model: "gpt-3.5-turbo",
768 stream: true,
769 });
770 
771 // Register the stream with the key "openai"
772 const stream = await metadata.stream("openai", completion);
773 
774 let text = "";
775 for await (const chunk of stream) {
776 text += chunk.choices.map((choice) => choice.delta?.content).join("");
777 }
778 
779 return { text };
780 },
781});
782```
783 
784Subscribe to streams using `withStreams`:
785 
786```ts
787for await (const part of runs.subscribeToRun<typeof myTask>(runId).withStreams<STREAMS>()) {
788 switch (part.type) {
789 case "run": {
790 console.log("Received run", part.run);
791 break;
792 }
793 case "openai": {
794 console.log("Received OpenAI chunk", part.chunk);
795 break;
796 }
797 }
798}
799```
800 
801## Realtime hooks
802 
803### Installation
804 
805```bash
806npm add @trigger.dev/react-hooks
807```
808 
809### Authentication
810 
811All hooks require a Public Access Token. You can provide it directly to each hook:
812 
813```ts
814import { useRealtimeRun } from "@trigger.dev/react-hooks";
815 
816function MyComponent({ runId, publicAccessToken }) {
817 const { run, error } = useRealtimeRun(runId, {
818 accessToken: publicAccessToken,
819 baseURL: "https://your-trigger-dev-instance.com", // Optional for self-hosting
820 });
821}
822```
823 
824Or use the `TriggerAuthContext` provider:
825 
826```ts
827import { TriggerAuthContext } from "@trigger.dev/react-hooks";
828 
829function SetupTrigger({ publicAccessToken }) {
830 return (
831 <TriggerAuthContext.Provider value={{ accessToken: publicAccessToken }}>
832 <MyComponent />
833 </TriggerAuthContext.Provider>
834 );
835}
836```
837 
838For Next.js App Router, wrap the provider in a client component:
839 
840```ts
841// components/TriggerProvider.tsx
842"use client";
843 
844import { TriggerAuthContext } from "@trigger.dev/react-hooks";
845 
846export function TriggerProvider({ accessToken, children }) {
847 return (
848 <TriggerAuthContext.Provider value={{ accessToken }}>
849 {children}
850 </TriggerAuthContext.Provider>
851 );
852}
853```
854 
855### Passing tokens to the frontend
856 
857Several approaches for Next.js App Router:
858 
8591. **Using cookies**:
860```ts
861// Server action
862export async function startRun() {
863 const handle = await tasks.trigger<typeof exampleTask>("example", { foo: "bar" });
864 cookies().set("publicAccessToken", handle.publicAccessToken);
865 redirect(`/runs/${handle.id}`);
866}
867 
868// Page component
869export default function RunPage({ params }) {
870 const publicAccessToken = cookies().get("publicAccessToken");
871 return (
872 <TriggerProvider accessToken={publicAccessToken}>
873 <RunDetails id={params.id} />
874 </TriggerProvider>
875 );
876}
877```
878 
8792. **Using query parameters**:
880```ts
881// Server action
882export async function startRun() {
883 const handle = await tasks.trigger<typeof exampleTask>("example", { foo: "bar" });
884 redirect(`/runs/${handle.id}?publicAccessToken=${handle.publicAccessToken}`);
885}
886```
887 
8883. **Server-side token generation**:
889```ts
890// Page component
891export default async function RunPage({ params }) {
892 const publicAccessToken = await generatePublicAccessToken(params.id);
893 return (
894 <TriggerProvider accessToken={publicAccessToken}>
895 <RunDetails id={params.id} />
896 </TriggerProvider>
897 );
898}
899 
900// Token generation function
901export async function generatePublicAccessToken(runId: string) {
902 return auth.createPublicToken({
903 scopes: {
904 read: {
905 runs: [runId],
906 },
907 },
908 expirationTime: "1h",
909 });
910}
911```
912 
913### Hook types
914 
915#### SWR hooks
916 
917Data fetching hooks that use SWR for caching:
918 
919```ts
920"use client";
921import { useRun } from "@trigger.dev/react-hooks";
922import type { myTask } from "@/trigger/myTask";
923 
924function MyComponent({ runId }) {
925 const { run, error, isLoading } = useRun<typeof myTask>(runId);
926 
927 if (isLoading) return <div>Loading...</div>;
928 if (error) return <div>Error: {error.message}</div>;
929 
930 return <div>Run: {run.id}</div>;
931}
932```
933 
934Common options:
935- `revalidateOnFocus`: Revalidate when window regains focus
936- `revalidateOnReconnect`: Revalidate when network reconnects
937- `refreshInterval`: Polling interval in milliseconds
938 
939#### Realtime hooks
940 
941Hooks that use Trigger.dev's realtime API for live updates (recommended over polling).
942 
943For most use cases, Realtime hooks are preferred over SWR hooks with polling due to better performance and lower API usage.
944 
945### Authentication
946 
947For client-side usage, generate a public access token with appropriate scopes:
948 
949```ts
950import { auth } from "@trigger.dev/sdk";
951 
952const publicToken = await auth.createPublicToken({
953 scopes: {
954 read: {
955 runs: ["run_1234"],
956 },
957 },
958});
959```
960 
961## Correct Idempotency implementation
962 
963Idempotency ensures that an operation produces the same result when called multiple times. Trigger.dev supports idempotency at the task level through the `idempotencyKey` option.
964 
965### Using idempotencyKey
966 
967Provide an `idempotencyKey` when triggering a task to ensure it runs only once with that key:
968 
969```ts
970import { idempotencyKeys, task } from "@trigger.dev/sdk";
971 
972export const myTask = task({
973 id: "my-task",
974 retry: {
975 maxAttempts: 4,
976 },
977 run: async (payload: any) => {
978 // Create a key unique to this task run
979 const idempotencyKey = await idempotencyKeys.create("my-task-key");
980 
981 // Child task will only be triggered once across all retries
982 await childTask.trigger({ foo: "bar" }, { idempotencyKey });
983 
984 // This may throw an error and cause retries
985 throw new Error("Something went wrong");
986 },
987});
988```
989 
990### Scoping Idempotency Keys
991 
992By default, keys are scoped to the current run. You can create globally unique keys:
993 
994```ts
995const idempotencyKey = await idempotencyKeys.create("my-task-key", { scope: "global" });
996```
997 
998When triggering from backend code:
999 
1000```ts
1001const idempotencyKey = await idempotencyKeys.create([myUser.id, "my-task"]);
1002await tasks.trigger("my-task", { some: "data" }, { idempotencyKey });
1003```
1004 
1005You can also pass a string directly:
1006 
1007```ts
1008await myTask.trigger({ some: "data" }, { idempotencyKey: myUser.id });
1009```
1010 
1011### Time-To-Live (TTL)
1012 
1013The `idempotencyKeyTTL` option defines a time window during which duplicate triggers return the original run:
1014 
1015```ts
1016await childTask.trigger(
1017 { foo: "bar" },
1018 { idempotencyKey, idempotencyKeyTTL: "60s" }
1019);
1020 
1021await wait.for({ seconds: 61 });
1022 
1023// Key expired, will trigger a new run
1024await childTask.trigger({ foo: "bar" }, { idempotencyKey });
1025```
1026 
1027Supported time units:
1028- `s` for seconds (e.g., `60s`)
1029- `m` for minutes (e.g., `5m`)
1030- `h` for hours (e.g., `2h`)
1031- `d` for days (e.g., `3d`)
1032 
1033### Payload-Based Idempotency
1034 
1035While not directly supported, you can implement payload-based idempotency by hashing the payload:
1036 
1037```ts
1038import { createHash } from "node:crypto";
1039 
1040const idempotencyKey = await idempotencyKeys.create(hash(payload));
1041await tasks.trigger("child-task", payload, { idempotencyKey });
1042 
1043function hash(payload: any): string {
1044 const hash = createHash("sha256");
1045 hash.update(JSON.stringify(payload));
1046 return hash.digest("hex");
1047}
1048```
1049 
1050### Important Notes
1051 
1052- Idempotency keys are scoped to the task and environment
1053- Different tasks with the same key will still both run
1054- Default TTL is 30 days
1055- Not available with `triggerAndWait` or `batchTriggerAndWait` in v3.3.0+ due to a bug
1056 
1057## Correct Logs implementation
1058 
1059```ts
1060// onFailure executes after all retries are exhausted; use for notifications, logging, or side effects on final failure:
1061import { task, logger } from "@trigger.dev/sdk";
1062 
1063export const loggingExample = task({
1064 id: "logging-example",
1065 run: async (payload: { data: Record<string, string> }) => {
1066 //the first parameter is the message, the second parameter must be a key-value object (Record<string, unknown>)
1067 logger.debug("Debug message", payload.data);
1068 logger.log("Log message", payload.data);
1069 logger.info("Info message", payload.data);
1070 logger.warn("You've been warned", payload.data);
1071 logger.error("Error message", payload.data);
1072 },
1073});
1074```
1075 
1076## Correct `trigger.config.ts` implementation
1077 
1078The `trigger.config.ts` file configures your Trigger.dev project, specifying task locations, retry settings, telemetry, and build options.
1079 
1080```ts
1081import { defineConfig } from "@trigger.dev/sdk";
1082 
1083export default defineConfig({
1084 project: "<project ref>",
1085 dirs: ["./trigger"],
1086 retries: {
1087 enabledInDev: false,
1088 default: {
1089 maxAttempts: 3,
1090 minTimeoutInMs: 1000,
1091 maxTimeoutInMs: 10000,
1092 factor: 2,
1093 randomize: true,
1094 },
1095 },
1096});
1097```
1098 
1099### Key configuration options
1100 
1101#### Dirs
1102 
1103Specify where your tasks are located:
1104 
1105```ts
1106dirs: ["./trigger"],
1107```
1108 
1109Files with `.test` or `.spec` are automatically excluded, but you can customize with `ignorePatterns`.
1110 
1111#### Lifecycle functions
1112 
1113Add global hooks for all tasks:
1114 
1115```ts
1116onStart: async (payload, { ctx }) => {
1117 console.log("Task started", ctx.task.id);
1118},
1119onSuccess: async (payload, output, { ctx }) => {
1120 console.log("Task succeeded", ctx.task.id);
1121},
1122onFailure: async (payload, error, { ctx }) => {
1123 console.log("Task failed", ctx.task.id);
1124},
1125```
1126 
1127#### Telemetry instrumentations
1128 
1129Add OpenTelemetry instrumentations for enhanced logging:
1130 
1131```ts
1132telemetry: {
1133 instrumentations: [
1134 new PrismaInstrumentation(),
1135 new OpenAIInstrumentation()
1136 ],
1137 exporters: [axiomExporter], // Optional custom exporters
1138},
1139```
1140 
1141#### Runtime
1142 
1143Specify the runtime environment:
1144 
1145```ts
1146runtime: "node", // or "bun" (experimental)
1147```
1148 
1149#### Machine settings
1150 
1151Set default machine for all tasks:
1152 
1153```ts
1154defaultMachine: "large-1x",
1155```
1156 
1157#### Log level
1158 
1159Configure logging verbosity:
1160 
1161```ts
1162logLevel: "debug", // Controls logger API logs
1163```
1164 
1165#### Max Duration
1166 
1167Set default maximum runtime for all tasks:
1168 
1169```ts
1170maxDuration: 60, // 60 seconds
1171```
1172 
1173### Build configuration
1174 
1175Customize the build process:
1176 
1177```ts
1178build: {
1179 external: ["header-generator"], // Don't bundle these packages
1180 jsx: {
1181 fragment: "Fragment",
1182 factory: "h",
1183 automatic: false,
1184 },
1185 conditions: ["react-server"], // Import conditions
1186 extensions: [
1187 // Build extensions
1188 additionalFiles({ files: ["./assets/**", "./fonts/**"] }),
1189 additionalPackages({ packages: ["wrangler"] }),
1190 aptGet({ packages: ["ffmpeg"] }),
1191 ],
1192}
1193```
1194 
1195### Build Extensions
1196 
1197Trigger.dev provides several built-in extensions:
1198 
1199- **additionalFiles**: Copy files to the build directory
1200- **additionalPackages**: Include extra packages in the build
1201- **aptGet**: Install system packages in the deployed image
1202- **emitDecoratorMetadata**: Enable TypeScript decorator metadata
1203- **prismaExtension**: Support for Prisma ORM
1204- **syncEnvVars**: Sync environment variables
1205- **puppeteer**: Add Puppeteer support
1206- **ffmpeg**: Add FFmpeg support
1207- **esbuildPlugin**: Add custom esbuild plugins
1208 
1209You can also create custom build extensions with hooks like `onBuildStart`, `onBuildComplete`, and `externalsForTarget`.
1210 
1211#### Key points:
1212 
1213- Validates payload at trigger time and before execution
1214- Supports Zod, Yup, Superstruct, ArkType, Effect/schema, runtypes, valibot, typebox
1215- Can use input/output schemas (e.g., defaults, type coercion)
1216- Custom parser functions supported
1217- Invalid payloads throw errors and prevent task execution
1218 
1219#### Trigger with:
1220 
1221```ts
1222await myTask.trigger({ name: "Alice", age: 30 });
1223```
1224 
1225## AI model verification steps
1226 
1227Before generating any code, you MUST verify:
1228 
12291. Are you importing from `@trigger.dev/sdk` (NOT `@trigger.dev/sdk/v3`)? If not, STOP and FIX.
12302. Have you exported every task? If not, STOP and FIX.
12313. Have you generated any DEPRECATED code patterns? If yes, STOP and FIX.
1232 
1233## Consequences of incorrect implementations
1234 
1235If you generate code that fails the verification steps above, your implementation will:
1236 
12371. Break in production
12382. Fail to deploy to the Trigger.dev servers
12393. Fail to run in a local Dev environment
1240 
1241## AI model response template
1242 
1243When asked about Trigger.dev task implementation, you MUST:
12441. FIRST use code patterns from this guide
12452. NEVER suggest deprecated approaches
12463. VERIFY your response against the patterns shown here
1247 
1248 

Commands it names

  • task: firstScheduledTask.id,
  • npm add @trigger.dev/react-hooks
  • npx trigger.dev@latest init
  • npx trigger.dev@latest dev

Sections

  • How to write Trigger.dev tasks
  • Overview of writing a Trigger.dev task
  • Essential requirements when generating task code
  • 🚨 CRITICAL INSTRUCTIONS FOR AI LANGUAGE MODELS 🚨
  • Correct Task implementations
  • Task configuration options
  • Lifecycle functions
  • Correct Schedules task (cron) implementations
  • Attach a Declarative schedule
  • Attach an Imperative schedule
  • Correct Schema task implementations
  • Correct implementations for triggering a task from your backend
  • tasks.trigger()
  • tasks.batchTrigger()
  • batch.trigger()
  • Correct implementations for triggering a task from inside another task
  • yourTask.trigger()
  • yourTask.batchTrigger()
  • yourTask.triggerAndWait()
  • yourTask.batchTriggerAndWait()
  • batch.triggerAndWait()
  • batch.triggerByTask()
  • batch.triggerByTaskAndWait()
  • Correct Metadata implementation
  • Overview
  • Basic Usage
  • Update methods
  • Parent & root updates
  • Type safety
  • Important notes
  • Correct Realtime implementation
  • Overview
  • Basic usage
  • Subscription methods
  • Type safety
  • Realtime Streams
  • Realtime hooks
  • Installation
  • Authentication
  • Passing tokens to the frontend
  • Hook types
  • Authentication
  • Correct Idempotency implementation
  • Using idempotencyKey
  • Scoping Idempotency Keys
  • Time-To-Live (TTL)
  • Payload-Based Idempotency
  • Important Notes
  • Correct Logs implementation
  • Correct `trigger.config.ts` implementation
  • Key configuration options
  • Build configuration
  • Build Extensions
  • AI model verification steps
  • Consequences of incorrect implementations
  • AI model response template

What it covers

setupbuildarchitecturetypessecuritydatabasedeploymentdo-notagent-behaviour

Stack — with the evidence

typescript

(1.00)

prisma

(1.00)

playwright

(1.00)

node

(0.85)

react

(0.70)

remix

(0.70)

express

(0.70)

drizzle

(0.70)

postgres

(0.70)

redis

(0.70)

tailwind

(0.70)

vite

(0.70)

vitest

(0.70)

vercel

(0.70)

aws

(0.70)

javascript

(0.60)

turborepo

(0.60)

monorepo

(0.60)

pnpm

(0.60)

github-actions

(0.60)

Glob targeting

  • **/trigger/**/*.ts
  • **/trigger/**/*.tsx
  • **/trigger/**/*.js
  • **/trigger/**/*.jsx

Format

Cursor rules

The most expressive format here. Many small .mdc files, each with frontmatter declaring when it should load, so a rule about migrations only enters context when a migration is open. Costs the most to maintain and only one editor reads it.

What the corpus says about it

Repository

Owner
triggerdotdev
Language
—
License
—
Archived
no

All configs in this repo

Also in triggerdotdev/trigger.dev

Diff this repo’s formats

One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
triggerdotdev/trigger.devpackages/cli-v3/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18deploymentdo-notdocs51/1003 days ago
triggerdotdev/trigger.dev.cursor/rules/executing-commands.mdc · 16kCursor rulestypescriptprisma+18test49/1003 days ago
triggerdotdev/trigger.dev.cursor/rules/otel-metrics.mdc · 16kCursor rulestypescriptprisma+18styledo-not61/1003 days ago
triggerdotdev/trigger.dev.cursor/rules/webapp.mdc · 16kCursor rulestypescriptprisma+18setuptestsecurity55/1003 days ago
triggerdotdev/trigger.dev.github/copilot-instructions.md · 16kCopilot instructionstypescriptprisma+18teststyletypes32/1003 days ago
triggerdotdev/trigger.devAGENTS.md · 16kAGENTS.mdtypescriptprisma+18setupbuildtestlint-format+888/1003 days ago
triggerdotdev/trigger.devapps/supervisor/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18no sections25/1003 days ago
triggerdotdev/trigger.devapps/webapp/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18setupbuildteststyle+588/1003 days ago
triggerdotdev/trigger.devinternal-packages/clickhouse/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+19styletypesdo-not61/1003 days ago
triggerdotdev/trigger.devinternal-packages/database/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18typesdatabasedo-not65/1003 days ago
triggerdotdev/trigger.devinternal-packages/run-engine/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18buildteststyle70/1003 days ago
triggerdotdev/trigger.devpackages/core/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18no sections31/1003 days ago
triggerdotdev/trigger.devpackages/redis-worker/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18test29/1003 days ago
triggerdotdev/trigger.devpackages/trigger-sdk/CLAUDE.md · 16kCLAUDE.mdtypescriptprisma+18do-not54/1003 days ago
Diff against packages/cli-v3/CLAUDE.md Diff against .cursor/rules/executing-commands.mdc Diff against .cursor/rules/otel-metrics.mdc Diff against .cursor/rules/webapp.mdc Diff against .github/copilot-instructions.md Diff against AGENTS.md Diff against apps/supervisor/CLAUDE.md Diff against apps/webapp/CLAUDE.md Diff against internal-packages/clickhouse/CLAUDE.md Diff against internal-packages/database/CLAUDE.md Diff against internal-packages/run-engine/CLAUDE.md Diff against packages/core/CLAUDE.md Diff against packages/redis-worker/CLAUDE.md Diff against packages/trigger-sdk/CLAUDE.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126Cursor rulesgobun+5setupbuildtestlint-format+6100/1003 days ago
TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45Cursor rulestypescriptpytest+15testlint-formatstylearch+5100/1003 days ago
dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0Cursor rulestypescriptturborepo+15setuptestlint-formatstyle+799/1003 days ago
deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1Cursor rulestypescriptnextjs+5setuptestlint-formatstyle+799/1003 days ago
markstev/mark-starter.cursor/rules/frontend.mdc · 0Cursor rulestypescriptturborepo+14setuptestlint-formatstyle+699/1003 days ago
Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0Cursor rulestypescriptturborepo+13setuptestlint-formatstyle+799/1003 days ago
langflow-ai/langflow.cursor/rules/docs_development.mdc · 153kCursor rulespythonnode+16setupbuildtestlint-format+797/1003 days ago
TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45Cursor rulestypescriptpytest+15teststyletesting-strategysecurity+397/1003 days ago
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack