Skip to content

Commit 5b5e9af

Browse files
committed
docs(management): document TriggerClient for multi-target SDK usage
Adds a dedicated `Multiple SDK clients` page covering when to reach for `new TriggerClient({...})` vs `configure()` vs `auth.withAuth`, the env-var fallback rules, the isolation contract, and the curated namespace surface. Updates the existing management overview, authentication, preview branches, and triggering pages to surface the new pattern. Refreshes the `auth.withAuth` section: removes the stale concurrency warning and the reference to issue #3298, since the fix landed alongside TriggerClient. Adds the `tr_preview_*` key prefix to the example.
1 parent 80bb600 commit 5b5e9af

6 files changed

Lines changed: 137 additions & 14 deletions

File tree

docs/deployment/preview-branches.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ async function triggerTask() {
6666
}
6767
```
6868

69+
### Triggering across multiple branches from one process
70+
71+
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.
72+
73+
```ts
74+
import { TriggerClient } from "@trigger.dev/sdk";
75+
76+
const signupFlow = new TriggerClient({
77+
accessToken: process.env.TRIGGER_PREVIEW_KEY,
78+
previewBranch: "signup-flow",
79+
});
80+
const checkout = new TriggerClient({
81+
accessToken: process.env.TRIGGER_PREVIEW_KEY,
82+
previewBranch: "checkout-redesign",
83+
});
84+
85+
await Promise.all([
86+
signupFlow.tasks.trigger("send-email", payload),
87+
checkout.tasks.trigger("send-email", payload),
88+
]);
89+
```
90+
91+
See [Multiple SDK clients](/management/multiple-clients) for the full pattern.
92+
6993
## Preview branches with GitHub Actions (recommended)
7094

7195
This GitHub Action will:

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@
342342
"pages": [
343343
"management/overview",
344344
"management/authentication",
345+
"management/multiple-clients",
345346
"management/errors-and-retries",
346347
"management/auto-pagination",
347348
"management/advanced-usage"

docs/management/authentication.mdx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { configure, runs } from "@trigger.dev/sdk";
1919

2020
// Using secretKey authentication
2121
configure({
22-
secretKey: process.env["TRIGGER_SECRET_KEY"], // starts with tr_dev_ or tr_prod_
22+
secretKey: process.env["TRIGGER_SECRET_KEY"], // starts with tr_dev_, tr_prod_, or tr_preview_
2323
});
2424

2525
function secretKeyExample() {
@@ -159,9 +159,12 @@ await envvars.update("proj_1234", "preview", "DATABASE_URL", {
159159
});
160160
```
161161

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

164-
`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.
164+
A long-running process often needs to talk to more than one Trigger.dev target. There are two patterns:
165+
166+
- **`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.
167+
- **`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.
165168

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

177-
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`).
178-
179-
<Warning>
180-
Avoid `auth.withAuth` as a per-request authentication strategy on long-running servers. Use it
181-
only for sequential, non-overlapping scopes.
182-
</Warning>
183-
184-
#### How scoping actually works
185-
186-
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.
180+
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`).
187181

188-
A fix using async context isolation is tracked in [issue #3298](https://github.com/triggerdotdev/trigger.dev/issues/3298).
182+
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.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
title: Multiple SDK clients
3+
sidebarTitle: Multiple SDK clients
4+
description: Use TriggerClient to talk to multiple Trigger.dev projects, environments, or preview branches from a single process.
5+
---
6+
7+
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.
8+
9+
```ts
10+
import { TriggerClient } from "@trigger.dev/sdk";
11+
12+
const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY });
13+
const preview = new TriggerClient({
14+
accessToken: process.env.TRIGGER_PREVIEW_KEY,
15+
previewBranch: "signup-flow",
16+
});
17+
18+
await prod.tasks.trigger("send-email", payload);
19+
await preview.runs.list({ status: ["COMPLETED"] });
20+
```
21+
22+
## Configuration
23+
24+
`TriggerClient` accepts the same fields as `configure()`:
25+
26+
| Field | Description | Env-var fallback |
27+
| --------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
28+
| `accessToken` | Secret key (`tr_dev_*`, `tr_prod_*`, `tr_preview_*`) or personal access token (`tr_pat_*`). | `TRIGGER_SECRET_KEY`, then `TRIGGER_ACCESS_TOKEN` |
29+
| `previewBranch` | Preview branch name when using a `tr_preview_*` key. | `TRIGGER_PREVIEW_BRANCH`, then `VERCEL_GIT_COMMIT_REF` |
30+
| `baseURL` | Override the Trigger.dev API URL. Defaults to `https://api.trigger.dev`. | `TRIGGER_API_URL` |
31+
| `requestOptions`| Request-level options (retry policy, additional headers, etc.) — see the `ApiRequestOptions` type. ||
32+
33+
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.
34+
35+
```ts
36+
// Picks up TRIGGER_SECRET_KEY / TRIGGER_PREVIEW_BRANCH from env.
37+
const fromEnv = new TriggerClient();
38+
39+
// Explicit values override env entirely.
40+
const explicit = new TriggerClient({
41+
accessToken: process.env.OTHER_PROJECT_KEY,
42+
previewBranch: "feature-x",
43+
});
44+
```
45+
46+
If no `accessToken` resolves from either the constructor or env vars, the first API call throws an `ApiClientMissingError` with a clear message.
47+
48+
## What's on a TriggerClient instance
49+
50+
Each instance exposes the management surface as namespaced properties: `tasks`, `runs`, `batch`, `schedules`, `envvars`, `queues`, `deployments`, `prompts`, and `auth`.
51+
52+
```ts
53+
const client = new TriggerClient();
54+
55+
await client.tasks.trigger<typeof emailTask>("send-email", { to: "..." });
56+
await client.runs.list({ status: ["COMPLETED"], limit: 10 });
57+
await client.schedules.create({ task: "daily-report", cron: "0 9 * * *" });
58+
await client.envvars.update("proj_1234", "preview", "DATABASE_URL", { value: "..." });
59+
```
60+
61+
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`).
62+
63+
## Isolation contract
64+
65+
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.
66+
67+
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`:
68+
69+
```ts
70+
const sameProject = new TriggerClient({
71+
accessToken: process.env.TRIGGER_SECRET_KEY,
72+
inheritContext: true,
73+
});
74+
```
75+
76+
## When to use what
77+
78+
| Scenario | Recommended |
79+
| ------------------------------------------------------------------------- | ------------------------------------ |
80+
| Single process, single project/env | `configure()` (or env vars only) |
81+
| Single process talking to multiple projects, envs, or branches | `new TriggerClient({...})` per target |
82+
| Short, sequential override (e.g. one batch under a different token) | `auth.withAuth(config, fn)` |
83+
| Inside a task, trigger a run in a different project | `new TriggerClient({...})` |
84+
85+
See [Authentication](/management/authentication) for the underlying token types and the `auth.withAuth` helper.

docs/management/overview.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,21 @@ async function main() {
4444
}
4545

4646
main().catch(console.error);
47+
```
48+
49+
### Multiple clients in one process
50+
51+
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.
52+
53+
```ts
54+
import { TriggerClient } from "@trigger.dev/sdk";
55+
56+
const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY });
57+
const preview = new TriggerClient({
58+
accessToken: process.env.TRIGGER_PREVIEW_KEY,
59+
previewBranch: "signup-flow",
60+
});
61+
62+
await prod.tasks.trigger("send-email", payload);
63+
await preview.runs.list({ status: ["COMPLETED"] });
4764
```

docs/triggering.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Trigger tasks **from inside a another task**:
2929

3030
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).
3131

32+
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.
33+
3234
<Note>
3335
**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.
3436
</Note>

0 commit comments

Comments
 (0)