Skip to content

Commit 97c12e2

Browse files
authored
docs(management): document TriggerClient for multi-target SDK usage (#3694)
## Summary Docs follow-up for #3683 (`TriggerClient` for per-instance SDK configuration). Adds a dedicated reference page and threads the new pattern through the existing management + preview-branches docs. ## What's in **New page** `docs/management/multiple-clients.mdx` — when to use `TriggerClient` vs `configure()` vs `auth.withAuth`, env-var fallback rules, isolation contract, namespace surface, `inheritContext` opt-in, and a when-to-use-what table. **Updated pages** - `docs/management/authentication.mdx` — rewrote the `auth.withAuth` section to reflect the now-ALS-backed semantics (the prior version warned about concurrency races and pointed at issue #3298 as a tracked fix; that fix landed in #3683). Added `tr_preview_*` to the key prefix list. Reframed the multi-target use case to lead with `TriggerClient`, with `auth.withAuth` as the temporary-override helper. - `docs/management/overview.mdx` — added a `Multiple clients in one process` subsection. - `docs/deployment/preview-branches.mdx` — added a `Triggering across multiple branches from one process` example. - `docs/triggering.mdx` — one-liner pointing at the new page for cross-project triggering. - `docs/docs.json` — slotted `management/multiple-clients` into the Management API nav, right after authentication. Paired with #3683. ## Test plan - [ ] Mintlify preview renders cleanly - [ ] Code samples in each updated page run as documented - [ ] Cross-page links resolve (`/management/multiple-clients`, `/management/authentication`)
1 parent 5fab8ca commit 97c12e2

6 files changed

Lines changed: 149 additions & 12 deletions

File tree

docs/deployment/preview-branches.mdx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ 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+
const payload = { to: "user@example.com" };
86+
await Promise.all([
87+
signupFlow.tasks.trigger("send-email", payload),
88+
checkout.tasks.trigger("send-email", payload),
89+
]);
90+
```
91+
92+
See [Multiple SDK clients](/management/multiple-clients) for the full pattern.
93+
6994
## Preview branches with GitHub Actions (recommended)
7095

7196
This GitHub Action will:

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@
344344
"pages": [
345345
"management/overview",
346346
"management/authentication",
347+
"management/multiple-clients",
347348
"management/errors-and-retries",
348349
"management/auto-pagination",
349350
"management/advanced-usage"

docs/management/authentication.mdx

Lines changed: 15 additions & 12 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,15 @@ 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>
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`).
183181

184-
#### How scoping actually works
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.
185183

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.
184+
Unlike `TriggerClient` instances (which stay isolated unless you opt in), `auth.withAuth` keeps the surrounding task context: a call made inside a task still inherits `parentRunId`, version locking, and the test flag, the same as a direct SDK call. See the [isolation contract](/management/multiple-clients#isolation-contract).
187185

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

docs/management/overview.mdx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,22 @@ 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+
const payload = { to: "user@example.com" };
63+
await prod.tasks.trigger("send-email", payload);
64+
await preview.runs.list({ status: ["COMPLETED"] });
4765
```

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)