There are different types of tasks including regular tasks and scheduled tasks.

Hello world task and how to trigger it

Here’s an incredibly simple task:
/trigger/hello-world.ts
import { task } from "@trigger.dev/sdk";

const helloWorld = task({
  //1. Use a unique id for each task
  id: "hello-world",
  //2. The run function is the main function of the task
  run: async (payload: { message: string }) => {
    //3. You can write code that runs for a long time here, there are no timeouts
    console.log(payload.message);
  },
});
You can trigger this in two ways:
  1. From the dashboard using the “Test” feature.
  2. Trigger it from your backend code. See the full triggering guide here.
Here’s how to trigger a single run from elsewhere in your code:
Your backend code
import { helloWorld } from "./trigger/hello-world";

async function triggerHelloWorld() {
  //This triggers the task and returns a handle
  const handle = await helloWorld.trigger({ message: "Hello world!" });

  //You can use the handle to check the status of the task, cancel and retry it.
  console.log("Task is running with handle", handle.id);
}
You can also trigger a task from another task, and wait for the result.

Defining a task

The task function takes an object with the following fields.

The id field

This is used to identify your task so it can be triggered, managed, and you can view runs in the dashboard. This must be unique in your project – we recommend making it descriptive and unique.

The run function

Your custom code inside run() will be executed when your task is triggered. It’s an async function that has two arguments:
  1. The run payload - the data that you pass to the task when you trigger it.
  2. An object with ctx about the run (Context), and any output from the optional init function that runs before every run attempt.
Anything you return from the run function will be the result of the task. Data you return must be JSON serializable: strings, numbers, booleans, arrays, objects, and null.

retry options

A task is retried if an error is thrown, by default we retry 3 times. You can set the number of retries and the delay between retries in the retry field:
/trigger/retry.ts
export const taskWithRetries = task({
  id: "task-with-retries",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
    randomize: false,
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});
For more information read the retrying guide. It’s also worth mentioning that you can retry a block of code inside your tasks as well.

queue options

Queues allow you to control the concurrency of your tasks. This allows you to have one-at-a-time execution and parallel executions. There are also more advanced techniques like having different concurrencies for different sets of your users. For more information read the concurrency & queues guide.
/trigger/one-at-a-time.ts
export const oneAtATime = task({
  id: "one-at-a-time",
  queue: {
    concurrencyLimit: 1,
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});

machine options

Some tasks require more vCPUs or GBs of RAM. You can specify these requirements in the machine field. For more information read the machines guide.
/trigger/heavy-task.ts
export const heavyTask = task({
  id: "heavy-task",
  machine: {
    preset: "large-1x", // 4 vCPU, 8 GB RAM
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});

maxDuration option

By default tasks can execute indefinitely, which can be great! But you also might want to set a maxDuration to prevent a task from running too long. You can set the maxDuration on a task, and all runs of that task will be stopped if they exceed the duration.
/trigger/long-task.ts
export const longTask = task({
  id: "long-task",
  maxDuration: 300, // 300 seconds or 5 minutes
  run: async (payload: any, { ctx }) => {
    //...
  },
});
See our maxDuration guide for more information.

Global lifecycle hooks

When specifying global lifecycle hooks, we recommend using the init.ts file.
You can register global lifecycle hooks that are executed for all runs, regardless of the task. While you can still define these in the trigger.config.ts file, you can also register them anywhere in your codebase:
import { tasks } from "@trigger.dev/sdk";

tasks.onStart(({ ctx, payload, task }) => {
  console.log("Run started", ctx.run);
});

tasks.onSuccess(({ ctx, output }) => {
  console.log("Run finished", ctx.run);
});

tasks.onFailure(({ ctx, error }) => {
  console.log("Run failed", ctx.run);
});

init.ts

If you create a init.ts file at the root of your trigger directory, it will be automatically loaded when a task is executed. This is useful if you want to register global lifecycle hooks, or initialize a database connection, etc.
init.ts
import { tasks } from "@trigger.dev/sdk";

tasks.onStart(({ ctx, payload, task }) => {
  console.log("Run started", ctx.run);
});

Lifecycle functions

Lifecycle functions

init function

This function is called before a run attempt:
/trigger/init.ts
export const taskWithInit = task({
  id: "task-with-init",
  init: async (payload, { ctx }) => {
    //...
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});
You can also return data from the init function that will be available in the params of the run, cleanup, onSuccess, and onFailure functions.
/trigger/init-return.ts
export const taskWithInitReturn = task({
  id: "task-with-init-return",
  init: async (payload, { ctx }) => {
    return { someData: "someValue" };
  },
  run: async (payload: any, { ctx, init }) => {
    console.log(init.someData); // "someValue"
  },
});
Errors thrown in the init function are ignored.

cleanup function

This function is called after the run function is executed, regardless of whether the run was successful or not. It’s useful for cleaning up resources, logging, or other side effects.
/trigger/cleanup.ts
export const taskWithCleanup = task({
  id: "task-with-cleanup",
  cleanup: async (payload, { ctx }) => {
    //...
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});
Errors thrown in the cleanup function will fail the attempt.

middleware and locals functions

Our task middleware system runs at the top level, executing before and after all lifecycle hooks. This allows you to wrap the entire task execution lifecycle with custom logic.
An error thrown in middleware is just like an uncaught error in the run function: it will propagate through to handleError() and then will fail the attempt (causing a retry).
The locals API allows you to share data between middleware and hooks.
db.ts
import { locals } from "@trigger.dev/sdk";
import { logger, tasks } from "@trigger.dev/sdk";

// This would be type of your database client here
const DbLocal = locals.create<{ connect: () => Promise<void>; disconnect: () => Promise<void> }>(
  "db"
);

export function getDb() {
  return locals.getOrThrow(DbLocal);
}

export function setDb(db: { connect: () => Promise<void> }) {
  locals.set(DbLocal, db);
}

tasks.middleware("db", async ({ ctx, payload, next, task }) => {
  // This would be your database client here
  const db = locals.set(DbLocal, {
    connect: async () => {
      logger.info("Connecting to the database");
    },
    disconnect: async () => {
      logger.info("Disconnecting from the database");
    },
  });

  await db.connect();

  await next();

  await db.disconnect();
});

// Disconnect when the run is paused
tasks.onWait("db", async ({ ctx, payload, task }) => {
  const db = getDb();
  await db.disconnect();
});

// Reconnect when the run is resumed
tasks.onResume("db", async ({ ctx, payload, task }) => {
  const db = getDb();
  await db.connect();
});
You can access the database client using getDb() in your tasks run function and all your hooks (global or task specific):
import { getDb } from "./db";

export const myTask = task({
  run: async (payload: any, { ctx }) => {
    const db = getDb();
    await db.query("SELECT 1");
  },
});

onStart function

When a task run starts, the onStart function is called. It’s useful for sending notifications, logging, and other side effects. This function will only be called one per run (not per retry). If you want to run code before each retry, use the init function.
/trigger/on-start.ts
export const taskWithOnStart = task({
  id: "task-with-on-start",
  onStart: async (payload, { ctx }) => {
    //...
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});
You can also define an onStart function in your trigger.config.ts file to get notified when any task starts.
trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "proj_1234",
  onStart: async (payload, { ctx }) => {
    console.log("Task started", ctx.task.id);
  },
});
Errors thrown in the onStart function are ignored.

onWait and onResume functions

These lifecycle hooks allow you to run code when a run is paused or resumed because of a wait:
export const myTask = task({
  id: "my-task",
  onWait: async ({ wait }) => {
    console.log("Run paused", wait);
  },
  onResume: async ({ wait }) => {
    console.log("Run resumed", wait);
  },
  run: async (payload: any, { ctx }) => {
    console.log("Run started", ctx.run);

    await wait.for({ seconds: 10 });

    console.log("Run finished", ctx.run);
  },
});

onSuccess function

When a task run succeeds, the onSuccess function is called. It’s useful for sending notifications, logging, syncing state to your database, or other side effects.
/trigger/on-success.ts
export const taskWithOnSuccess = task({
  id: "task-with-on-success",
  onSuccess: async (payload, output, { ctx }) => {
    //...
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});
You can also define an onSuccess function in your trigger.config.ts file to get notified when any task succeeds.
trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "proj_1234",
  onSuccess: async (payload, output, { ctx }) => {
    console.log("Task succeeded", ctx.task.id);
  },
});
Errors thrown in the onSuccess function are ignored.

onComplete function

This hook is executed when a run completes, regardless of whether it succeeded or failed:
/trigger/on-complete.ts
export const taskWithOnComplete = task({
  id: "task-with-on-complete",
  onComplete: async (payload, output, { ctx }) => {
  if (result.ok) {
    console.log("Run succeeded", result.data);
  } else {
    console.log("Run failed", result.error);
  }
});

onFailure function

When a task run fails, the onFailure function is called. It’s useful for sending notifications, logging, or other side effects. It will only be executed once the task run has exhausted all its retries.
/trigger/on-failure.ts
export const taskWithOnFailure = task({
  id: "task-with-on-failure",
  onFailure: async (payload, error, { ctx }) => {
    //...
  },
  run: async (payload: any, { ctx }) => {
    //...
  },
});
You can also define an onFailure function in your trigger.config.ts file to get notified when any task fails.
trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "proj_1234",
  onFailure: async (payload, error, { ctx }) => {
    console.log("Task failed", ctx.task.id);
  },
});
Errors thrown in the onFailure function are ignored.
onFailure doesn’t fire for some of the run statuses like Crashed, System failures, and Canceled.

handleError functions

You can define a function that will be called when an error is thrown in the run function, that allows you to control how the error is handled and whether the task should be retried. Read more about handleError in our Errors and Retrying guide.
Uncaught errors will throw a special internal error of the type HANDLE_ERROR_ERROR.

onCancel

You can define an onCancel hook that is called when a run is cancelled. This is useful if you want to clean up any resources that were allocated for the run.
tasks.onCancel(({ ctx, signal }) => {
  console.log("Run cancelled", signal);
});
You can use the onCancel hook along with the signal passed into the run function to interrupt a call to an external service, for example using the streamText function from the AI SDK:
import { logger, tasks, schemaTask } from "@trigger.dev/sdk";
import { streamText } from "ai";
import { z } from "zod";

export const interruptibleChat = schemaTask({
  id: "interruptible-chat",
  description: "Chat with the AI",
  schema: z.object({
    prompt: z.string().describe("The prompt to chat with the AI"),
  }),
  run: async ({ prompt }, { signal }) => {
    const chunks: TextStreamPart<{}>[] = [];

    // 👇 This is a global onCancel hook, but it's inside of the run function
    tasks.onCancel(async () => {
      // We have access to the chunks here, and can save them to the database
      await saveChunksToDatabase(chunks);
    });

    try {
      const result = streamText({
        model: getModel(),
        prompt,
        experimental_telemetry: {
          isEnabled: true,
        },
        tools: {},
        abortSignal: signal, // 👈 Pass the signal to the streamText function, which aborts with the run is cancelled
        onChunk: ({ chunk }) => {
          chunks.push(chunk);
        },
      });

      const textParts = [];

      for await (const part of result.textStream) {
        textParts.push(part);
      }

      return textParts.join("");
    } catch (error) {
      if (error instanceof Error && error.name === "AbortError") {
        // streamText will throw an AbortError if the signal is aborted, so we can handle it here
      } else {
        throw error;
      }
    }
  },
});
The onCancel hook can optionally wait for the run function to finish, and access the output of the run:
import { logger, task } from "@trigger.dev/sdk";
import { setTimeout } from "node:timers/promises";

export const cancelExampleTask = task({
  id: "cancel-example",
  // Signal will be aborted when the task is cancelled 👇
  run: async (payload: { message: string }, { signal }) => {
    try {
      // We pass the signal to setTimeout to abort the timeout if the task is cancelled
      await setTimeout(10_000, undefined, { signal });
    } catch (error) {
      // Ignore the abort error
    }

    // Do some more work here

    return {
      message: "Hello, world!",
    };
  },
  onCancel: async ({ runPromise }) => {
    // You can await the runPromise to get the output of the task
    const output = await runPromise;
  },
});
You will have up to 30 seconds to complete the runPromise in the onCancel hook. After that point the process will be killed.

Next steps