-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
docs(management): document TriggerClient for multi-target SDK usage #3694
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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() { | ||
|
|
@@ -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. | ||
|
ericallam marked this conversation as resolved.
|
||
|
|
||
| ```ts | ||
| import { auth, runs } from "@trigger.dev/sdk"; | ||
|
|
@@ -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. | ||
|
ericallam marked this conversation as resolved.
ericallam marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚩 auth.withAuth concurrency safety claim omits edge-runtime caveat The old docs explicitly warned that This is accurate for Node.js: the SDK imports Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| 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"] }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
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, | ||
| }); | ||
| ``` | ||
|
ericallam marked this conversation as resolved.
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. | ||
Uh oh!
There was an error while loading. Please reload this page.