---
title: "Triggering"
description: "Tasks need to be triggered to run."
---

These are the different ways you can trigger tasks:

| Function                         | Where does this work? | What it does                                                                                                                       |
| -------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `yourTask.trigger()`             | Anywhere              | Triggers a task and gets a handle you can use to monitor and manage the run. It does not wait for the result.                      |
| `yourTask.batchTrigger()`        | Anywhere              | Triggers a task multiple times and gets a handle you can use to monitor and manage the runs. It does not wait for the results.     |
| `yourTask.triggerAndWait()`      | Inside task           | Triggers a task and then waits until it's complete. You get the result data to continue with.                                      |
| `yourTask.batchTriggerAndWait()` | Inside task           | Triggers a task multiple times in parallel and then waits until they're all complete. You get the resulting data to continue with. |
| `tasks.trigger()`                | Anywhere              | Triggers a task and gets a handle you can use to fetch and manage the run.                                                         |
| `tasks.batchTrigger()`           | Anywhere              | Triggers a task multiple times and gets a handle you can use to fetch and manage the runs.                                         |
| `tasks.triggerAndWait()`         | Inside task           | Triggers a task and then waits until it's complete. You get the result data to continue with                                       |
| `tasks.batchTriggerAndWait()`    | Inside task           | Triggers a task multiple times in parallel and then waits until they're all complete. You get the resulting data to continue with. |

Additionally, [scheduled tasks](/v3/tasks-scheduled) get automatically triggered on their schedule and [webhooks](/v3/tasks-webhooks) when receiving a webhook.

## Scheduled tasks

You should attach one or more schedules to your `schedules.task()` to trigger it on a recurring schedule. [Read the scheduled tasks docs](/v3/tasks-scheduled).

## Authentication

When you trigger a task from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable. You can find the value on the API keys page in the Trigger.dev dashboard. [More info on API keys](/v3/apikeys).

## Task instance methods

Task instance methods are available on the `Task` object you receive when you define a task. They can be called from your backend code or from inside another task.

### Task.trigger()

Triggers a single run of a task with the payload you pass in, and any options you specify. It does NOT wait for the result, you cannot do that from outside a task.

If called from within a task, you can use the `AndWait` version to pause execution until the triggered run is complete.

<CodeGroup>

```ts Next.js API route
import { emailSequence } from "~/trigger/emails";

//app/email/route.ts
export async function POST(request: Request) {
  //get the JSON from the request
  const data = await request.json();

  //trigger your task
  const handle = await emailSequence.trigger({ to: data.email, name: data.name });

  //return a success response with the handle
  return Response.json(handle);
}
```

```ts Remix
import { emailSequence } from "~/trigger/emails";

export async function action({ request, params }: ActionFunctionArgs) {
  if (request.method.toUpperCase() !== "POST") {
    return json("Method Not Allowed", { status: 405 });
  }

  //get the JSON from the request
  const data = await request.json();

  //trigger your task
  const handle = await emailSequence.trigger({ to: data.email, name: data.name });

  //return a success response with the handle
  return json(handle);
}
```

```ts /trigger/my-task.ts
import { myOtherTask } from "~/trigger/my-other-task";

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

    //...do other stuff
  },
});
```

</CodeGroup>

### Task.batchTrigger()

Triggers multiples runs of a task with the payloads you pass in, and any options you specify.

<CodeGroup>

```ts Next.js API route
import { emailSequence } from "~/trigger/emails";

//app/email/route.ts
export async function POST(request: Request) {
  //get the JSON from the request
  const data = await request.json();

  //batch trigger your task
  const batchHandle = await emailSequence.batchTrigger(
    data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
  );

  //return a success response with the handle
  return Response.json(batchHandle);
}
```

```ts Remix
import { emailSequence } from "~/trigger/emails";

export async function action({ request, params }: ActionFunctionArgs) {
  if (request.method.toUpperCase() !== "POST") {
    return json("Method Not Allowed", { status: 405 });
  }

  //get the JSON from the request
  const data = await request.json();

  //batch trigger your task
  const batchHandle = await emailSequence.batchTrigger(
    data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
  );

  //return a success response with the handle
  return json(batchHandle);
}
```

```ts /trigger/my-task.ts
import { myOtherTask } from "~/trigger/my-other-task";

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

    //...do other stuff
  },
});
```

</CodeGroup>

### Task.triggerAndWait()

<Warning>
  This method should only be used inside a task. If you use it outside a task, it will throw an
  error.
</Warning>

This is where it gets interesting. You can trigger a task and then wait for the result. This is useful when you need to call a different task and then use the result to continue with your task.

<Accordion title="Don't use this in parallel, e.g. with `Promise.all()`">
  Instead, use `batchTriggerAndWait()` if you can, or a for loop if you can't.

To control concurrency using batch triggers, you can set `queue.concurrencyLimit` on the child task.

<CodeGroup>

```ts /trigger/batch.ts
export const batchTask = task({
  id: "batch-task",
  run: async (payload: string) => {
    const results = await childTask.batchTriggerAndWait([
      { payload: "item1" },
      { payload: "item2" },
    ]);
    console.log("Results", results);

    //...do stuff with the results
  },
});
```

```ts /trigger/loop.ts
export const loopTask = task({
  id: "loop-task",
  run: async (payload: string) => {
    //this will be slower than the batch version
    //as we have to resume the parent after each iteration
    for (let i = 0; i < 2; i++) {
      const result = await childTask.triggerAndWait(`item${i}`);
      console.log("Result", result);

      //...do stuff with the result
    }
  },
});
```

</CodeGroup>

</Accordion>

```ts /trigger/parent.ts
export const parentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    const result = await batchChildTask.triggerAndWait("some-data");
    console.log("Result", result);

    //...do stuff with the result
  },
});
```

### Task.batchTriggerAndWait()

<Warning>
  This method should only be used inside a task. If you use it outside a task, it will throw an
  error.
</Warning>

You can batch trigger a task and wait for all the results. This is useful for the fan-out pattern, where you need to call a task multiple times and then wait for all the results to continue with your task.

<Accordion title="Don't use this in parallel, e.g. with `Promise.all()`">
  Instead, pass in all items at once and set an appropriate `maxConcurrency`. Alternatively, use sequentially with a for loop.

To control concurrency, you can set `queue.concurrencyLimit` on the child task.

<CodeGroup>

```ts /trigger/batch.ts
export const batchTask = task({
  id: "batch-task",
  run: async (payload: string) => {
    const results = await childTask.batchTriggerAndWait([
      { payload: "item1" },
      { payload: "item2" },
    ]);
    console.log("Results", results);

    //...do stuff with the results
  },
});
```

```ts /trigger/loop.ts
export const loopTask = task({
  id: "loop-task",
  run: async (payload: string) => {
    //this will be slower than a single batchTriggerAndWait()
    //as we have to resume the parent after each iteration
    for (let i = 0; i < 2; i++) {
      const result = await childTask.batchTriggerAndWait([
        { payload: `itemA${i}` },
        { payload: `itemB${i}` },
      ]);
      console.log("Result", result);

      //...do stuff with the result
    }
  },
});
```

</CodeGroup>

</Accordion>

```ts /trigger/nested.ts
export const batchParentTask = task({
  id: "parent-task",
  run: async (payload: string) => {
    const results = await childTask.batchTriggerAndWait([
      { payload: "item4" },
      { payload: "item5" },
      { payload: "item6" },
    ]);
    console.log("Results", results);

    //...do stuff with the result
  },
});
```

## SDK functions

You can trigger any task from your backend code using the `tasks.trigger()` or `tasks.batchTrigger()` SDK functions.

<Note>
  Do not trigger tasks directly from your frontend. If you do, you will leak your private
  Trigger.dev API key.
</Note>

You can use Next.js Server Actions but [you need to be careful with bundling](#next-js-server-actions).

### tasks.trigger()

Triggers a single run of a task with the payload you pass in, and any options you specify, without needing to import the task.

<Note>
  Why would you use this instead of the `Task.trigger()` instance method? Tasks can import
  dependencies/modules that you might not want included in your application code or cause problems
  with building.
</Note>

<CodeGroup>

```ts Next.js API route
import { tasks } from "@trigger.dev/sdk/v3";
import type { emailSequence } from "~/trigger/emails";
//     👆 **type-only** import

//app/email/route.ts
export async function POST(request: Request) {
  //get the JSON from the request
  const data = await request.json();

  // Pass the task type to `trigger()` as a generic argument, giving you full type checking
  const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
    to: data.email,
    name: data.name,
  });

  //return a success response with the handle
  return Response.json(handle);
}
```

```ts Remix
import { tasks } from "@trigger.dev/sdk/v3";

export async function action({ request, params }: ActionFunctionArgs) {
  if (request.method.toUpperCase() !== "POST") {
    return json("Method Not Allowed", { status: 405 });
  }

  //get the JSON from the request
  const data = await request.json();

  // The generic argument is optional, but recommended for full type checking
  const handle = await tasks.trigger("email-sequence", {
    to: data.email,
    name: data.name,
  });

  //return a success response with the handle
  return json(handle);
}
```

</CodeGroup>

<Tip>
  By importing the task with the type modifier, the import of `"~/trigger/emails"` is a type-only
  import. This means that the task code is not included in your application at build time.
</Tip>

### tasks.batchTrigger()

Triggers multiples runs of a task with the payloads you pass in, and any options you specify, without needing to import the task.

<CodeGroup>

```ts Next.js API route
import { tasks } from "@trigger.dev/sdk/v3";
import type { emailSequence } from "~/trigger/emails";
//     👆 **type-only** import

//app/email/route.ts
export async function POST(request: Request) {
  //get the JSON from the request
  const data = await request.json();

  // Pass the task type to `batchTrigger()` as a generic argument, giving you full type checking
  const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
    "email-sequence",
    data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
  );

  //return a success response with the handle
  return Response.json(batchHandle);
}
```

</CodeGroup>

### tasks.triggerAndPoll()

Triggers a single run of a task with the payload you pass in, and any options you specify, and then polls the run until it's complete.

<CodeGroup>

```ts Next.js API route
import { tasks } from "@trigger.dev/sdk/v3";
import type { emailSequence } from "~/trigger/emails";

//app/email/route.ts
export async function POST(request: Request) {
  //get the JSON from the request
  const data = await request.json();

  // Pass the task type to `triggerAndPoll()` as a generic argument, giving you full type checking
  const result = await tasks.triggerAndPoll<typeof emailSequence>(
    "email-sequence",
    {
      to: data.email,
      name: data.name,
    },
    { pollIntervalMs: 5000 }
  );

  //return a success response with the result
  return Response.json(result);
}
```

</CodeGroup>

<Note>
  The above code is just a demonstration of the API and is not recommended to use in an API route
  this way as it will block the request until the task is complete.
</Note>

## Options

All of the above functions accept an options object:

```ts
await myTask.trigger({ some: "data" }, { delay: "1h", ttl: "1h" });
await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" } }]);
```

The following options are available:

### `delay`

When you want to trigger a task now, but have it run at a later time, you can use the `delay` option:

```ts
// Delay the task run by 1 hour
await myTask.trigger({ some: "data" }, { delay: "1h" });
// Delay the task run by 88 seconds
await myTask.trigger({ some: "data" }, { delay: "88s" });
// Delay the task run by 1 hour and 52 minutes and 18 seconds
await myTask.trigger({ some: "data" }, { delay: "1h52m18s" });
// Delay until a specific time
await myTask.trigger({ some: "data" }, { delay: "2024-12-01T00:00:00" });
// Delay using a Date object
await myTask.trigger({ some: "data" }, { delay: new Date(Date.now() + 1000 * 60 * 60) });
```

Runs that are delayed and have not been enqueued yet will display in the dashboard with a "Delayed" status:

![Delayed run in the dashboard](/images/v3/delayed-runs.png)

<Note>
  Delayed runs will be enqueued at the time specified, and will run as soon as possible after that
  time, just as a normally triggered run would.
</Note>

You can cancel a delayed run using the `runs.cancel` SDK function:

```ts
import { runs } from "@trigger.dev/sdk/v3";

await runs.cancel("run_1234");
```

You can also reschedule a delayed run using the `runs.reschedule` SDK function:

```ts
import { runs } from "@trigger.dev/sdk/v3";

// The delay option here takes the same format as the trigger delay option
await runs.reschedule("run_1234", { delay: "1h" });
```

The `delay` option is also available when using `batchTrigger`:

```ts
await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" } }]);
```

### `ttl`

You can set a TTL (time to live) when triggering a task, which will automatically expire the run if it hasn't started within the specified time. This is useful for ensuring that a run doesn't get stuck in the queue for too long.

<Note>
  All runs in development have a default `ttl` of 10 minutes. You can disable this by setting the
  `ttl` option.
</Note>

```ts
import { myTask } from "./trigger/myTasks";

// Expire the run if it hasn't started within 1 hour
await myTask.trigger({ some: "data" }, { ttl: "1h" });

// If you specify a number, it will be treated as seconds
await myTask.trigger({ some: "data" }, { ttl: 3600 }); // 1 hour
```

When a run is expired, it will be marked as "Expired" in the dashboard:

![Expired runs in the dashboard](/images/v3/expired-runs.png)

When you use both `delay` and `ttl`, the TTL will start counting down from the time the run is enqueued, not from the time the run is triggered.

So for example, when using the following code:

```ts
await myTask.trigger({ some: "data" }, { delay: "10m", ttl: "1h" });
```

The timeline would look like this:

1. The run is created at 12:00:00
2. The run is enqueued at 12:10:00
3. The TTL starts counting down from 12:10:00
4. If the run hasn't started by 13:10:00, it will be expired

For this reason, the `ttl` option only accepts durations and not absolute timestamps.

### `idempotencyKey`

You can provide an `idempotencyKey` to ensure that a task is only triggered once with the same key. This is useful if you are triggering a task within another task that might be retried:

```typescript
import { idempotencyKeys, task } from "@trigger.dev/sdk/v3";

export const myTask = task({
  id: "my-task",
  retry: {
    maxAttempts: 4,
  },
  run: async (payload: any) => {
    // By default, idempotency keys generated are unique to the run, to prevent retries from duplicating child tasks
    const idempotencyKey = await idempotencyKeys.create("my-task-key");

    // childTask will only be triggered once with the same idempotency key
    await childTask.triggerAndWait(payload, { idempotencyKey });

    // Do something else, that may throw an error and cause the task to be retried
  },
});
```

For more information, see our [Idempotency](/v3/idempotency) documentation.

### `queue`

When you trigger a task you can override the concurrency limit. This is really useful if you sometimes have high priority runs.

The task:

```ts /trigger/override-concurrency.ts
const generatePullRequest = task({
  id: "generate-pull-request",
  queue: {
    //normally when triggering this task it will be limited to 1 run at a time
    concurrencyLimit: 1,
  },
  run: async (payload) => {
    //todo generate a PR using OpenAI
  },
});
```

Triggering from your backend and overriding the concurrency:

```ts app/api/push/route.ts
import { generatePullRequest } from "~/trigger/override-concurrency";

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

  if (data.branch === "main") {
    //trigger the task, with a different queue
    const handle = await generatePullRequest.trigger(data, {
      queue: {
        //the "main-branch" queue will have a concurrency limit of 10
        //this triggered run will use that queue
        name: "main-branch",
        concurrencyLimit: 10,
      },
    });

    return Response.json(handle);
  } else {
    //triggered with the default (concurrency of 1)
    const handle = await generatePullRequest.trigger(data);
    return Response.json(handle);
  }
}
```

### `concurrencyKey`

If you're building an application where you want to run tasks for your users, you might want a separate queue for each of your users. (It doesn't have to be users, it can be any entity you want to separately limit the concurrency for.)

You can do this by using `concurrencyKey`. It creates a separate queue for each value of the key.

Your backend code:

```ts app/api/pr/route.ts
import { generatePullRequest } from "~/trigger/override-concurrency";

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

  if (data.isFreeUser) {
    //free users can only have 1 PR generated at a time
    const handle = await generatePullRequest.trigger(data, {
      queue: {
        //every free user gets a queue with a concurrency limit of 1
        name: "free-users",
        concurrencyLimit: 1,
      },
      concurrencyKey: data.userId,
    });

    //return a success response with the handle
    return Response.json(handle);
  } else {
    //trigger the task, with a different queue
    const handle = await generatePullRequest.trigger(data, {
      queue: {
        //every paid user gets a queue with a concurrency limit of 10
        name: "paid-users",
        concurrencyLimit: 10,
      },
      concurrencyKey: data.userId,
    });

    //return a success response with the handle
    return Response.json(handle);
  }
}
```

### `maxAttempts`

You can set the maximum number of attempts for a task run. If the run fails, it will be retried up to the number of attempts you specify.

```ts
await myTask.trigger({ some: "data" }, { maxAttempts: 3 });
await myTask.trigger({ some: "data" }, { maxAttempts: 1 }); // no retries
```

This will override the `retry.maxAttempts` value set in the task definition.

## Large Payloads

We recommend keeping your task payloads as small as possible. We currently have a hard limit on task payloads above 10MB.

If your payload size is larger than 512KB, instead of saving the payload to the database, we will upload it to an S3-compatible object store and store the URL in the database.

When your task runs, we automatically download the payload from the object store and pass it to your task function. We also will return to you a `payloadPresignedUrl` from the `runs.retrieve` SDK function so you can download the payload if needed:

```ts
import { runs } from "@trigger.dev/sdk/v3";

const run = await runs.retrieve(handle);

if (run.payloadPresignedUrl) {
  const response = await fetch(run.payloadPresignedUrl);
  const payload = await response.json();

  console.log("Payload", payload);
}
```

<Note>
  We also use this same system for dealing with large task outputs, and subsequently will return a
  corresponding `outputPresignedUrl`. Task outputs are limited to 100MB.
</Note>

If you need to pass larger payloads, you'll need to upload the payload to your own storage and pass a URL to the file in the payload instead. For example, uploading to S3 and then sending a presigned URL that expires in URL:

<CodeGroup>

```ts /yourServer.ts
import { myTask } from "./trigger/myTasks";
import { s3Client, getSignedUrl, PutObjectCommand, GetObjectCommand } from "./s3";
import { createReadStream } from "node:fs";

// Upload file to S3
await s3Client.send(
  new PutObjectCommand({
    Bucket: "my-bucket",
    Key: "myfile.json",
    Body: createReadStream("large-payload.json"),
  })
);

// Create presigned URL
const presignedUrl = await getSignedUrl(
  s3Client,
  new GetObjectCommand({
    Bucket: "my-bucket",
    Key: "my-file.json",
  }),
  {
    expiresIn: 3600, // expires in 1 hour
  }
);

// Now send the URL to the task
const handle = await myTask.trigger({
  url: presignedUrl,
});
```

```ts /trigger/myTasks.ts
import { task } from "@trigger.dev/sdk/v3";

export const myTask = task({
  id: "my-task",
  run: async (payload: { url: string }) => {
    // Download the file from the URL
    const response = await fetch(payload.url);
    const data = await response.json();

    // Do something with the data
  },
});
```

</CodeGroup>

### Batch Triggering

When using `batchTrigger` or `batchTriggerAndWait`, the total size of all payloads cannot exceed 10MB. This means if you are doing a batch of 100 runs, each payload should be less than 100KB.

## Next.js Server Actions

Server Actions allow you to call your backend code without creating API routes. This is very useful for triggering tasks but you need to be careful you don't accidentally bundle the Trigger.dev SDK into your frontend code.

If you see an error like this then you've bundled `@trigger.dev/sdk/v3` into your frontend code:

```bash
Module build failed: UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins (Unhandled scheme).
Module build failed: UnhandledSchemeError: Reading from "node:process" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
```

When you use server actions that use `@trigger.dev/sdk/v3`:

- The file can't have any React components in it.
- The file should have `"use server"` on the first line.

Here's an example of how to do it with a component that calls the server action and the actions file:

<CodeGroup>

```tsx app/page.tsx
"use client";

import { create } from "@/app/actions";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <button
        onClick={async () => {
          const handle = await create();
          console.log(handle);
        }}
      >
        Create a thing
      </button>
    </main>
  );
}
```

```tsx app/actions.ts
"use server";

import { createAvatar } from "@/trigger/create-avatar";

export async function create() {
  try {
    const handle = await createAvatar.trigger({
      userImage: "http://...",
    });

    return { handle };
  } catch (error) {
    console.error(error);
    return {
      error: "something went wrong",
    };
  }
}
```

</CodeGroup>
