chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
---
|
||||
title: "Migrating from v3"
|
||||
description: "What's new in v4, how to migrate, and breaking changes."
|
||||
---
|
||||
|
||||
import NodeVersions from "/snippets/node-versions.mdx";
|
||||
import MigrateV4UsingAi from "/snippets/migrate-v4-using-ai.mdx";
|
||||
|
||||
<Warning>
|
||||
**Action required: Trigger.dev v3 deprecation**
|
||||
|
||||
We're retiring Trigger.dev v3. **New v3 deploys will stop working from 1 April 2026.** Trigger.dev v4 is stable, fully supported, and recommended for all users.
|
||||
|
||||
**Key dates:**
|
||||
|
||||
- **1 April 2026** — New v3 deploys will no longer work. Existing v3 runs will continue to execute.
|
||||
- **1 July 2026** — v3 will be fully shut down. All v3 runs will stop executing.
|
||||
|
||||
**What you need to do:** Migrate to v4 before April to avoid disruption to your task executions. The migration takes about 2 minutes — follow the steps on this page below. If you have questions or need help, [contact us](https://trigger.dev/contact) or reach out in our [Discord](https://trigger.dev/discord).
|
||||
|
||||
</Warning>
|
||||
|
||||
## What's new in v4?
|
||||
|
||||
| Feature | Description |
|
||||
| :------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Wait for token](/wait-for-token) | Create and wait for tokens to be completed, enabling approval workflows and waiting for arbitrary external conditions. |
|
||||
| Wait idempotency | Skip waits if the same idempotency key is used again when using [wait for](/wait-for#wait-idempotency), [wait until](/wait-until#wait-idempotency), or [wait for token](/wait-for-token#wait-idempotency). |
|
||||
| [Priority](/runs/priority) | Specify a priority when triggering a task. |
|
||||
| [Global lifecycle hooks](/tasks/overview#global-lifecycle-hooks) | Register global lifecycle hooks that are executed for all runs, regardless of the task. |
|
||||
| [onWait and onResume](/tasks/overview#onwait-and-onresume-functions) | Run code when a run is paused or resumed because of a wait. |
|
||||
| [onComplete](/tasks/overview#oncomplete-function) | Run code when a run completes, regardless of whether it succeeded or failed. |
|
||||
| [onCancel](/tasks/overview#oncancel-function) | Run code when a run is cancelled. |
|
||||
| [Hidden tasks](/hidden-tasks) | Create tasks that are not exported from your trigger files but can still be executed. |
|
||||
| [Middleware & locals](#middleware-and-locals) | The middleware system runs at the top level, executing before and after all lifecycle hooks. The locals API allows sharing data between middleware and hooks. |
|
||||
| [useWaitToken](/realtime/react-hooks/use-wait-token) | Use the useWaitToken hook to complete a wait token from a React component. |
|
||||
| [Task-backed AI tools](/tasks/schemaTask#task-backed-ai-tools) | Use `schemaTask` with AI SDK `tool()` and `ai.toolExecute()` (legacy `ai.tool` is deprecated). |
|
||||
|
||||
## Node.js support
|
||||
|
||||
<NodeVersions />
|
||||
|
||||
## How to migrate to v4
|
||||
|
||||
First read the deprecations and breaking changes sections below.
|
||||
|
||||
We recommend the following steps to migrate to v4:
|
||||
|
||||
1. Install the v4 package.
|
||||
2. Run the `trigger dev` CLI command and test your tasks locally, fixing any breaking changes.
|
||||
3. Deploy to the staging environment and test your tasks in staging, fixing any breaking changes. (this step is optional, but highly recommended)
|
||||
4. Once you've verified that v4 is working as expected, you should deploy your application backend with the updated v4 package.
|
||||
5. Once you've deployed your application backend, you should deploy your tasks to the production environment.
|
||||
|
||||
Note that between steps 4 and 5, runs triggered with the v4 package will continue using v3, and only new runs triggered after step 5 is complete will use v4.
|
||||
|
||||
<Warning>
|
||||
Once v4 is activated in your environment, there will be a period of time where old runs will
|
||||
continue to execute using v3, while new runs will use v4. Because these engines use completely
|
||||
different underlying queues and concurrency models, it's possible you may have up to double the
|
||||
amount of concurrently executing runs. Once the runs drain from the old run engine, the
|
||||
concurrency will return to normal.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
When migrating from v3 to v4, our infrastructure IPs may change. If you use IP allowlisting (e.g. for databases or APIs), [update your allowlists](https://trigger.dev/changelog/static-ips) with the current static IPs before or immediately after switching to v4 to avoid connectivity issues or downtime.
|
||||
</Note>
|
||||
|
||||
## Migrate using AI
|
||||
|
||||
Use the prompt in the accordion below to help you migrate your v3 tasks to v4. The prompt gives good results when using Claude 4 Sonnet. You’ll need a relatively large token limit.
|
||||
|
||||
<MigrateV4UsingAi />
|
||||
|
||||
## Installation
|
||||
|
||||
To opt-in to using v4, you will need to update your dependencies to the latest version:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash npx
|
||||
npx trigger.dev@latest update
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn dlx trigger.dev@latest update
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm dlx trigger.dev@latest update
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
This command should update all of your `@trigger.dev/*` packages to a `4.x` version.
|
||||
|
||||
## Deprecations
|
||||
|
||||
We've deprecated the following APIs:
|
||||
|
||||
### @trigger.dev/sdk/v3
|
||||
|
||||
We've deprecated the `@trigger.dev/sdk/v3` import path and moved to a new path:
|
||||
|
||||
```ts
|
||||
// This still works, but will be removed in a future version
|
||||
import { task } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// This is the new path
|
||||
import { task } from "@trigger.dev/sdk";
|
||||
```
|
||||
|
||||
### `handleError` and `init`
|
||||
|
||||
We've renamed the `handleError` hook to `catchError` to better reflect that it can catch and react to errors. `handleError` will be removed in a future version.
|
||||
|
||||
`init` was previously used to initialize data used in the run function:
|
||||
|
||||
```ts
|
||||
import { task } from "@trigger.dev/sdk";
|
||||
|
||||
const myTask = task({
|
||||
init: async () => {
|
||||
return {
|
||||
myClient: new MyClient(),
|
||||
};
|
||||
},
|
||||
run: async (payload: any, { ctx, init }) => {
|
||||
const client = init.myClient;
|
||||
await client.doSomething();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This has now been deprecated in favor of the `locals` API and middleware. See the [Improved middleware and locals](/tasks/overview#middleware-and-locals-functions) section for more details.
|
||||
|
||||
### toolTask
|
||||
|
||||
We've deprecated the `toolTask` function, which created both a Trigger.dev task and a tool compatible with the Vercel [AI SDK](https://vercel.com/docs/ai-sdk):
|
||||
|
||||
```ts
|
||||
import { toolTask, schemaTask } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
import { generateText } from "ai";
|
||||
|
||||
const myToolTask = toolTask({
|
||||
id: "my-tool-task",
|
||||
run: async (payload: any, { ctx }) => {},
|
||||
});
|
||||
|
||||
export const myAiTask = schemaTask({
|
||||
id: "my-ai-task",
|
||||
schema: z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
run: async (payload, { ctx }) => {
|
||||
const { text } = await generateText({
|
||||
prompt: payload.text,
|
||||
model: openai("gpt-4o"),
|
||||
tools: {
|
||||
myToolTask,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
We've replaced the `toolTask` function with `schemaTask` plus AI SDK `tool()` and `ai.toolExecute()` (the older `ai.tool()` wrapper is deprecated). See [Task-backed AI tools](/tasks/schemaTask#task-backed-ai-tools).
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### Queue changes
|
||||
|
||||
Previously, it was possible to specify a queue name of a queue that did not exist, along with a concurrency limit. The queue would then be created "on-demand" with the specified concurrency limit. If the queue did exist, the concurrency limit of the queue would be updated to the specified value:
|
||||
|
||||
```ts
|
||||
await myTask.trigger({ foo: "bar" }, { queue: { name: "my-queue", concurrencyLimit: 10 } });
|
||||
```
|
||||
|
||||
This is no longer possible, and queues must now be defined ahead of time using the `queue` function:
|
||||
|
||||
```ts
|
||||
import { queue } from "@trigger.dev/sdk";
|
||||
|
||||
const myQueue = queue({
|
||||
name: "my-queue",
|
||||
concurrencyLimit: 10,
|
||||
});
|
||||
```
|
||||
|
||||
Now when you trigger a task, you can only specify the queue by name:
|
||||
|
||||
```ts
|
||||
await myTask.trigger({ foo: "bar" }, { queue: "my-queue" });
|
||||
```
|
||||
|
||||
Or you can set the queue on the task:
|
||||
|
||||
```ts
|
||||
import { queue, task } from "@trigger.dev/sdk";
|
||||
|
||||
const myQueue = queue({
|
||||
name: "my-queue",
|
||||
concurrencyLimit: 10,
|
||||
});
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
queue: myQueue,
|
||||
run: async (payload: any, { ctx }) => {},
|
||||
});
|
||||
|
||||
// You can optionally specify the queue directly on the task
|
||||
export const myTask2 = task({
|
||||
id: "my-task-2",
|
||||
queue: {
|
||||
name: "my-queue-2",
|
||||
concurrencyLimit: 50,
|
||||
},
|
||||
run: async (payload: any, { ctx }) => {},
|
||||
});
|
||||
```
|
||||
|
||||
Now you can trigger these tasks without having to specify the queue name in the trigger options:
|
||||
|
||||
```ts
|
||||
await myTask.trigger({ foo: "bar" }); // Will use the queue defined on the task
|
||||
await myTask2.trigger({ foo: "bar" }); // Will use the queue defined on the task
|
||||
```
|
||||
|
||||
If you're using `concurrencyKey` you can specify the `queue` and `concurrencyKey` like this:
|
||||
|
||||
```ts
|
||||
const handle = await generatePullRequest.trigger(data, {
|
||||
queue: "paid-users",
|
||||
concurrencyKey: data.userId,
|
||||
});
|
||||
```
|
||||
|
||||
For each unique value of `concurrencyKey`, a new queue will be created using the `concurrencyLimit` from the queue. This allows you to have a queue per user.
|
||||
|
||||
### Lifecycle hooks
|
||||
|
||||
We've changed the function signatures of the lifecycle hooks to be more consistent and easier to use, by unifying all the parameters into a single object that can be destructured.
|
||||
|
||||
Previously, hooks received a payload as the first argument and then an additional object as the second argument:
|
||||
|
||||
```ts
|
||||
import { task } from "@trigger.dev/sdk";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
onStart: ({ payload, ctx }) => {},
|
||||
run: async (payload, { ctx }) => {},
|
||||
});
|
||||
```
|
||||
|
||||
Now, all the parameters are passed in a single object:
|
||||
|
||||
```ts
|
||||
import { task } from "@trigger.dev/sdk";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
onStart: ({ payload, ctx }) => {},
|
||||
// The run function still uses separate parameters
|
||||
run: async (payload, { ctx }) => {},
|
||||
});
|
||||
```
|
||||
|
||||
This is true for all the lifecycle hooks:
|
||||
|
||||
```ts
|
||||
import { task } from "@trigger.dev/sdk";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
onStart: ({ payload, ctx, task }) => {},
|
||||
onSuccess: ({ payload, ctx, task, output }) => {},
|
||||
onFailure: ({ payload, ctx, task, error }) => {},
|
||||
onWait: ({ payload, ctx, task, wait }) => {},
|
||||
onResume: ({ payload, ctx, task, wait }) => {},
|
||||
onComplete: ({ payload, ctx, task, result }) => {},
|
||||
catchError: ({ payload, ctx, task, error, retry, retryAt, retryDelayInMs }) => {},
|
||||
run: async (payload, { ctx }) => {},
|
||||
});
|
||||
```
|
||||
|
||||
### Context changes
|
||||
|
||||
We've made a few small changes to the `ctx` object:
|
||||
|
||||
- `ctx.attempt.id` and `ctx.attempt.status` have been removed. `ctx.attempt.number` is still available.
|
||||
- `ctx.task.exportName` has been removed (since we no longer require tasks to be exported to be triggered).
|
||||
|
||||
### BatchTrigger changes
|
||||
|
||||
The `batchTrigger` function no longer returns a `runs` list directly. In v3, you could access the runs directly from the batch handle:
|
||||
|
||||
```ts
|
||||
// In v3
|
||||
const batchHandle = await tasks.batchTrigger([
|
||||
[myTask, { foo: "bar" }],
|
||||
[myOtherTask, { baz: "qux" }],
|
||||
]);
|
||||
|
||||
// You could access runs directly
|
||||
console.log(batchHandle.runs);
|
||||
```
|
||||
|
||||
In v4, you now need to use the `batch.retrieve()` method to get the batch with its runs:
|
||||
|
||||
```ts
|
||||
// In v4
|
||||
const batchHandle = await tasks.batchTrigger([
|
||||
[myTask, { foo: "bar" }],
|
||||
[myOtherTask, { baz: "qux" }],
|
||||
]);
|
||||
|
||||
// Now you need to retrieve the batch to get the runs
|
||||
const batch = await batch.retrieve(batchHandle.batchId);
|
||||
console.log(batch.runs);
|
||||
```
|
||||
|
||||
### OpenTelemetry
|
||||
|
||||
We are now using newer versions of the OpenTelemetry packages. This means that if you're using custom exporters you may need to update the packages:
|
||||
|
||||
| Package | Previous Version | New Version | Change Type |
|
||||
| ----------------------------------------- | ---------------- | ----------- | ------------ |
|
||||
| `@opentelemetry/api-logs` | 0.52.1 | 0.203.0 | Major update |
|
||||
| `@opentelemetry/exporter-logs-otlp-http` | 0.52.1 | 0.203.0 | Major update |
|
||||
| `@opentelemetry/exporter-trace-otlp-http` | 0.52.1 | 0.203.0 | Major update |
|
||||
| `@opentelemetry/instrumentation` | 0.52.1 | 0.203.0 | Major update |
|
||||
Reference in New Issue
Block a user