Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/deployment/preview-branches.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ async function triggerTask() {
}
```

### Triggering across multiple branches from one process

If a single process needs to trigger runs in several preview branches (or a mix of prod and preview), use `new TriggerClient({...})` for each target instead of mutating global config. Each instance owns its own auth and branch.

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

const signupFlow = new TriggerClient({
accessToken: process.env.TRIGGER_PREVIEW_KEY,
previewBranch: "signup-flow",
});
const checkout = new TriggerClient({
accessToken: process.env.TRIGGER_PREVIEW_KEY,
previewBranch: "checkout-redesign",
});

const payload = { to: "user@example.com" };
await Promise.all([
signupFlow.tasks.trigger("send-email", payload),
checkout.tasks.trigger("send-email", payload),
]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```
Comment thread
ericallam marked this conversation as resolved.

See [Multiple SDK clients](/management/multiple-clients) for the full pattern.

## Preview branches with GitHub Actions (recommended)

This GitHub Action will:
Expand Down
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
"pages": [
"management/overview",
"management/authentication",
"management/multiple-clients",
Comment thread
ericallam marked this conversation as resolved.
"management/errors-and-retries",
"management/auto-pagination",
"management/advanced-usage"
Expand Down
22 changes: 8 additions & 14 deletions docs/management/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { configure, runs } from "@trigger.dev/sdk";

// Using secretKey authentication
configure({
secretKey: process.env["TRIGGER_SECRET_KEY"], // starts with tr_dev_ or tr_prod_
secretKey: process.env["TRIGGER_SECRET_KEY"], // starts with tr_dev_, tr_prod_, or tr_preview_
});

function secretKeyExample() {
Expand Down Expand Up @@ -159,9 +159,12 @@ await envvars.update("proj_1234", "preview", "DATABASE_URL", {
});
```

### Scoped authentication with `auth.withAuth`
### Talking to multiple projects, environments, or branches

`auth.withAuth` runs a callback with a temporary API client configuration, then restores the previous configuration when the callback resolves or rejects. It's useful when a single process needs to make calls across multiple Trigger.dev projects or environments without mutating the global config manually.
A long-running process often needs to talk to more than one Trigger.dev target. There are two patterns:

- **`new TriggerClient({...})`** — an explicit instance that owns its own auth, baseURL, and preview branch. Use this when the targets are long-lived (a dashboard that watches prod + preview, a worker that triggers across multiple projects, etc.). Each instance is fully isolated and concurrent calls don't interfere. See [Multiple SDK clients](/management/multiple-clients) for details.
- **`auth.withAuth(config, fn)`** — runs a single callback under a temporary config override, then restores. Use this for short, sequential overrides (e.g. one batch under a different token) where keeping a dedicated client around is overkill.
Comment thread
ericallam marked this conversation as resolved.

```ts
import { auth, runs } from "@trigger.dev/sdk";
Expand All @@ -174,15 +177,6 @@ const projectBRuns = await auth.withAuth(
);
```

Any SDK call inside the callback uses the overridden token. Calls outside the callback continue to use whatever was set by `configure` (or picked up from `TRIGGER_SECRET_KEY`).

<Warning>
Avoid `auth.withAuth` as a per-request authentication strategy on long-running servers. Use it
only for sequential, non-overlapping scopes.
</Warning>

#### How scoping actually works

Despite looking block-scoped, `auth.withAuth` stores the overridden configuration in a process-wide global (not [AsyncLocalStorage](https://nodejs.org/api/async_context.html)). It saves the previous config, installs the new one globally, runs the callback, and restores the previous config in a `finally`. This means sequential, non-overlapping usage is safe, but concurrent usage is not — if two `auth.withAuth` calls overlap (for example inside `Promise.all` with different tokens, or across concurrent request handlers on a long-running server) both will share whichever configuration was installed most recently, and SDK calls in one scope can silently use the other scope's token.
Any SDK call inside the callback uses the overridden config. Calls outside the callback continue to use whatever was set by `configure` (or picked up from `TRIGGER_SECRET_KEY`).

A fix using async context isolation is tracked in [issue #3298](https://github.com/triggerdotdev/trigger.dev/issues/3298).
The override is scoped via [AsyncLocalStorage](https://nodejs.org/api/async_context.html), so concurrent `auth.withAuth` calls (including overlapping calls inside `Promise.all` with different tokens) do not interfere. Nested calls compose — an inner `auth.withAuth({ accessToken })` inside an outer `auth.withAuth({ baseURL })` runs with both fields applied.
Comment thread
ericallam marked this conversation as resolved.
Comment thread
ericallam marked this conversation as resolved.
88 changes: 88 additions & 0 deletions docs/management/multiple-clients.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: Multiple SDK clients
sidebarTitle: Multiple SDK clients
description: Use TriggerClient to talk to multiple Trigger.dev projects, environments, or preview branches from a single process.
---

The global `configure()` API binds the SDK to one set of credentials per process. When a single process needs to talk to more than one Trigger.dev project, environment, or preview branch, use `new TriggerClient({...})` for each target instead. Each instance owns its own auth, baseURL, and preview branch, and concurrent calls across instances stay isolated.

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

const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY });
const preview = new TriggerClient({
accessToken: process.env.TRIGGER_PREVIEW_KEY,
previewBranch: "signup-flow",
});

const payload = { to: "user@example.com" };
await prod.tasks.trigger("send-email", payload);
await preview.runs.list({ status: ["COMPLETED"] });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
ericallam marked this conversation as resolved.
```

## Configuration

`TriggerClient` accepts the same fields as `configure()`:

| Field | Description | Env-var fallback |
| --------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `accessToken` | Secret key (`tr_dev_*`, `tr_prod_*`, `tr_preview_*`) or personal access token (`tr_pat_*`). | `TRIGGER_SECRET_KEY`, then `TRIGGER_ACCESS_TOKEN` |
| `previewBranch` | Preview branch name when using a `tr_preview_*` key. | `TRIGGER_PREVIEW_BRANCH`, then `VERCEL_GIT_COMMIT_REF` |
| `baseURL` | Override the Trigger.dev API URL. Defaults to `https://api.trigger.dev`. | `TRIGGER_API_URL` |
| `requestOptions`| Request-level options (retry policy, additional headers, etc.) — see the `ApiRequestOptions` type. | — |

Fields not passed to the constructor fall back to the matching env var (and then to a sensible default for `baseURL`). Explicit constructor values always win, so you can mix env-var-backed clients and fully explicit clients in the same process.

```ts
// Picks up TRIGGER_SECRET_KEY / TRIGGER_PREVIEW_BRANCH from env.
const fromEnv = new TriggerClient();

// Explicit values override env entirely.
const explicit = new TriggerClient({
accessToken: process.env.OTHER_PROJECT_KEY,
previewBranch: "feature-x",
});
```

If no `accessToken` resolves from either the constructor or env vars, the first API call throws an `ApiClientMissingError` with a clear message.

## What's on a TriggerClient instance

Each instance exposes the management surface as namespaced properties: `tasks`, `runs`, `batch`, `schedules`, `envvars`, `queues`, `deployments`, `prompts`, and `auth`.

```ts
import type { emailTask } from "./trigger/email";

const client = new TriggerClient();

await client.tasks.trigger<typeof emailTask>("send-email", { to: "user@example.com" });
await client.runs.list({ status: ["COMPLETED"], limit: 10 });
await client.schedules.create({ task: "daily-report", cron: "0 9 * * *" });
await client.envvars.update("proj_1234", "preview", "DATABASE_URL", { value: "..." });
```

Methods that only make sense inside a running task are not on the instance surface: `tasks.triggerAndWait`, `tasks.batchTriggerAndWait`, `tasks.triggerAndSubscribe`, `batch.triggerAndWait`, `batch.triggerByTaskAndWait`, and the task-definition helpers (`schedules.task`, `prompts.define`).

## Isolation contract

When you make a call through a `TriggerClient` instance, the SDK does not look at the process-wide global config, env vars (other than the constructor-time fallback), or the ambient task context. Two instances pointing at different projects can run in the same process — including in parallel under `Promise.all` — without interfering with each other.

That isolation also means a call from inside a task does not automatically inherit the surrounding task's `parentRunId`, `lockToVersion`, or test flag. If you specifically want a call to inherit those (rare — usually you want a clean external trigger), opt in with `inheritContext: true`:

```ts
const sameProject = new TriggerClient({
accessToken: process.env.TRIGGER_SECRET_KEY,
inheritContext: true,
});
```
Comment thread
ericallam marked this conversation as resolved.
Comment thread
ericallam marked this conversation as resolved.

## When to use what

| Scenario | Recommended |
| ------------------------------------------------------------------------- | ------------------------------------ |
| Single process, single project/env | `configure()` (or env vars only) |
| Single process talking to multiple projects, envs, or branches | `new TriggerClient({...})` per target |
| Short, sequential override (e.g. one batch under a different token) | `auth.withAuth(config, fn)` |
| Inside a task, trigger a run in a different project | `new TriggerClient({...})` |

See [Authentication](/management/authentication) for the underlying token types and the `auth.withAuth` helper.
18 changes: 18 additions & 0 deletions docs/management/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,22 @@ async function main() {
}

main().catch(console.error);
```

### Multiple clients in one process

If a single process needs to talk to more than one Trigger.dev project, environment, or preview branch, use `new TriggerClient({...})` for each target instead of `configure()`. Each instance owns its own auth and config, with no shared global state. See [Multiple SDK clients](/management/multiple-clients) for the full pattern.

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

const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY });
const preview = new TriggerClient({
accessToken: process.env.TRIGGER_PREVIEW_KEY,
previewBranch: "signup-flow",
});

const payload = { to: "user@example.com" };
await prod.tasks.trigger("send-email", payload);
await preview.runs.list({ status: ["COMPLETED"] });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
ericallam marked this conversation as resolved.
```
2 changes: 2 additions & 0 deletions docs/triggering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Trigger tasks **from inside a another task**:

When you trigger a task from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable. If you're [using a preview branch](/deployment/preview-branches), you also need to set the `TRIGGER_PREVIEW_BRANCH` environment variable. You can find the value on the API keys page in the Trigger.dev dashboard. [More info on API keys](/apikeys).

If a single process needs to trigger across multiple projects, environments, or preview branches, use [`new TriggerClient({...})`](/management/multiple-clients) for each target instead of relying on the global env vars.
Comment thread
ericallam marked this conversation as resolved.

<Note>
**Which trigger pattern should I use?** If your triggering code can import the task definition (same codebase), use `yourTask.trigger()` for full type safety. Use `tasks.trigger()` with a type-only import when the task runs in a separate service or you need to avoid bundling task code into your app (common in Next.js). Both do the same thing at runtime.
</Note>
Expand Down