Skip to main content

Trigger functions

Trigger tasks from your backend:
FunctionWhat it does
tasks.trigger()Triggers a single task run and returns a handle.
tasks.batchTrigger()Triggers a single task multiple times in a batch.
batch.trigger()Triggers multiple different tasks in a single batch.
Trigger tasks from inside another task:
FunctionWhat it does
yourTask.trigger()Triggers a task and returns a handle. Does not wait.
yourTask.batchTrigger()Triggers a task multiple times and returns handles. Does not wait.
yourTask.triggerAndWait()Triggers a task and waits for the result before continuing.
yourTask.batchTriggerAndWait()Triggers a task multiple times and waits for all results.
batch.triggerAndWait()Triggers multiple different tasks and waits for all results.
batch.triggerByTask()Like batch.trigger() but accepts task instances.
batch.triggerByTaskAndWait()Like batch.triggerByTask() but waits for all results.

Triggering from your backend

When triggering from your backend, set the TRIGGER_SECRET_KEY environment variable. You can find it on the API keys page in the dashboard.
If you are using Next.js Server Actions, take care with bundling.

tasks.trigger()

Triggers a single run without needing to import the task. Use a type-only import to get full type-checking:
Your backend
import { tasks } from "@trigger.dev/sdk";
import type { emailSequence } from "~/trigger/emails";
//     👆 type-only import

export async function POST(request: Request) {
  const data = await request.json();

  const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
    to: data.email,
    name: data.name,
  });

  return Response.json(handle);
}
Pass options as a third argument:
Your backend
import { tasks } from "@trigger.dev/sdk";
import type { emailSequence } from "~/trigger/emails";

export async function POST(request: Request) {
  const data = await request.json();

  const handle = await tasks.trigger<typeof emailSequence>(
    "email-sequence",
    { to: data.email, name: data.name },
    { delay: "1h" } // 👈 options
  );

  return Response.json(handle);
}

tasks.batchTrigger()

Triggers multiple runs of a single task at once:
Your backend
import { tasks } from "@trigger.dev/sdk";
import type { emailSequence } from "~/trigger/emails";

export async function POST(request: Request) {
  const data = await request.json();

  const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
    "email-sequence",
    data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
  );

  return Response.json(batchHandle);
}
Pass per-item options inline, or batch-level options as a third argument:
Your backend
import { tasks } from "@trigger.dev/sdk";
import type { emailSequence } from "~/trigger/emails";

const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
  "email-sequence",
  data.users.map((u) => ({
    payload: { to: u.email, name: u.name },
    options: { delay: "1h" }, // 👈 per-item options
  }))
);

batch.trigger()

Triggers multiple different tasks at once:
Your backend
import { batch } from "@trigger.dev/sdk";
import type { myTask1, myTask2 } from "~/trigger/myTasks";

export async function POST(request: Request) {
  const data = await request.json();

  const result = await batch.trigger<typeof myTask1 | typeof myTask2>([
    { id: "my-task-1", payload: { some: data.some } },
    { id: "my-task-2", payload: { other: data.other } },
  ]);

  return Response.json(result);
}

Triggering from inside another task

Use these functions when inside a task run. They allow waiting for results and avoid importing task code into your backend bundle.

yourTask.trigger()

Triggers a single run and returns a handle:
/trigger/my-task.ts
import { task, runs } from "@trigger.dev/sdk";
import { myOtherTask } from "~/trigger/my-other-task";

export const myTask = task({
  id: "my-task",
  run: async (payload: string) => {
    const handle = await myOtherTask.trigger({ foo: "some data" });

    const run = await runs.retrieve(handle);
    console.log("Triggered run status", run.status);
  },
});
If you need to call trigger() in a loop, use batchTrigger() instead — it is more efficient and can trigger up to 1,000 runs in a single call.

yourTask.batchTrigger()

Triggers multiple runs of the same task:
/trigger/my-task.ts
import { task, batch } from "@trigger.dev/sdk";
import { myOtherTask } from "~/trigger/my-other-task";

export const myTask = task({
  id: "my-task",
  run: async (payload: string) => {
    const batchHandle = await myOtherTask.batchTrigger([
      { payload: "item1" },
      { payload: "item2" },
    ]);

    const batchResult = await batch.retrieve(batchHandle.id);
  },
});

yourTask.triggerAndWait()

Triggers a task and pauses the current run until the child task completes:
/trigger/parent.ts
import { task } from "@trigger.dev/sdk";
import { childTask } from "~/trigger/child";

export const parentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    const result = await childTask.triggerAndWait("some-data");

    if (result.ok) {
      console.log("Child output", result.output);
    } else {
      console.error("Child error", result.error);
    }
  },
});
Use .unwrap() to throw automatically if the child task fails:
/trigger/parent.ts
import { task, SubtaskUnwrapError } from "@trigger.dev/sdk";
import { childTask } from "~/trigger/child";

export const parentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    try {
      const output = await childTask.triggerAndWait("some-data").unwrap();
      console.log("Output", output);
    } catch (error) {
      if (error instanceof SubtaskUnwrapError) {
        console.error("Child task failed", {
          runId: error.runId,
          taskId: error.taskId,
          cause: error.cause,
        });
      }
    }
  },
});
Do not use triggerAndWait inside Promise.all(). Use batchTriggerAndWait() instead.
This method can only be used inside a task. It will throw if called outside one.

yourTask.batchTriggerAndWait()

Triggers multiple runs in parallel and waits for all of them to finish:
/trigger/parent.ts
import { task } from "@trigger.dev/sdk";
import { childTask } from "~/trigger/child";

export const batchParentTask = task({
  id: "batch-parent-task",
  run: async (payload: string) => {
    const results = await childTask.batchTriggerAndWait([
      { payload: "item1" },
      { payload: "item2" },
      { payload: "item3" },
    ]);

    for (const run of results.runs) {
      if (run.ok) {
        console.log("Run succeeded", run.output);
      } else {
        console.error("Run failed", run.error);
      }
    }
  },
});

batch.triggerAndWait()

Triggers multiple different tasks and waits for all results:
/trigger/parent.ts
import { batch, task } from "@trigger.dev/sdk";
import { childTask1, childTask2 } from "~/trigger/children";

export const parentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
      { id: "child-task-1", payload: { foo: "World" } },
      { id: "child-task-2", payload: { bar: 42 } },
    ]);

    for (const result of results.runs) {
      if (result.ok) {
        switch (result.taskIdentifier) {
          case "child-task-1":
            console.log("Task 1 output", result.output); // typed as string
            break;
          case "child-task-2":
            console.log("Task 2 output", result.output); // typed as number
            break;
        }
      }
    }
  },
});

batch.triggerByTask()

Like batch.trigger() but accepts task instances directly for better type inference:
/trigger/parent.ts
import { batch, task, runs } from "@trigger.dev/sdk";
import { childTask1, childTask2 } from "~/trigger/children";

export const parentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    const results = await batch.triggerByTask([
      { task: childTask1, payload: { foo: "World" } },
      { task: childTask2, payload: { bar: 42 } },
    ]);

    // results.runs is a tuple — index access is fully typed
    const run1 = await runs.retrieve(results.runs[0]);
    const run2 = await runs.retrieve(results.runs[1]);
  },
});

batch.triggerByTaskAndWait()

Like batch.triggerByTask() but waits for all runs to complete:
/trigger/parent.ts
import { batch, task } from "@trigger.dev/sdk";
import { childTask1, childTask2 } from "~/trigger/children";

export const parentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    const {
      runs: [run1, run2],
    } = await batch.triggerByTaskAndWait([
      { task: childTask1, payload: { foo: "World" } },
      { task: childTask2, payload: { bar: 42 } },
    ]);

    if (run1.ok) {
      console.log("Task 1 output", run1.output);
    }
    if (run2.ok) {
      console.log("Task 2 output", run2.output);
    }
  },
});

Triggering from your frontend

If you want to trigger a task from a frontend application, use our React hooks.

Options

All trigger functions accept an options object as the last argument.

delay

Trigger now but run later:
await myTask.trigger({ some: "data" }, { delay: "1h" });
await myTask.trigger({ some: "data" }, { delay: "88s" });
await myTask.trigger({ some: "data" }, { delay: "2024-12-01T00:00:00" });
await myTask.trigger({ some: "data" }, { delay: new Date(Date.now() + 1000 * 60 * 60) });
Delayed runs show as Delayed in the dashboard. You can cancel or reschedule them:
import { runs } from "@trigger.dev/sdk";

await runs.cancel("run_1234");
await runs.reschedule("run_1234", { delay: "1h" });

ttl

Automatically expire a run if it has not started within the given time:
await myTask.trigger({ some: "data" }, { ttl: "1h" });
await myTask.trigger({ some: "data" }, { ttl: 3600 }); // seconds
Dev runs automatically get a 10-minute TTL. On Trigger.dev Cloud, staging and production runs receive a maximum TTL of 14 days.

idempotencyKey

Ensures a task is only triggered once with the same key — useful inside tasks that may be retried:
import { idempotencyKeys, task } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  retry: { maxAttempts: 4 },
  run: async (payload: any) => {
    const idempotencyKey = await idempotencyKeys.create("my-task-key");
    await childTask.trigger(payload, { idempotencyKey });
  },
});

debounce

Consolidate multiple trigger calls into a single delayed run:
// First trigger creates a run delayed by 5 seconds
await myTask.trigger({ some: "data" }, { debounce: { key: "user-123", delay: "5s" } });

// If triggered again within 5 seconds, the existing run is pushed later
await myTask.trigger({ updated: "data" }, { debounce: { key: "user-123", delay: "5s" } });

// The run only executes after 5 seconds of inactivity (first payload wins by default)
Use mode: "trailing" to execute with the last payload instead:
await myTask.trigger(
  { count: 2 },
  { debounce: { key: "user-123", delay: "5s", mode: "trailing" } }
);
Set maxDelay to guarantee execution even with continuous triggers:
await summarizeChat.trigger(
  { conversationId: "123" },
  {
    debounce: {
      key: "conversation-123",
      delay: "10s",
      maxDelay: "5m", // Always run within 5 minutes of first trigger
    },
  }
);

queue

Override the task’s default queue when triggering:
await generatePullRequest.trigger(data, {
  queue: {
    name: "main-branch",
    concurrencyLimit: 10,
  },
});

concurrencyKey

Create a separate queue for each value of the key — useful for per-user concurrency:
await generatePullRequest.trigger(data, {
  queue: { name: "free-users", concurrencyLimit: 1 },
  concurrencyKey: data.userId,
});

maxAttempts

Override the task’s retry limit for this specific run:
await myTask.trigger({ some: "data" }, { maxAttempts: 1 }); // no retries

machine

Override the machine preset for this run:
await yourTask.trigger(payload, { machine: "large-1x" });

region

Override the execution region for this run:
await yourTask.trigger(payload, { region: "eu-central-1" });

Streaming batch triggering

This feature requires SDK 4.3.1+.
Pass an AsyncIterable or ReadableStream to any batch trigger function to generate items lazily without loading them all into memory:
/trigger/my-task.ts
import { task } from "@trigger.dev/sdk";
import { myOtherTask } from "~/trigger/my-other-task";

export const myTask = task({
  id: "my-task",
  run: async (payload: { userIds: string[] }) => {
    async function* generateItems() {
      for (const userId of payload.userIds) {
        yield { payload: { userId } };
      }
    }

    const batchHandle = await myOtherTask.batchTrigger(generateItems());
    return { batchId: batchHandle.batchId };
  },
});

Handling batch trigger errors

When a batch trigger fails, the SDK throws a BatchTriggerError:
PropertyTypeDescription
isRateLimitedbooleantrue if caused by rate limiting.
retryAfterMsnumber | undefinedMilliseconds until the rate limit resets.
phase"create" | "stream"Which phase failed.
batchIdstring | undefinedThe batch ID if it was created before failure.
itemCountnumberNumber of items attempted.
/trigger/parent-task.ts
import { task, BatchTriggerError } from "@trigger.dev/sdk";
import { childTask } from "./child-task";

export const parentTask = task({
  id: "parent-task",
  run: async (payload: { userIds: string[] }) => {
    const items = payload.userIds.map((userId) => ({ payload: { userId } }));

    try {
      const batchHandle = await childTask.batchTrigger(items);
      return { batchId: batchHandle.batchId };
    } catch (error) {
      if (error instanceof BatchTriggerError && error.isRateLimited) {
        // Re-throw to let the task retry naturally
        throw error;
      }
      throw error;
    }
  },
});

Large payloads

Payloads must be under 10MB. Payloads over 512KB are automatically stored in object storage and downloaded when the task runs. Task outputs are limited to 100MB. For payloads above 10MB, upload to your own storage and pass a URL:
/yourServer.ts
import { myTask } from "./trigger/myTasks";

// Upload to S3 and get a presigned URL
const presignedUrl = await getSignedUrl(s3Client, new GetObjectCommand({ Bucket, Key }), {
  expiresIn: 3600,
});

const handle = await myTask.trigger({ url: presignedUrl });

Build docs developers (and LLMs) love