@trigger.dev/sdk 4.5.0-rc.5 → 4.5.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/dist/commonjs/v3/ai.d.ts +178 -5
  2. package/dist/commonjs/v3/ai.js +603 -119
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.js +3 -0
  5. package/dist/commonjs/v3/chat-client.js.map +1 -1
  6. package/dist/commonjs/v3/chat-react.js +10 -7
  7. package/dist/commonjs/v3/chat-react.js.map +1 -1
  8. package/dist/commonjs/v3/chat-server.d.ts +8 -0
  9. package/dist/commonjs/v3/chat-server.js +32 -10
  10. package/dist/commonjs/v3/chat-server.js.map +1 -1
  11. package/dist/commonjs/v3/chat-server.test.js +51 -0
  12. package/dist/commonjs/v3/chat-server.test.js.map +1 -1
  13. package/dist/commonjs/v3/chat.js +34 -6
  14. package/dist/commonjs/v3/chat.js.map +1 -1
  15. package/dist/commonjs/v3/chat.test.js +53 -0
  16. package/dist/commonjs/v3/chat.test.js.map +1 -1
  17. package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
  18. package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
  19. package/dist/commonjs/v3/sessions.d.ts +11 -6
  20. package/dist/commonjs/v3/sessions.js +10 -5
  21. package/dist/commonjs/v3/sessions.js.map +1 -1
  22. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +6 -0
  23. package/dist/commonjs/v3/test/mock-chat-agent.js +1 -0
  24. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
  25. package/dist/commonjs/version.js +1 -1
  26. package/dist/esm/v3/ai.d.ts +178 -5
  27. package/dist/esm/v3/ai.js +603 -120
  28. package/dist/esm/v3/ai.js.map +1 -1
  29. package/dist/esm/v3/chat-client.js +3 -0
  30. package/dist/esm/v3/chat-client.js.map +1 -1
  31. package/dist/esm/v3/chat-react.js +10 -7
  32. package/dist/esm/v3/chat-react.js.map +1 -1
  33. package/dist/esm/v3/chat-server.d.ts +8 -0
  34. package/dist/esm/v3/chat-server.js +32 -10
  35. package/dist/esm/v3/chat-server.js.map +1 -1
  36. package/dist/esm/v3/chat-server.test.js +51 -0
  37. package/dist/esm/v3/chat-server.test.js.map +1 -1
  38. package/dist/esm/v3/chat.js +34 -6
  39. package/dist/esm/v3/chat.js.map +1 -1
  40. package/dist/esm/v3/chat.test.js +53 -0
  41. package/dist/esm/v3/chat.test.js.map +1 -1
  42. package/dist/esm/v3/createStartSessionAction.test.js +30 -0
  43. package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
  44. package/dist/esm/v3/sessions.d.ts +11 -6
  45. package/dist/esm/v3/sessions.js +10 -5
  46. package/dist/esm/v3/sessions.js.map +1 -1
  47. package/dist/esm/v3/test/mock-chat-agent.d.ts +6 -0
  48. package/dist/esm/v3/test/mock-chat-agent.js +1 -0
  49. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
  50. package/dist/esm/version.js +1 -1
  51. package/docs/ai/prompts.mdx +430 -0
  52. package/docs/ai-chat/actions.mdx +115 -0
  53. package/docs/ai-chat/anatomy.mdx +71 -0
  54. package/docs/ai-chat/backend.mdx +817 -0
  55. package/docs/ai-chat/background-injection.mdx +221 -0
  56. package/docs/ai-chat/changelog.mdx +850 -0
  57. package/docs/ai-chat/chat-local.mdx +174 -0
  58. package/docs/ai-chat/client-protocol.mdx +1081 -0
  59. package/docs/ai-chat/compaction.mdx +411 -0
  60. package/docs/ai-chat/custom-agents.mdx +364 -0
  61. package/docs/ai-chat/error-handling.mdx +415 -0
  62. package/docs/ai-chat/fast-starts.mdx +672 -0
  63. package/docs/ai-chat/frontend.mdx +580 -0
  64. package/docs/ai-chat/how-it-works.mdx +230 -0
  65. package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
  66. package/docs/ai-chat/mcp.mdx +101 -0
  67. package/docs/ai-chat/overview.mdx +90 -0
  68. package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
  69. package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
  70. package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
  71. package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
  72. package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
  73. package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
  74. package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
  75. package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
  76. package/docs/ai-chat/patterns/skills.mdx +221 -0
  77. package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
  78. package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
  79. package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
  80. package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
  81. package/docs/ai-chat/pending-messages.mdx +343 -0
  82. package/docs/ai-chat/prompt-caching.mdx +206 -0
  83. package/docs/ai-chat/quick-start.mdx +161 -0
  84. package/docs/ai-chat/reference.mdx +909 -0
  85. package/docs/ai-chat/server-chat.mdx +263 -0
  86. package/docs/ai-chat/sessions.mdx +333 -0
  87. package/docs/ai-chat/testing.mdx +682 -0
  88. package/docs/ai-chat/tools.mdx +191 -0
  89. package/docs/ai-chat/types.mdx +242 -0
  90. package/docs/ai-chat/upgrade-guide.mdx +515 -0
  91. package/docs/apikeys.mdx +54 -0
  92. package/docs/building-with-ai.mdx +261 -0
  93. package/docs/bulk-actions.mdx +49 -0
  94. package/docs/changelog.mdx +6 -0
  95. package/docs/cli-deploy-commands.mdx +9 -0
  96. package/docs/cli-dev-commands.mdx +9 -0
  97. package/docs/cli-dev.mdx +8 -0
  98. package/docs/cli-init-commands.mdx +58 -0
  99. package/docs/cli-introduction.mdx +25 -0
  100. package/docs/cli-list-profiles-commands.mdx +42 -0
  101. package/docs/cli-login-commands.mdx +33 -0
  102. package/docs/cli-logout-commands.mdx +33 -0
  103. package/docs/cli-preview-archive.mdx +59 -0
  104. package/docs/cli-promote-commands.mdx +9 -0
  105. package/docs/cli-switch.mdx +43 -0
  106. package/docs/cli-update-commands.mdx +42 -0
  107. package/docs/cli-whoami-commands.mdx +33 -0
  108. package/docs/community.mdx +6 -0
  109. package/docs/config/config-file.mdx +602 -0
  110. package/docs/config/extensions/additionalFiles.mdx +38 -0
  111. package/docs/config/extensions/additionalPackages.mdx +40 -0
  112. package/docs/config/extensions/aptGet.mdx +34 -0
  113. package/docs/config/extensions/audioWaveform.mdx +20 -0
  114. package/docs/config/extensions/custom.mdx +380 -0
  115. package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
  116. package/docs/config/extensions/esbuildPlugin.mdx +31 -0
  117. package/docs/config/extensions/ffmpeg.mdx +45 -0
  118. package/docs/config/extensions/lightpanda.mdx +56 -0
  119. package/docs/config/extensions/overview.mdx +67 -0
  120. package/docs/config/extensions/playwright.mdx +195 -0
  121. package/docs/config/extensions/prismaExtension.mdx +1014 -0
  122. package/docs/config/extensions/puppeteer.mdx +30 -0
  123. package/docs/config/extensions/pythonExtension.mdx +182 -0
  124. package/docs/config/extensions/syncEnvVars.mdx +291 -0
  125. package/docs/context.mdx +235 -0
  126. package/docs/database-connections.mdx +213 -0
  127. package/docs/deploy-environment-variables.mdx +435 -0
  128. package/docs/deployment/atomic-deployment.mdx +172 -0
  129. package/docs/deployment/overview.mdx +257 -0
  130. package/docs/deployment/preview-branches.mdx +224 -0
  131. package/docs/errors-retrying.mdx +379 -0
  132. package/docs/github-actions.mdx +222 -0
  133. package/docs/github-integration.mdx +136 -0
  134. package/docs/github-repo.mdx +8 -0
  135. package/docs/help-email.mdx +6 -0
  136. package/docs/help-slack.mdx +11 -0
  137. package/docs/hidden-tasks.mdx +56 -0
  138. package/docs/how-it-works.mdx +454 -0
  139. package/docs/how-to-reduce-your-spend.mdx +217 -0
  140. package/docs/idempotency.mdx +504 -0
  141. package/docs/introduction.mdx +223 -0
  142. package/docs/limits.mdx +241 -0
  143. package/docs/logging.mdx +195 -0
  144. package/docs/machines.mdx +952 -0
  145. package/docs/manual-setup.mdx +632 -0
  146. package/docs/mcp-agent-rules.mdx +41 -0
  147. package/docs/mcp-introduction.mdx +385 -0
  148. package/docs/mcp-tools.mdx +273 -0
  149. package/docs/migrating-from-v3.mdx +334 -0
  150. package/docs/observability/dashboards.mdx +102 -0
  151. package/docs/observability/query.mdx +585 -0
  152. package/docs/open-source-contributing.mdx +16 -0
  153. package/docs/open-source-self-hosting.mdx +541 -0
  154. package/docs/private-networking/aws-console-setup.mdx +304 -0
  155. package/docs/private-networking/overview.mdx +144 -0
  156. package/docs/private-networking/troubleshooting.mdx +78 -0
  157. package/docs/queue-concurrency.mdx +354 -0
  158. package/docs/quick-start.mdx +97 -0
  159. package/docs/realtime/auth.mdx +208 -0
  160. package/docs/realtime/backend/overview.mdx +45 -0
  161. package/docs/realtime/backend/streams.mdx +418 -0
  162. package/docs/realtime/backend/subscribe.mdx +225 -0
  163. package/docs/realtime/how-it-works.mdx +94 -0
  164. package/docs/realtime/overview.mdx +63 -0
  165. package/docs/realtime/react-hooks/overview.mdx +73 -0
  166. package/docs/realtime/react-hooks/streams.mdx +449 -0
  167. package/docs/realtime/react-hooks/subscribe.mdx +674 -0
  168. package/docs/realtime/react-hooks/swr.mdx +87 -0
  169. package/docs/realtime/react-hooks/triggering.mdx +194 -0
  170. package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
  171. package/docs/realtime/run-object.mdx +174 -0
  172. package/docs/replaying.mdx +72 -0
  173. package/docs/request-feature.mdx +6 -0
  174. package/docs/roadmap.mdx +6 -0
  175. package/docs/run-tests.mdx +20 -0
  176. package/docs/run-usage.mdx +113 -0
  177. package/docs/runs/heartbeats.mdx +38 -0
  178. package/docs/runs/max-duration.mdx +139 -0
  179. package/docs/runs/metadata.mdx +734 -0
  180. package/docs/runs/priority.mdx +31 -0
  181. package/docs/runs.mdx +396 -0
  182. package/docs/self-hosting/docker.mdx +458 -0
  183. package/docs/self-hosting/env/supervisor.mdx +74 -0
  184. package/docs/self-hosting/env/webapp.mdx +276 -0
  185. package/docs/self-hosting/kubernetes.mdx +601 -0
  186. package/docs/self-hosting/overview.mdx +108 -0
  187. package/docs/skills.mdx +85 -0
  188. package/docs/tags.mdx +120 -0
  189. package/docs/tasks/overview.mdx +697 -0
  190. package/docs/tasks/scheduled.mdx +382 -0
  191. package/docs/tasks/schemaTask.mdx +413 -0
  192. package/docs/tasks/streams.mdx +884 -0
  193. package/docs/triggering.mdx +1320 -0
  194. package/docs/troubleshooting-alerts.mdx +385 -0
  195. package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
  196. package/docs/troubleshooting-github-issues.mdx +6 -0
  197. package/docs/troubleshooting-uptime-status.mdx +6 -0
  198. package/docs/troubleshooting.mdx +398 -0
  199. package/docs/upgrading-packages.mdx +80 -0
  200. package/docs/vercel-integration.mdx +207 -0
  201. package/docs/versioning.mdx +56 -0
  202. package/docs/video-walkthrough.mdx +23 -0
  203. package/docs/wait-for-token.mdx +540 -0
  204. package/docs/wait-for.mdx +42 -0
  205. package/docs/wait-until.mdx +53 -0
  206. package/docs/wait.mdx +18 -0
  207. package/docs/writing-tasks-introduction.mdx +33 -0
  208. package/package.json +10 -6
  209. package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
  210. package/skills/trigger-authoring-tasks/SKILL.md +254 -0
  211. package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
  212. package/skills/trigger-cost-savings/SKILL.md +116 -0
  213. package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
@@ -0,0 +1,1320 @@
1
+ ---
2
+ title: "Triggering"
3
+ description: "Tasks need to be triggered in order to run."
4
+ ---
5
+
6
+ ## Trigger functions
7
+
8
+ Trigger tasks **from your backend**:
9
+
10
+ | Function | What it does | |
11
+ | :--------------------- | :----------------------------------------------------------------------------------------------- | --------------------------- |
12
+ | `tasks.trigger()` | Triggers a task and returns a handle you can use to fetch and manage the run. | [Docs](#tasks-trigger) |
13
+ | `tasks.batchTrigger()` | Triggers a single task in a batch and returns a handle you can use to fetch and manage the runs. | [Docs](#tasks-batchtrigger) |
14
+ | `batch.trigger()` | Similar to `tasks.batchTrigger` but allows running multiple different tasks | [Docs](#batch-trigger) |
15
+
16
+ Trigger tasks **from inside a another task**:
17
+
18
+ | Function | What it does | |
19
+ | :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
20
+ | `yourTask.trigger()` | Triggers a task and gets a handle you can use to monitor and manage the run. It does not wait for the result. | [Docs](#yourtask-trigger) |
21
+ | `yourTask.batchTrigger()` | Triggers a task multiple times and gets a handle you can use to monitor and manage the runs. It does not wait for the results. | [Docs](#yourtask-batchtrigger) |
22
+ | `yourTask.triggerAndWait()` | Triggers a task and then waits until it's complete. You get the result data to continue with. | [Docs](#yourtask-triggerandwait) |
23
+ | `yourTask.batchTriggerAndWait()` | Triggers a task multiple times in parallel and then waits until they're all complete. You get the resulting data to continue with. | [Docs](#yourtask-batchtriggerandwait) |
24
+ | `batch.triggerAndWait()` | Similar to `batch.trigger` but will wait on the triggered tasks to finish and return the results. | [Docs](#batch-triggerandwait) |
25
+ | `batch.triggerByTask()` | Similar to `batch.trigger` but allows passing in task instances instead of task IDs. | [Docs](#batch-triggerbytask) |
26
+ | `batch.triggerByTaskAndWait()` | Similar to `batch.triggerbyTask` but will wait on the triggered tasks to finish and return the results. | [Docs](#batch-triggerbytaskandwait) |
27
+
28
+ ## Triggering from your backend
29
+
30
+ When you trigger a task from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable. If you're [using a preview branch](/deployment/preview-branches), you also need to set the `TRIGGER_PREVIEW_BRANCH` environment variable. You can find the value on the API keys page in the Trigger.dev dashboard. [More info on API keys](/apikeys).
31
+
32
+ If a single process needs to trigger across multiple projects, environments, or preview branches, use [`new TriggerClient({...})`](/management/multiple-clients) for each target instead of relying on the global env vars.
33
+
34
+ <Note>
35
+ **Which trigger pattern should I use?** If your triggering code can import the task definition (same codebase), use `yourTask.trigger()` for full type safety. Use `tasks.trigger()` with a type-only import when the task runs in a separate service or you need to avoid bundling task code into your app (common in Next.js). Both do the same thing at runtime.
36
+ </Note>
37
+
38
+ <Note>
39
+ If you are using Next.js Server Actions [you'll need to be careful with
40
+ bundling](/guides/frameworks/nextjs#triggering-your-task-in-next-js).
41
+ </Note>
42
+
43
+ ### tasks.trigger()
44
+
45
+ Triggers a single run of a task with the payload you pass in, and any options you specify, without needing to import the task.
46
+
47
+ <Note>
48
+ By using `tasks.trigger()`, you can pass in the task type as a generic argument, giving you full
49
+ type checking. Make sure you use a `type` import so that your task code is not imported into your
50
+ application.
51
+ </Note>
52
+
53
+ ```ts Your backend
54
+ import { tasks } from "@trigger.dev/sdk";
55
+ import type { emailSequence } from "~/trigger/emails";
56
+ // 👆 **type-only** import
57
+
58
+ //app/email/route.ts
59
+ export async function POST(request: Request) {
60
+ //get the JSON from the request
61
+ const data = await request.json();
62
+
63
+ // Pass the task type to `trigger()` as a generic argument, giving you full type checking
64
+ const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
65
+ to: data.email,
66
+ name: data.name,
67
+ });
68
+
69
+ //return a success response with the handle
70
+ return Response.json(handle);
71
+ }
72
+ ```
73
+
74
+ You can pass in options to the task using the third argument:
75
+
76
+ ```ts Your backend
77
+ import { tasks } from "@trigger.dev/sdk";
78
+ import type { emailSequence } from "~/trigger/emails";
79
+
80
+ //app/email/route.ts
81
+ export async function POST(request: Request) {
82
+ //get the JSON from the request
83
+ const data = await request.json();
84
+
85
+ // Pass the task type to `trigger()` as a generic argument, giving you full type checking
86
+ const handle = await tasks.trigger<typeof emailSequence>(
87
+ "email-sequence",
88
+ {
89
+ to: data.email,
90
+ name: data.name,
91
+ },
92
+ { delay: "1h" } // 👈 Pass in the options here
93
+ );
94
+
95
+ //return a success response with the handle
96
+ return Response.json(handle);
97
+ }
98
+ ```
99
+
100
+ ### tasks.batchTrigger()
101
+
102
+ Triggers multiple runs of a single task with the payloads you pass in, and any options you specify, without needing to import the task.
103
+
104
+ ```ts Your backend
105
+ import { tasks } from "@trigger.dev/sdk";
106
+ import type { emailSequence } from "~/trigger/emails";
107
+ // 👆 **type-only** import
108
+
109
+ //app/email/route.ts
110
+ export async function POST(request: Request) {
111
+ //get the JSON from the request
112
+ const data = await request.json();
113
+
114
+ // Pass the task type to `batchTrigger()` as a generic argument, giving you full type checking
115
+ const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
116
+ "email-sequence",
117
+ data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
118
+ );
119
+
120
+ //return a success response with the handle
121
+ return Response.json(batchHandle);
122
+ }
123
+ ```
124
+
125
+ You can pass in options to the `batchTrigger` function using the third argument:
126
+
127
+ ```ts Your backend
128
+ import { tasks } from "@trigger.dev/sdk";
129
+ import type { emailSequence } from "~/trigger/emails";
130
+
131
+ //app/email/route.ts
132
+ export async function POST(request: Request) {
133
+ //get the JSON from the request
134
+ const data = await request.json();
135
+
136
+ // Pass the task type to `batchTrigger()` as a generic argument, giving you full type checking
137
+ const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
138
+ "email-sequence",
139
+ data.users.map((u) => ({ payload: { to: u.email, name: u.name } })),
140
+ { idempotencyKey: "my-idempotency-key" } // 👈 Pass in the options here
141
+ );
142
+
143
+ //return a success response with the handle
144
+ return Response.json(batchHandle);
145
+ }
146
+ ```
147
+
148
+ You can also pass in options for each run in the batch:
149
+
150
+ ```ts Your backend
151
+ import { tasks } from "@trigger.dev/sdk";
152
+ import type { emailSequence } from "~/trigger/emails";
153
+
154
+ //app/email/route.ts
155
+ export async function POST(request: Request) {
156
+ //get the JSON from the request
157
+ const data = await request.json();
158
+
159
+ // Pass the task type to `batchTrigger()` as a generic argument, giving you full type checking
160
+ const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
161
+ "email-sequence",
162
+ data.users.map((u) => ({ payload: { to: u.email, name: u.name }, options: { delay: "1h" } })) // 👈 Pass in options to each item like so
163
+ );
164
+
165
+ //return a success response with the handle
166
+ return Response.json(batchHandle);
167
+ }
168
+ ```
169
+
170
+ ### batch.trigger()
171
+
172
+ Triggers multiple runs of different tasks with the payloads you pass in, and any options you specify. This is useful when you need to trigger multiple tasks at once.
173
+
174
+ ```ts Your backend
175
+ import { batch } from "@trigger.dev/sdk";
176
+ import type { myTask1, myTask2 } from "~/trigger/myTasks";
177
+
178
+ export async function POST(request: Request) {
179
+ //get the JSON from the request
180
+ const data = await request.json();
181
+
182
+ // Pass a union of the tasks to `trigger()` as a generic argument, giving you full type checking
183
+ const result = await batch.trigger<typeof myTask1 | typeof myTask2>([
184
+ // Because we're using a union, we can pass in multiple tasks by ID
185
+ { id: "my-task-1", payload: { some: data.some } },
186
+ { id: "my-task-2", payload: { other: data.other } },
187
+ ]);
188
+
189
+ //return a success response with the result
190
+ return Response.json(result);
191
+ }
192
+ ```
193
+
194
+ ## Triggering from inside another task
195
+
196
+ The following functions should only be used when running inside a task, for one of the following reasons:
197
+
198
+ - You need to **wait** for the result of the triggered task.
199
+ - You need to import the task instance. Importing a task instance from your backend code is not recommended, as it can pull in a lot of unnecessary code and dependencies.
200
+
201
+ ### yourTask.trigger()
202
+
203
+ Triggers a single run of a task with the payload you pass in, and any options you specify.
204
+
205
+ <Note>
206
+ If you need to call `trigger()` on a task in a loop, use
207
+ [`batchTrigger()`](#yourTask-batchtrigger) instead which will trigger up to 1,000 runs in a single
208
+ call with SDK 4.3.1+ (500 runs in prior versions).
209
+ </Note>
210
+
211
+ ```ts ./trigger/my-task.ts
212
+ import { runs } from "@trigger.dev/sdk";
213
+ import { myOtherTask } from "~/trigger/my-other-task";
214
+
215
+ export const myTask = task({
216
+ id: "my-task",
217
+ run: async (payload: string) => {
218
+ const handle = await myOtherTask.trigger({ foo: "some data" });
219
+
220
+ const run = await runs.retrieve(handle);
221
+ // Do something with the run
222
+ },
223
+ });
224
+ ```
225
+
226
+ To pass options to the triggered task, you can use the second argument:
227
+
228
+ ```ts ./trigger/my-task.ts
229
+ import { runs } from "@trigger.dev/sdk";
230
+ import { myOtherTask } from "~/trigger/my-other-task";
231
+
232
+ export const myTask = task({
233
+ id: "my-task",
234
+ run: async (payload: string) => {
235
+ const handle = await myOtherTask.trigger({ foo: "some data" }, { delay: "1h" });
236
+
237
+ const run = await runs.retrieve(handle);
238
+ // Do something with the run
239
+ },
240
+ });
241
+ ```
242
+
243
+ ### yourTask.batchTrigger()
244
+
245
+ Triggers multiple runs of a single task with the payloads you pass in, and any options you specify.
246
+
247
+ ```ts /trigger/my-task.ts
248
+ import { batch } from "@trigger.dev/sdk";
249
+ import { myOtherTask } from "~/trigger/my-other-task";
250
+
251
+ export const myTask = task({
252
+ id: "my-task",
253
+ run: async (payload: string) => {
254
+ const batchHandle = await myOtherTask.batchTrigger([{ payload: "some data" }]);
255
+
256
+ //...do other stuff
257
+ const batch = await batch.retrieve(batchHandle.id);
258
+ },
259
+ });
260
+ ```
261
+
262
+ If you need to pass options to `batchTrigger`, you can use the second argument:
263
+
264
+ ```ts /trigger/my-task.ts
265
+ import { batch } from "@trigger.dev/sdk";
266
+ import { myOtherTask } from "~/trigger/my-other-task";
267
+
268
+ export const myTask = task({
269
+ id: "my-task",
270
+ run: async (payload: string) => {
271
+ const batchHandle = await myOtherTask.batchTrigger([{ payload: "some data" }], {
272
+ idempotencyKey: "my-task-key",
273
+ });
274
+
275
+ //...do other stuff
276
+ const batch = await batch.retrieve(batchHandle.id);
277
+ },
278
+ });
279
+ ```
280
+
281
+ You can also pass in options for each run in the batch:
282
+
283
+ ```ts /trigger/my-task.ts
284
+ import { batch } from "@trigger.dev/sdk";
285
+ import { myOtherTask } from "~/trigger/my-other-task";
286
+
287
+ export const myTask = task({
288
+ id: "my-task",
289
+ run: async (payload: string) => {
290
+ const batchHandle = await myOtherTask.batchTrigger([
291
+ { payload: "some data", options: { delay: "1h" } },
292
+ ]);
293
+
294
+ //...do other stuff
295
+ const batch = await batch.retrieve(batchHandle.id);
296
+ },
297
+ });
298
+ ```
299
+
300
+ ### yourTask.triggerAndWait()
301
+
302
+ This is where it gets interesting. You can trigger a task and then wait for the result. This is useful when you need to call a different task and then use the result to continue with your task.
303
+
304
+ <Accordion title="Don't use this in parallel, e.g. with `Promise.all()`">
305
+ Instead, use `batchTriggerAndWait()` if you can, or a for loop if you can't.
306
+
307
+ To control concurrency using batch triggers, you can set `queue.concurrencyLimit` on the child task.
308
+
309
+ <CodeGroup>
310
+
311
+ ```ts /trigger/batch.ts
312
+ export const batchTask = task({
313
+ id: "batch-task",
314
+ run: async (payload: string) => {
315
+ const results = await childTask.batchTriggerAndWait([
316
+ { payload: "item1" },
317
+ { payload: "item2" },
318
+ ]);
319
+ console.log("Results", results);
320
+
321
+ //...do stuff with the results
322
+ },
323
+ });
324
+ ```
325
+
326
+ ```ts /trigger/loop.ts
327
+ export const loopTask = task({
328
+ id: "loop-task",
329
+ run: async (payload: string) => {
330
+ //this will be slower than the batch version
331
+ //as we have to resume the parent after each iteration
332
+ for (let i = 0; i < 2; i++) {
333
+ const result = await childTask.triggerAndWait(`item${i}`);
334
+ console.log("Result", result);
335
+
336
+ //...do stuff with the result
337
+ }
338
+ },
339
+ });
340
+ ```
341
+
342
+ </CodeGroup>
343
+
344
+ </Accordion>
345
+
346
+ ```ts /trigger/parent.ts
347
+ export const parentTask = task({
348
+ id: "parent-task",
349
+ run: async (payload: string) => {
350
+ const result = await childTask.triggerAndWait("some-data");
351
+ console.log("Result", result);
352
+
353
+ //...do stuff with the result
354
+ },
355
+ });
356
+ ```
357
+
358
+ The `result` object is a "Result" type that needs to be checked to see if the child task run was successful:
359
+
360
+ ```ts /trigger/parent.ts
361
+ export const parentTask = task({
362
+ id: "parent-task",
363
+ run: async (payload: string) => {
364
+ const result = await childTask.triggerAndWait("some-data");
365
+
366
+ if (result.ok) {
367
+ console.log("Result", result.output); // result.output is the typed return value of the child task
368
+ } else {
369
+ console.error("Error", result.error); // result.error is the error that caused the run to fail
370
+ }
371
+ },
372
+ });
373
+ ```
374
+
375
+ If instead you just want to get the output of the child task, and throw an error if the child task failed, you can use the `unwrap` method:
376
+
377
+ ```ts /trigger/parent.ts
378
+ export const parentTask = task({
379
+ id: "parent-task",
380
+ run: async (payload: string) => {
381
+ const output = await childTask.triggerAndWait("some-data").unwrap();
382
+ console.log("Output", output);
383
+ },
384
+ });
385
+ ```
386
+
387
+ You can also catch the error if the child task fails and get more information about the error:
388
+
389
+ ```ts /trigger/parent.ts
390
+ import { task, SubtaskUnwrapError } from "@trigger.dev/sdk";
391
+ export const parentTask = task({
392
+ id: "parent-task",
393
+ run: async (payload: string) => {
394
+ try {
395
+ const output = await childTask.triggerAndWait("some-data").unwrap();
396
+ console.log("Output", output);
397
+ } catch (error) {
398
+ if (error instanceof SubtaskUnwrapError) {
399
+ console.error("Error in fetch-post-task", {
400
+ runId: error.runId,
401
+ taskId: error.taskId,
402
+ cause: error.cause,
403
+ });
404
+ }
405
+ }
406
+ },
407
+ });
408
+ ```
409
+
410
+ <Warning>
411
+ This method should only be used inside a task. If you use it outside a task, it will throw an
412
+ error.
413
+ </Warning>
414
+
415
+ ### yourTask.batchTriggerAndWait()
416
+
417
+ You can batch trigger a task and wait for all the results. This is useful for the fan-out pattern, where you need to call a task multiple times and then wait for all the results to continue with your task.
418
+
419
+ <Accordion title="Don't use this in parallel, e.g. with `Promise.all()`">
420
+ Instead, pass in all items at once and set an appropriate `maxConcurrency`. Alternatively, use sequentially with a for loop.
421
+
422
+ To control concurrency, you can set `queue.concurrencyLimit` on the child task.
423
+
424
+ <CodeGroup>
425
+
426
+ ```ts /trigger/batch.ts
427
+ export const batchTask = task({
428
+ id: "batch-task",
429
+ run: async (payload: string) => {
430
+ const results = await childTask.batchTriggerAndWait([
431
+ { payload: "item1" },
432
+ { payload: "item2" },
433
+ ]);
434
+ console.log("Results", results);
435
+
436
+ //...do stuff with the results
437
+ },
438
+ });
439
+ ```
440
+
441
+ ```ts /trigger/loop.ts
442
+ export const loopTask = task({
443
+ id: "loop-task",
444
+ run: async (payload: string) => {
445
+ //this will be slower than a single batchTriggerAndWait()
446
+ //as we have to resume the parent after each iteration
447
+ for (let i = 0; i < 2; i++) {
448
+ const result = await childTask.batchTriggerAndWait([
449
+ { payload: `itemA${i}` },
450
+ { payload: `itemB${i}` },
451
+ ]);
452
+ console.log("Result", result);
453
+
454
+ //...do stuff with the result
455
+ }
456
+ },
457
+ });
458
+ ```
459
+
460
+ </CodeGroup>
461
+
462
+ </Accordion>
463
+
464
+ <Accordion title="How to handle run failures">
465
+
466
+ When using `batchTriggerAndWait`, you have full control over how to handle failures within the batch. The method returns an array of run results, allowing you to inspect each run's outcome individually and implement custom error handling.
467
+
468
+ Here's how you can manage run failures:
469
+
470
+ 1. **Inspect individual run results**: Each run in the returned array has an `ok` property indicating success or failure.
471
+
472
+ 2. **Access error information**: For failed runs, you can examine the `error` property to get details about the failure.
473
+
474
+ 3. **Choose your failure strategy**: You have two main options:
475
+
476
+ - **Fail the entire batch**: Throw an error if any run fails, causing the parent task to reattempt.
477
+ - **Continue despite failures**: Process the results without throwing an error, allowing the parent task to continue.
478
+
479
+ 4. **Implement custom logic**: You can create sophisticated handling based on the number of failures, types of errors, or other criteria.
480
+
481
+ Here's an example of how you might handle run failures:
482
+
483
+ <CodeGroup>
484
+
485
+ ```ts /trigger/batchTriggerAndWait.ts
486
+ const result = await batchChildTask.batchTriggerAndWait([
487
+ { payload: "item1" },
488
+ { payload: "item2" },
489
+ { payload: "item3" },
490
+ ]);
491
+
492
+ // Result will contain the finished runs.
493
+ // They're only finished if they have succeeded or failed.
494
+ // "Failed" means all attempts failed
495
+
496
+ for (const run of result.runs) {
497
+ // Check if the run succeeded
498
+ if (run.ok) {
499
+ logger.info("Batch task run succeeded", { output: run.output });
500
+ } else {
501
+ logger.error("Batch task run error", { error: run.error });
502
+
503
+ //You can choose if you want to throw an error and fail the entire run
504
+ throw new Error(`Fail the entire run because ${run.id} failed`);
505
+ }
506
+ }
507
+ ```
508
+
509
+ </CodeGroup>
510
+
511
+ </Accordion>
512
+
513
+ ```ts /trigger/nested.ts
514
+ export const batchParentTask = task({
515
+ id: "parent-task",
516
+ run: async (payload: string) => {
517
+ const results = await childTask.batchTriggerAndWait([
518
+ { payload: "item4" },
519
+ { payload: "item5" },
520
+ { payload: "item6" },
521
+ ]);
522
+ console.log("Results", results);
523
+
524
+ //...do stuff with the result
525
+ },
526
+ });
527
+ ```
528
+
529
+ <Warning>
530
+ This method should only be used inside a task. If you use it outside a task, it will throw an
531
+ error.
532
+ </Warning>
533
+
534
+ ### batch.triggerAndWait()
535
+
536
+ You can batch trigger multiple different tasks and wait for all the results:
537
+
538
+ ```ts /trigger/batch.ts
539
+ import { batch, task } from "@trigger.dev/sdk";
540
+
541
+ export const parentTask = task({
542
+ id: "parent-task",
543
+ run: async (payload: string) => {
544
+ // 👇 Pass a union of all the tasks you want to trigger
545
+ const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
546
+ { id: "child-task-1", payload: { foo: "World" } }, // 👈 The payload is typed correctly based on the task `id`
547
+ { id: "child-task-2", payload: { bar: 42 } }, // 👈 The payload is typed correctly based on the task `id`
548
+ ]);
549
+
550
+ for (const result of results.runs) {
551
+ if (result.ok) {
552
+ // 👇 Narrow the type of the result based on the taskIdentifier
553
+ switch (result.taskIdentifier) {
554
+ case "child-task-1":
555
+ console.log("Child task 1 output", result.output); // 👈 result.output is typed as a string
556
+ break;
557
+ case "child-task-2":
558
+ console.log("Child task 2 output", result.output); // 👈 result.output is typed as a number
559
+ break;
560
+ }
561
+ } else {
562
+ console.error("Error", result.error); // 👈 result.error is the error that caused the run to fail
563
+ }
564
+ }
565
+ },
566
+ });
567
+
568
+ export const childTask1 = task({
569
+ id: "child-task-1",
570
+ run: async (payload: { foo: string }) => {
571
+ return `Hello ${payload}`;
572
+ },
573
+ });
574
+
575
+ export const childTask2 = task({
576
+ id: "child-task-2",
577
+ run: async (payload: { bar: number }) => {
578
+ return bar + 1;
579
+ },
580
+ });
581
+ ```
582
+
583
+ ### batch.triggerByTask()
584
+
585
+ You can batch trigger multiple different tasks by passing in the task instances. This function is especially useful when you have a static set of tasks you want to trigger:
586
+
587
+ ```ts /trigger/batch.ts
588
+ import { batch, task, runs } from "@trigger.dev/sdk";
589
+
590
+ export const parentTask = task({
591
+ id: "parent-task",
592
+ run: async (payload: string) => {
593
+ const results = await batch.triggerByTask([
594
+ { task: childTask1, payload: { foo: "World" } }, // 👈 The payload is typed correctly based on the task instance
595
+ { task: childTask2, payload: { bar: 42 } }, // 👈 The payload is typed correctly based on the task instance
596
+ ]);
597
+
598
+ // 👇 results.runs is a tuple, allowing you to get type safety without needing to narrow
599
+ const run1 = await runs.retrieve(results.runs[0]); // 👈 run1 is typed as the output of childTask1
600
+ const run2 = await runs.retrieve(results.runs[1]); // 👈 run2 is typed as the output of childTask2
601
+ },
602
+ });
603
+
604
+ export const childTask1 = task({
605
+ id: "child-task-1",
606
+ run: async (payload: { foo: string }) => {
607
+ return `Hello ${payload}`;
608
+ },
609
+ });
610
+
611
+ export const childTask2 = task({
612
+ id: "child-task-2",
613
+ run: async (payload: { bar: number }) => {
614
+ return bar + 1;
615
+ },
616
+ });
617
+ ```
618
+
619
+ ### batch.triggerByTaskAndWait()
620
+
621
+ You can batch trigger multiple different tasks by passing in the task instances, and wait for all the results. This function is especially useful when you have a static set of tasks you want to trigger:
622
+
623
+ ```ts /trigger/batch.ts
624
+ import { batch, task, runs } from "@trigger.dev/sdk";
625
+
626
+ export const parentTask = task({
627
+ id: "parent-task",
628
+ run: async (payload: string) => {
629
+ const { runs } = await batch.triggerByTaskAndWait([
630
+ { task: childTask1, payload: { foo: "World" } }, // 👈 The payload is typed correctly based on the task instance
631
+ { task: childTask2, payload: { bar: 42 } }, // 👈 The payload is typed correctly based on the task instance
632
+ ]);
633
+
634
+ if (runs[0].ok) {
635
+ console.log("Child task 1 output", runs[0].output); // 👈 runs[0].output is typed as the output of childTask1
636
+ }
637
+
638
+ if (runs[1].ok) {
639
+ console.log("Child task 2 output", runs[1].output); // 👈 runs[1].output is typed as the output of childTask2
640
+ }
641
+
642
+ // 💭 A nice alternative syntax is to destructure the runs array:
643
+ const {
644
+ runs: [run1, run2],
645
+ } = await batch.triggerByTaskAndWait([
646
+ { task: childTask1, payload: { foo: "World" } }, // 👈 The payload is typed correctly based on the task instance
647
+ { task: childTask2, payload: { bar: 42 } }, // 👈 The payload is typed correctly based on the task instance
648
+ ]);
649
+
650
+ if (run1.ok) {
651
+ console.log("Child task 1 output", run1.output); // 👈 run1.output is typed as the output of childTask1
652
+ }
653
+
654
+ if (run2.ok) {
655
+ console.log("Child task 2 output", run2.output); // 👈 run2.output is typed as the output of childTask2
656
+ }
657
+ },
658
+ });
659
+
660
+ export const childTask1 = task({
661
+ id: "child-task-1",
662
+ run: async (payload: { foo: string }) => {
663
+ return `Hello ${payload}`;
664
+ },
665
+ });
666
+
667
+ export const childTask2 = task({
668
+ id: "child-task-2",
669
+ run: async (payload: { bar: number }) => {
670
+ return bar + 1;
671
+ },
672
+ });
673
+ ```
674
+
675
+ ## Triggering from your frontend
676
+
677
+ If you want to trigger a task directly from a frontend application, you can use our [React
678
+ hooks](/realtime/react-hooks/triggering).
679
+
680
+ ## Options
681
+
682
+ All of the above functions accept an options object:
683
+
684
+ ```ts
685
+ await myTask.trigger({ some: "data" }, { delay: "1h", ttl: "1h" });
686
+ await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" } }]);
687
+ ```
688
+
689
+ The following options are available:
690
+
691
+ ### `delay`
692
+
693
+ When you want to trigger a task now, but have it run at a later time, you can use the `delay` option:
694
+
695
+ ```ts
696
+ // Delay the task run by 1 hour
697
+ await myTask.trigger({ some: "data" }, { delay: "1h" });
698
+ // Delay the task run by 88 seconds
699
+ await myTask.trigger({ some: "data" }, { delay: "88s" });
700
+ // Delay the task run by 1 hour and 52 minutes and 18 seconds
701
+ await myTask.trigger({ some: "data" }, { delay: "1h52m18s" });
702
+ // Delay until a specific time
703
+ await myTask.trigger({ some: "data" }, { delay: "2024-12-01T00:00:00" });
704
+ // Delay using a Date object
705
+ await myTask.trigger({ some: "data" }, { delay: new Date(Date.now() + 1000 * 60 * 60) });
706
+ // Delay using a timezone
707
+ await myTask.trigger({ some: "data" }, { delay: new Date("2024-07-23T11:50:00+02:00") });
708
+ ```
709
+
710
+ Runs that are delayed and have not been enqueued yet will display in the dashboard with a "Delayed" status:
711
+
712
+ ![Delayed run in the dashboard](/images/delayed-runs.png)
713
+
714
+ <Note>
715
+ Delayed runs will be enqueued at the time specified, and will run as soon as possible after that
716
+ time, just as a normally triggered run would. They execute on the currently deployed version when
717
+ they start, not the version that was active when they were enqueued.
718
+ </Note>
719
+
720
+ You can cancel a delayed run using the `runs.cancel` SDK function:
721
+
722
+ ```ts
723
+ import { runs } from "@trigger.dev/sdk";
724
+
725
+ await runs.cancel("run_1234");
726
+ ```
727
+
728
+ You can also reschedule a delayed run using the `runs.reschedule` SDK function:
729
+
730
+ ```ts
731
+ import { runs } from "@trigger.dev/sdk";
732
+
733
+ // The delay option here takes the same format as the trigger delay option
734
+ await runs.reschedule("run_1234", { delay: "1h" });
735
+ ```
736
+
737
+ The `delay` option is also available when using `batchTrigger`:
738
+
739
+ ```ts
740
+ await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" } }]);
741
+ ```
742
+
743
+ <Note>
744
+ If your payload contains Date objects, pass them directly rather than manually stringifying with
745
+ `JSON.stringify()`. The SDK handles Date serialization automatically. If you need to stringify
746
+ manually, convert Dates to ISO strings first (e.g., `date.toISOString()`).
747
+ </Note>
748
+
749
+ ### `ttl`
750
+
751
+ You can set a TTL (time to live) when triggering a task, which will automatically expire the run if it hasn't started within the specified time. This is useful for ensuring that a run doesn't get stuck in the queue for too long.
752
+
753
+ <Note>
754
+ All runs in development have a default `ttl` of 10 minutes. You can disable this by setting the
755
+ `ttl` option.
756
+ </Note>
757
+
758
+ ```ts
759
+ import { myTask } from "./trigger/myTasks";
760
+
761
+ // Expire the run if it hasn't started within 1 hour
762
+ await myTask.trigger({ some: "data" }, { ttl: "1h" });
763
+
764
+ // If you specify a number, it will be treated as seconds
765
+ await myTask.trigger({ some: "data" }, { ttl: 3600 }); // 1 hour
766
+ ```
767
+
768
+ When a run is expired, it will be marked as "Expired" in the dashboard:
769
+
770
+ ![Expired runs in the dashboard](/images/expired-runs.png)
771
+
772
+ When you use both `delay` and `ttl`, the TTL will start counting down from the time the run is enqueued, not from the time the run is triggered.
773
+
774
+ So for example, when using the following code:
775
+
776
+ ```ts
777
+ await myTask.trigger({ some: "data" }, { delay: "10m", ttl: "1h" });
778
+ ```
779
+
780
+ The timeline would look like this:
781
+
782
+ 1. The run is created at 12:00:00
783
+ 2. The run is enqueued at 12:10:00
784
+ 3. The TTL starts counting down from 12:10:00
785
+ 4. If the run hasn't started by 13:10:00, it will be expired
786
+
787
+ For this reason, the `ttl` option only accepts durations and not absolute timestamps.
788
+
789
+ <Note>
790
+ On [Trigger.dev Cloud](https://trigger.dev), there is a maximum TTL of 14 days. If you don't specify a TTL in staging or production, runs automatically get a 14-day TTL. If you specify a TTL longer than 14 days, it is clamped to 14 days. See [Limits — Maximum run TTL](/limits#maximum-run-ttl) for details.
791
+ </Note>
792
+
793
+ ### `idempotencyKey`
794
+
795
+ You can provide an `idempotencyKey` to ensure that a task is only triggered once with the same key. This is useful if you are triggering a task within another task that might be retried:
796
+
797
+ ```typescript
798
+ import { idempotencyKeys, task } from "@trigger.dev/sdk";
799
+
800
+ export const myTask = task({
801
+ id: "my-task",
802
+ retry: {
803
+ maxAttempts: 4,
804
+ },
805
+ run: async (payload: any) => {
806
+ // By default, idempotency keys generated are unique to the run, to prevent retries from duplicating child tasks
807
+ const idempotencyKey = await idempotencyKeys.create("my-task-key");
808
+
809
+ // childTask will only be triggered once with the same idempotency key
810
+ await childTask.trigger(payload, { idempotencyKey });
811
+
812
+ // Do something else, that may throw an error and cause the task to be retried
813
+ },
814
+ });
815
+ ```
816
+
817
+ For more information, see our [Idempotency](/idempotency) documentation.
818
+
819
+ ### `idempotencyKeyTTL`
820
+
821
+ Idempotency keys automatically expire after 30 days, but you can set a custom TTL for an idempotency key when triggering a task:
822
+
823
+ ```typescript
824
+ import { idempotencyKeys, task } from "@trigger.dev/sdk";
825
+
826
+ export const myTask = task({
827
+ id: "my-task",
828
+ retry: {
829
+ maxAttempts: 4,
830
+ },
831
+ run: async (payload: any) => {
832
+ // By default, idempotency keys generated are unique to the run, to prevent retries from duplicating child tasks
833
+ const idempotencyKey = await idempotencyKeys.create("my-task-key");
834
+
835
+ // childTask will only be triggered once with the same idempotency key
836
+ await childTask.trigger(payload, { idempotencyKey, idempotencyKeyTTL: "1h" });
837
+
838
+ // Do something else, that may throw an error and cause the task to be retried
839
+ },
840
+ });
841
+ ```
842
+
843
+ For more information, see our [Idempotency](/idempotency) documentation.
844
+
845
+ ### `debounce`
846
+
847
+ You can debounce task triggers to consolidate multiple trigger calls into a single delayed run. When a run with the same debounce key already exists in the delayed state, subsequent triggers "push" the existing run's execution time later rather than creating new runs.
848
+
849
+ This is useful for scenarios like:
850
+
851
+ - Real-time document indexing where you want to wait for the user to finish typing
852
+ - Aggregating webhook events from the same source
853
+ - Rate limiting expensive operations while still processing the final request
854
+
855
+ ```ts
856
+ // First trigger creates a new run, delayed by 5 seconds
857
+ await myTask.trigger({ some: "data" }, { debounce: { key: "user-123", delay: "5s" } });
858
+
859
+ // If triggered again within 5 seconds, the existing run is pushed later
860
+ await myTask.trigger({ updated: "data" }, { debounce: { key: "user-123", delay: "5s" } });
861
+
862
+ // The run only executes after 5 seconds of no new triggers
863
+ // Note: The first payload is used (first trigger wins)
864
+ ```
865
+
866
+ <Note>
867
+ Debounce keys are scoped to the task identifier, so different tasks can use the same key without
868
+ conflicts.
869
+ </Note>
870
+
871
+ The `debounce` option accepts:
872
+
873
+ - `key` - A unique string to identify the debounce group (scoped to the task)
874
+ - `delay` - Duration string specifying how long to delay. Supported units: `s` (seconds), `m` (minutes), `h`/`hr` (hours), `d` (days), `w` (weeks). Minimum is 1 second. Examples: `"5s"`, `"1m"`, `"2h30m"`
875
+ - `mode` - Optional. Controls which trigger's data is used: `"leading"` (default) or `"trailing"`
876
+ - `maxDelay` - Optional. Maximum total time from the first trigger before the run must execute. Uses the same duration format as `delay`
877
+
878
+ **How it works:**
879
+
880
+ 1. First trigger with a debounce key creates a new delayed run
881
+ 2. Subsequent triggers with the same key (while the run is still delayed) push the execution time further
882
+ 3. Once no new triggers occur within the delay duration, the run executes
883
+ 4. After the run starts executing, a new trigger with the same key will create a new run
884
+
885
+ **Limiting total delay with `maxDelay`:**
886
+
887
+ By default, continuous triggers can delay execution indefinitely. The `maxDelay` option sets an upper bound on the total delay from the first trigger, ensuring the run eventually executes even with constant activity.
888
+
889
+ ```ts
890
+ await summarizeChat.trigger(
891
+ { conversationId: "123" },
892
+ {
893
+ debounce: {
894
+ key: "conversation-123",
895
+ delay: "10s", // Wait 10s after each message
896
+ maxDelay: "5m", // But always run within 5 minutes of first trigger
897
+ },
898
+ }
899
+ );
900
+ ```
901
+
902
+ This is useful for scenarios like:
903
+
904
+ - Summarizing AI chat threads that need periodic updates even during active conversations
905
+ - Syncing data that should happen regularly despite continuous changes
906
+ - Any case where you want debouncing but also guarantee timely execution
907
+
908
+ **Timeline example with `maxDelay`:**
909
+
910
+ Consider `delay: "5s"` and `maxDelay: "30s"` with triggers arriving every 2 seconds:
911
+
912
+ | Time | Event | Result |
913
+ | :--- | :--- | :--- |
914
+ | 0s | Trigger 1 | Run A created, scheduled for 5s |
915
+ | 2s | Trigger 2 | Run A rescheduled to 7s |
916
+ | 4s | Trigger 3 | Run A rescheduled to 9s |
917
+ | ... | ... | ... |
918
+ | 26s | Trigger 14 | Run A rescheduled to 31s |
919
+ | 28s | Trigger 15 | Would reschedule to 33s, but exceeds maxDelay (30s). Run A executes, Run B created |
920
+ | 30s | Trigger 16 | Run B rescheduled to 35s |
921
+
922
+ Without `maxDelay`, continuous triggers would prevent the run from ever executing. With `maxDelay: "30s"`, execution is guaranteed within 30 seconds of the first trigger.
923
+
924
+ <Note>
925
+ The `maxDelay` value is evaluated from each trigger call, not stored with the original run. This
926
+ means if you pass different `maxDelay` values for the same debounce key, each trigger uses its own
927
+ `maxDelay` to check against the original run's creation time. For consistent behavior, use the
928
+ same `maxDelay` value for all triggers with the same debounce key.
929
+ </Note>
930
+
931
+ **Leading vs Trailing mode:**
932
+
933
+ By default, debounce uses **leading mode** - the run executes with data from the **first** trigger.
934
+
935
+ With **trailing mode**, each subsequent trigger updates the run's data (payload, metadata, tags, maxAttempts, maxDuration, and machine), so the run executes with data from the **last** trigger:
936
+
937
+ ```ts
938
+ // Leading mode (default): runs with first payload
939
+ await myTask.trigger({ count: 1 }, { debounce: { key: "user-123", delay: "5s" } });
940
+ await myTask.trigger({ count: 2 }, { debounce: { key: "user-123", delay: "5s" } });
941
+ // After 5 seconds, runs with { count: 1 }
942
+
943
+ // Trailing mode: runs with last payload
944
+ await myTask.trigger(
945
+ { count: 1 },
946
+ { debounce: { key: "user-123", delay: "5s", mode: "trailing" } }
947
+ );
948
+ await myTask.trigger(
949
+ { count: 2 },
950
+ { debounce: { key: "user-123", delay: "5s", mode: "trailing" } }
951
+ );
952
+ // After 5 seconds, runs with { count: 2 }
953
+ ```
954
+
955
+ Use **trailing mode** when you want to process the most recent data, such as:
956
+
957
+ - Saving the latest version of a document after edits stop
958
+ - Processing the final state after a series of rapid updates
959
+
960
+ **With `triggerAndWait`:**
961
+
962
+ When using `triggerAndWait` with debounce, the parent run blocks on the existing debounced run if one exists:
963
+
964
+ ```ts
965
+ export const parentTask = task({
966
+ id: "parent-task",
967
+ run: async (payload: string) => {
968
+ // Both will wait for the same run
969
+ const result = await childTask.triggerAndWait(
970
+ { data: payload },
971
+ { debounce: { key: "shared-key", delay: "3s" } }
972
+ );
973
+ return result;
974
+ },
975
+ });
976
+ ```
977
+
978
+ <Note>
979
+ Idempotency keys take precedence over debounce keys. If both are provided and an idempotency match
980
+ is found, it wins.
981
+ </Note>
982
+
983
+ ### `queue`
984
+
985
+ When you trigger a task you can override the concurrency limit. This is really useful if you sometimes have high priority runs.
986
+
987
+ The task:
988
+
989
+ ```ts /trigger/override-concurrency.ts
990
+ const generatePullRequest = task({
991
+ id: "generate-pull-request",
992
+ queue: {
993
+ //normally when triggering this task it will be limited to 1 run at a time
994
+ concurrencyLimit: 1,
995
+ },
996
+ run: async (payload) => {
997
+ //todo generate a PR using OpenAI
998
+ },
999
+ });
1000
+ ```
1001
+
1002
+ Triggering from your backend and overriding the concurrency:
1003
+
1004
+ ```ts app/api/push/route.ts
1005
+ import { generatePullRequest } from "~/trigger/override-concurrency";
1006
+
1007
+ export async function POST(request: Request) {
1008
+ const data = await request.json();
1009
+
1010
+ if (data.branch === "main") {
1011
+ //trigger the task, with a different queue
1012
+ const handle = await generatePullRequest.trigger(data, {
1013
+ queue: {
1014
+ //the "main-branch" queue will have a concurrency limit of 10
1015
+ //this triggered run will use that queue
1016
+ name: "main-branch",
1017
+ concurrencyLimit: 10,
1018
+ },
1019
+ });
1020
+
1021
+ return Response.json(handle);
1022
+ } else {
1023
+ //triggered with the default (concurrency of 1)
1024
+ const handle = await generatePullRequest.trigger(data);
1025
+ return Response.json(handle);
1026
+ }
1027
+ }
1028
+ ```
1029
+
1030
+ ### `concurrencyKey`
1031
+
1032
+ If you're building an application where you want to run tasks for your users, you might want a separate queue for each of your users. (It doesn't have to be users, it can be any entity you want to separately limit the concurrency for.)
1033
+
1034
+ You can do this by using `concurrencyKey`. It creates a separate queue for each value of the key.
1035
+
1036
+ Your backend code:
1037
+
1038
+ ```ts app/api/pr/route.ts
1039
+ import { generatePullRequest } from "~/trigger/override-concurrency";
1040
+
1041
+ export async function POST(request: Request) {
1042
+ const data = await request.json();
1043
+
1044
+ if (data.isFreeUser) {
1045
+ //free users can only have 1 PR generated at a time
1046
+ const handle = await generatePullRequest.trigger(data, {
1047
+ queue: {
1048
+ //every free user gets a queue with a concurrency limit of 1
1049
+ name: "free-users",
1050
+ concurrencyLimit: 1,
1051
+ },
1052
+ concurrencyKey: data.userId,
1053
+ });
1054
+
1055
+ //return a success response with the handle
1056
+ return Response.json(handle);
1057
+ } else {
1058
+ //trigger the task, with a different queue
1059
+ const handle = await generatePullRequest.trigger(data, {
1060
+ queue: {
1061
+ //every paid user gets a queue with a concurrency limit of 10
1062
+ name: "paid-users",
1063
+ concurrencyLimit: 10,
1064
+ },
1065
+ concurrencyKey: data.userId,
1066
+ });
1067
+
1068
+ //return a success response with the handle
1069
+ return Response.json(handle);
1070
+ }
1071
+ }
1072
+ ```
1073
+
1074
+ ### `maxAttempts`
1075
+
1076
+ You can set the maximum number of attempts for a task run. If the run fails, it will be retried up to the number of attempts you specify.
1077
+
1078
+ ```ts
1079
+ await myTask.trigger({ some: "data" }, { maxAttempts: 3 });
1080
+ await myTask.trigger({ some: "data" }, { maxAttempts: 1 }); // no retries
1081
+ ```
1082
+
1083
+ This will override the `retry.maxAttempts` value set in the task definition.
1084
+
1085
+ ### `tags`
1086
+
1087
+ View our [tags doc](/tags) for more information.
1088
+
1089
+ ### `metadata`
1090
+
1091
+ View our [metadata doc](/runs/metadata) for more information.
1092
+
1093
+ ### `maxDuration`
1094
+
1095
+ View our [maxDuration doc](/runs/max-duration) for more information.
1096
+
1097
+ ### `priority`
1098
+
1099
+ View our [priority doc](/runs/priority) for more information.
1100
+
1101
+ ### `region`
1102
+
1103
+ You can override the default region when you trigger a run:
1104
+
1105
+ ```ts
1106
+ await yourTask.trigger(payload, { region: "eu-central-1" });
1107
+ ```
1108
+
1109
+ If you don't specify a region it will use the default for your project. Go to the "Regions" page in the dashboard to see available regions or switch your default. Static IP addresses are also available on the Regions page for paid plans.
1110
+
1111
+ The region is where your runs are executed, it does not change where the run payload, output, tags, logs, or are any other data is stored.
1112
+
1113
+ ### `machine`
1114
+
1115
+ You can override the default machine preset when you trigger a run:
1116
+
1117
+ ```ts
1118
+ await yourTask.trigger(payload, { machine: "large-1x" });
1119
+ ```
1120
+
1121
+ If you don't specify a machine it will use the machine preset for your task (or the default for your project). For more information read [the machines guide](/machines).
1122
+
1123
+ ## Streaming batch triggering
1124
+
1125
+ <Note>This feature is only available with SDK 4.3.1+</Note>
1126
+
1127
+ For large batches, you can pass an `AsyncIterable` or `ReadableStream` instead of an array. This allows you to generate items on-demand without loading them all into memory upfront.
1128
+
1129
+ ```ts /trigger/my-task.ts
1130
+ import { task } from "@trigger.dev/sdk";
1131
+ import { myOtherTask } from "~/trigger/my-other-task";
1132
+
1133
+ export const myTask = task({
1134
+ id: "my-task",
1135
+ run: async (payload: { userIds: string[] }) => {
1136
+ // Use an async generator to stream items
1137
+ async function* generateItems() {
1138
+ for (const userId of payload.userIds) {
1139
+ yield { payload: { userId } };
1140
+ }
1141
+ }
1142
+
1143
+ const batchHandle = await myOtherTask.batchTrigger(generateItems());
1144
+
1145
+ return { batchId: batchHandle.batchId };
1146
+ },
1147
+ });
1148
+ ```
1149
+
1150
+ This works with all batch trigger methods:
1151
+
1152
+ - `yourTask.batchTrigger()`
1153
+ - `yourTask.batchTriggerAndWait()`
1154
+ - `batch.triggerByTask()`
1155
+ - `batch.triggerByTaskAndWait()`
1156
+ - `tasks.batchTrigger()`
1157
+
1158
+ Streaming is especially useful when generating batches from database queries, API pagination, or
1159
+ file processing where you don't want to load all items into memory at once.
1160
+
1161
+ ## Handling batch trigger errors
1162
+
1163
+ When batch triggering fails, the SDK throws a `BatchTriggerError` with properties that help you understand what went wrong and how to react:
1164
+
1165
+ | Property | Type | Description |
1166
+ | :--- | :--- | :--- |
1167
+ | `isRateLimited` | `boolean` | `true` if the error was caused by rate limiting |
1168
+ | `retryAfterMs` | `number \| undefined` | Milliseconds until the rate limit resets |
1169
+ | `phase` | `"create" \| "stream"` | Which phase of batch creation failed |
1170
+ | `batchId` | `string \| undefined` | The batch ID if it was created before failure |
1171
+ | `itemCount` | `number` | Number of items attempted in the batch |
1172
+ | `cause` | `unknown` | The underlying error |
1173
+
1174
+ ### Detecting and handling rate limits
1175
+
1176
+ When you hit [batch trigger rate limits](/limits#batch-trigger-rate-limits), you can detect this and implement retry logic:
1177
+
1178
+ ```ts Your backend
1179
+ import { tasks, BatchTriggerError } from "@trigger.dev/sdk";
1180
+ import type { myTask } from "~/trigger/myTask";
1181
+
1182
+ async function triggerBatchWithRetry(items: { payload: { userId: string } }[], maxRetries = 3) {
1183
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1184
+ try {
1185
+ return await tasks.batchTrigger<typeof myTask>("my-task", items);
1186
+ } catch (error) {
1187
+ if (error instanceof BatchTriggerError && error.isRateLimited) {
1188
+ // Rate limited - wait and retry
1189
+ const waitMs = error.retryAfterMs ?? 10000;
1190
+ console.log(`Rate limited. Waiting ${waitMs}ms before retry ${attempt + 1}/${maxRetries}`);
1191
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
1192
+ continue;
1193
+ }
1194
+ // Not a rate limit error - rethrow
1195
+ throw error;
1196
+ }
1197
+ }
1198
+ throw new Error("Max retries exceeded");
1199
+ }
1200
+ ```
1201
+
1202
+ ### Handling errors inside tasks
1203
+
1204
+ When calling `batchTrigger` from inside another task, you can handle errors similarly:
1205
+
1206
+ ```ts /trigger/parent-task.ts
1207
+ import { task, BatchTriggerError } from "@trigger.dev/sdk";
1208
+ import { childTask } from "./child-task";
1209
+
1210
+ export const parentTask = task({
1211
+ id: "parent-task",
1212
+ run: async (payload: { userIds: string[] }) => {
1213
+ const items = payload.userIds.map((userId) => ({ payload: { userId } }));
1214
+
1215
+ try {
1216
+ const batchHandle = await childTask.batchTrigger(items);
1217
+ return { batchId: batchHandle.batchId };
1218
+ } catch (error) {
1219
+ if (error instanceof BatchTriggerError) {
1220
+ // Log details about the failure
1221
+ console.error("Batch trigger failed", {
1222
+ message: error.message,
1223
+ phase: error.phase,
1224
+ itemCount: error.itemCount,
1225
+ isRateLimited: error.isRateLimited,
1226
+ });
1227
+
1228
+ if (error.isRateLimited) {
1229
+ // You might want to re-throw to let the task retry naturally
1230
+ throw error;
1231
+ }
1232
+ }
1233
+ throw error;
1234
+ }
1235
+ },
1236
+ });
1237
+ ```
1238
+
1239
+ <Note>
1240
+ For rate limit values and how the token bucket algorithm works, see [Batch trigger rate limits](/limits#batch-trigger-rate-limits).
1241
+ </Note>
1242
+
1243
+ ## Large Payloads
1244
+
1245
+ We recommend keeping your task payloads as small as possible. We currently have a hard limit on task payloads above 10MB.
1246
+
1247
+ If your payload size is larger than 512KB, instead of saving the payload to the database, we will upload it to an S3-compatible object store and store the URL in the database.
1248
+
1249
+ When your task runs, we automatically download the payload from the object store and pass it to your task function. We also will return to you a `payloadPresignedUrl` from the `runs.retrieve` SDK function so you can download the payload if needed:
1250
+
1251
+ ```ts
1252
+ import { runs } from "@trigger.dev/sdk";
1253
+
1254
+ const run = await runs.retrieve(handle);
1255
+
1256
+ if (run.payloadPresignedUrl) {
1257
+ const response = await fetch(run.payloadPresignedUrl);
1258
+ const payload = await response.json();
1259
+
1260
+ console.log("Payload", payload);
1261
+ }
1262
+ ```
1263
+
1264
+ <Note>
1265
+ We also use this same system for dealing with large task outputs, and subsequently will return a
1266
+ corresponding `outputPresignedUrl`. Task outputs are limited to 100MB.
1267
+ </Note>
1268
+
1269
+ If you need to pass larger payloads, you'll need to upload the payload to your own storage and pass a URL to the file in the payload instead. For example, uploading to S3 and then sending a presigned URL that expires in URL:
1270
+
1271
+ <CodeGroup>
1272
+
1273
+ ```ts /yourServer.ts
1274
+ import { myTask } from "./trigger/myTasks";
1275
+ import { s3Client, getSignedUrl, PutObjectCommand, GetObjectCommand } from "./s3";
1276
+ import { createReadStream } from "node:fs";
1277
+
1278
+ // Upload file to S3
1279
+ await s3Client.send(
1280
+ new PutObjectCommand({
1281
+ Bucket: "my-bucket",
1282
+ Key: "myfile.json",
1283
+ Body: createReadStream("large-payload.json"),
1284
+ })
1285
+ );
1286
+
1287
+ // Create presigned URL
1288
+ const presignedUrl = await getSignedUrl(
1289
+ s3Client,
1290
+ new GetObjectCommand({
1291
+ Bucket: "my-bucket",
1292
+ Key: "my-file.json",
1293
+ }),
1294
+ {
1295
+ expiresIn: 3600, // expires in 1 hour
1296
+ }
1297
+ );
1298
+
1299
+ // Now send the URL to the task
1300
+ const handle = await myTask.trigger({
1301
+ url: presignedUrl,
1302
+ });
1303
+ ```
1304
+
1305
+ ```ts /trigger/myTasks.ts
1306
+ import { task } from "@trigger.dev/sdk";
1307
+
1308
+ export const myTask = task({
1309
+ id: "my-task",
1310
+ run: async (payload: { url: string }) => {
1311
+ // Download the file from the URL
1312
+ const response = await fetch(payload.url);
1313
+ const data = await response.json();
1314
+
1315
+ // Do something with the data
1316
+ },
1317
+ });
1318
+ ```
1319
+
1320
+ </CodeGroup>