335 lines
14 KiB
Plaintext
335 lines
14 KiB
Plaintext
---
|
||
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 |
|