Skip to content

Commit 0213d98

Browse files
committed
n8n concept map and common patterns
1 parent 8244ac6 commit 0213d98

File tree

2 files changed

+272
-1
lines changed

2 files changed

+272
-1
lines changed

docs/docs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@
470470
},
471471
{
472472
"group": "Migration guides",
473-
"pages": ["migration-mergent"]
473+
"pages": ["migration-mergent", "migration-n8n"]
474474
},
475475
{
476476
"group": "Community packages",

docs/migration-n8n.mdx

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
---
2+
title: "Migrating from n8n"
3+
description: "A practical guide for moving your n8n workflows to Trigger.dev"
4+
sidebarTitle: "Migrating from n8n"
5+
---
6+
7+
If you've been building with n8n and are ready to move to code-first workflows, this guide is for you. This page maps them to their Trigger.dev equivalents and walks through common patterns side by side.
8+
9+
## Concept map
10+
11+
| n8n | Trigger.dev |
12+
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
13+
| Workflow | [`task`](/tasks/overview) plus its config (`queue`, `retry`, `onFailure`) |
14+
| Schedule Trigger | [`schedules.task`](/tasks/scheduled) |
15+
| Webhook node | Route handler + [`task.trigger()`](/triggering) |
16+
| Node | A step or library call inside `run()` |
17+
| Execute Sub-workflow node (wait for completion) | [`tasks.triggerAndWait()`](/triggering#yourtask-triggerandwait) |
18+
| Execute Sub-workflow node (execute in background) | [`tasks.trigger()`](/triggering) |
19+
| Loop over N items → Execute Sub-workflow → Merge | [`tasks.batchTriggerAndWait()`](/tasks#yourtask-batchtriggerandwait) |
20+
| Loop Over Items (Split in Batches) | `for` loop or `.map()` |
21+
| IF / Switch node | `if` / `switch` statements |
22+
| Wait node (time interval or specific time) | [`wait.for()`](/wait-for) or [`wait.until()`](/wait-until) |
23+
| Error Trigger node / Error Workflow | [`onFailure`](/tasks/overview#onfailure-function) hook (both collapse into one concept in Trigger.dev) |
24+
| Continue On Fail | `try/catch` around an individual step |
25+
| Stop And Error | `throw new Error(...)` |
26+
| Code node | A function or step within `run()` |
27+
| Credentials | [Environment variable secret](/deploy-environment-variables) |
28+
| Execution | Run (visible in the dashboard with full logs) |
29+
| Retry on Fail (per-node setting) | [`retry.maxAttempts`](/tasks/overview#retry) (retries the whole `run()`, not a single step) |
30+
| AI Agent node | Any AI SDK called inside `run()` (Vercel AI SDK, Claude SDK, OpenAI SDK, etc.) |
31+
| Respond to Webhook node | Route handler + [`task.triggerAndWait()`](/triggering#yourtask-triggerandwait) returning the result as HTTP response |
32+
33+
---
34+
35+
## Setup
36+
37+
<Steps>
38+
39+
<Step title="Create an account">
40+
41+
Go to [Trigger.dev Cloud](https://cloud.trigger.dev), create an account, and create a project.
42+
43+
</Step>
44+
45+
<Step title="Install the CLI and initialize">
46+
47+
```bash
48+
npx trigger.dev@latest init
49+
```
50+
51+
This adds Trigger.dev to your project and creates a `trigger/` directory for your tasks.
52+
53+
</Step>
54+
55+
<Step title="Run the local dev server">
56+
57+
```bash
58+
npx trigger.dev@latest dev
59+
```
60+
61+
You'll get a local server that behaves like production. Your runs appear in the dashboard as you test.
62+
63+
</Step>
64+
65+
</Steps>
66+
67+
---
68+
69+
## Common patterns
70+
71+
### Webhook trigger
72+
73+
In n8n you use a **Webhook** trigger node, which registers a URL that starts the workflow.
74+
75+
In Trigger.dev, your existing route handler receives the webhook and triggers the task:
76+
77+
<CodeGroup>
78+
79+
```ts trigger/process-webhook.ts
80+
import { task } from "@trigger.dev/sdk";
81+
82+
export const processWebhook = task({
83+
id: "process-webhook",
84+
run: async (payload: { event: string; data: Record<string, unknown> }) => {
85+
// handle the webhook payload
86+
await handleEvent(payload.event, payload.data);
87+
},
88+
});
89+
```
90+
91+
```ts app/api/webhook/route.ts
92+
import { processWebhook } from "@/trigger/process-webhook";
93+
94+
export async function POST(request: Request) {
95+
const body = await request.json();
96+
97+
await processWebhook.trigger({
98+
event: body.event,
99+
data: body.data,
100+
});
101+
102+
return Response.json({ received: true });
103+
}
104+
```
105+
106+
</CodeGroup>
107+
108+
---
109+
110+
### Chaining steps (Sub-workflows)
111+
112+
In n8n you use the **Execute Sub-workflow** node to call another workflow and wait for the result.
113+
114+
In Trigger.dev you use `triggerAndWait()`:
115+
116+
<CodeGroup>
117+
118+
```ts trigger/process-order.ts
119+
import { task } from "@trigger.dev/sdk";
120+
import { sendConfirmationEmail } from "./send-confirmation-email";
121+
122+
export const processOrder = task({
123+
id: "process-order",
124+
run: async (payload: { orderId: string; email: string }) => {
125+
const result = await processPayment(payload.orderId);
126+
127+
// trigger a subtask and wait for it to complete
128+
await sendConfirmationEmail.triggerAndWait({
129+
email: payload.email,
130+
orderId: payload.orderId,
131+
amount: result.amount,
132+
});
133+
134+
return { processed: true };
135+
},
136+
});
137+
```
138+
139+
```ts trigger/send-confirmation-email.ts
140+
import { task } from "@trigger.dev/sdk";
141+
142+
export const sendConfirmationEmail = task({
143+
id: "send-confirmation-email",
144+
run: async (payload: { email: string; orderId: string; amount: number }) => {
145+
await sendEmail({
146+
to: payload.email,
147+
subject: `Order ${payload.orderId} confirmed`,
148+
body: `Your order for $${payload.amount} has been confirmed.`,
149+
});
150+
},
151+
});
152+
```
153+
154+
</CodeGroup>
155+
156+
To trigger multiple subtasks in parallel and wait for all of them (like the **Merge** node in n8n):
157+
158+
```ts trigger/process-batch.ts
159+
import { task } from "@trigger.dev/sdk";
160+
import { processItem } from "./process-item";
161+
162+
export const processBatch = task({
163+
id: "process-batch",
164+
run: async (payload: { items: { id: string }[] }) => {
165+
// fan out to subtasks, collect all results
166+
const results = await processItem.batchTriggerAndWait(
167+
payload.items.map((item) => ({ payload: { id: item.id } }))
168+
);
169+
170+
return { processed: results.runs.length };
171+
},
172+
});
173+
```
174+
175+
---
176+
177+
### Error handling
178+
179+
In n8n you use **Continue On Fail** on individual nodes and a separate **Error Workflow** for workflow-level failures.
180+
181+
In Trigger.dev:
182+
183+
- Use `try/catch` for recoverable errors at a specific step
184+
- Use the `onFailure` hook for workflow-level failure handling
185+
- Configure `retry` for automatic retries with backoff
186+
187+
```ts trigger/import-data.ts
188+
import { task } from "@trigger.dev/sdk";
189+
190+
export const importData = task({
191+
id: "import-data",
192+
// automatic retries with exponential backoff
193+
retry: {
194+
maxAttempts: 3,
195+
},
196+
// runs if this task fails after all retries
197+
onFailure: async ({ payload, error }) => {
198+
await sendAlertToSlack(`import-data failed: ${(error as Error).message}`);
199+
},
200+
run: async (payload: { source: string }) => {
201+
let records;
202+
203+
// continue on fail equivalent: catch the error and handle locally
204+
try {
205+
records = await fetchFromSource(payload.source);
206+
} catch (error) {
207+
records = await fetchFromFallback(payload.source);
208+
}
209+
210+
await saveRecords(records);
211+
},
212+
});
213+
```
214+
215+
---
216+
217+
### Waiting and delays
218+
219+
In n8n you use the **Wait** node to pause a workflow for a fixed time or until a webhook is called.
220+
221+
In Trigger.dev:
222+
223+
```ts trigger/send-followup.ts
224+
import { task, wait } from "@trigger.dev/sdk";
225+
226+
export const sendFollowup = task({
227+
id: "send-followup",
228+
run: async (payload: { userId: string; email: string }) => {
229+
await sendWelcomeEmail(payload.email);
230+
231+
// wait for a fixed duration, execution is frozen, you don't pay while waiting
232+
await wait.for({ days: 3 });
233+
234+
const hasActivated = await checkUserActivation(payload.userId);
235+
if (!hasActivated) {
236+
await sendFollowupEmail(payload.email);
237+
}
238+
},
239+
});
240+
```
241+
242+
To wait for an external event (like n8n's "On Webhook Call" resume mode), use `wait.createToken()` to generate a URL, send that URL to the external system, then pause with `wait.forToken()` until the external system POSTs to that URL to resume the run.
243+
244+
```ts trigger/approval-flow.ts
245+
import { task, wait } from "@trigger.dev/sdk";
246+
247+
export const approvalFlow = task({
248+
id: "approval-flow",
249+
run: async (payload: { requestId: string; approverEmail: string }) => {
250+
// create a token — this generates a URL the external system can POST to
251+
const token = await wait.createToken({
252+
timeout: "48h",
253+
tags: [`request-${payload.requestId}`],
254+
});
255+
256+
// send the token URL to whoever needs to resume this run
257+
await sendApprovalRequest(payload.approverEmail, payload.requestId, token.url);
258+
259+
// pause until the external system POSTs to token.url
260+
const result = await wait.forToken<{ approved: boolean }>(token).unwrap();
261+
262+
if (result.approved) {
263+
await executeApprovedAction(payload.requestId);
264+
} else {
265+
await notifyRejection(payload.requestId);
266+
}
267+
},
268+
});
269+
```
270+
271+
---

0 commit comments

Comments
 (0)