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
4 changes: 4 additions & 0 deletions .changeset/docs-external-vercel-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Document authenticating the Vercel World from outside a deployment (local scripts and CI) with a Vercel auth token.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ import { getHookByToken, resumeHook } from "workflow/api";

export async function POST(request: Request) {
const { token, data } = await request.json();
const expectedEnv = process.env.VERCEL_ENV || "development";
// On Vercel the platform sets these: `VERCEL_TARGET_ENV` carries a custom
// environment's slug, where `VERCEL_ENV` reports `preview`. Running against
// the Local World, hooks are recorded as `local`.
const expectedEnv =
process.env.VERCEL_TARGET_ENV ?? process.env.VERCEL_ENV ?? "local";

try {
const hook = await getHookByToken(token); // [!code highlight]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ import { getHookByToken, resumeHook } from "workflow/api";

export async function POST(request: Request) {
const { token, data } = await request.json();
const expectedEnv = process.env.VERCEL_ENV || "development";
// On Vercel the platform sets these: `VERCEL_TARGET_ENV` carries a custom
// environment's slug, where `VERCEL_ENV` reports `preview`. Running against
// the Local World, hooks are recorded as `local`.
const expectedEnv =
process.env.VERCEL_TARGET_ENV ?? process.env.VERCEL_ENV ?? "local";

try {
const hook = await getHookByToken(token); // [!code highlight]
Expand Down
4 changes: 3 additions & 1 deletion docs/content/docs/v5/configuration/cli-and-web-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The `workflow` CLI uses flags first, then environment variables, then defaults o

Vercel project and auth settings can often be inferred from `.vercel/project.json` and your Vercel CLI login.

Whichever way they are resolved, the CLI ends up building a Vercel World from an auth token plus a project ID, team ID, and environment — the same four values any external client needs. Set them explicitly when there is no interactive `vercel login` to infer from, or when writing your own script instead of using the CLI; see [Connecting from outside a deployment](/worlds/vercel#connecting-from-outside-a-deployment).

## Target backend

### `--backend` / `-b`
Expand Down Expand Up @@ -49,7 +51,7 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
- Environment variable: `WORKFLOW_VERCEL_ENV`
- Default: `production`
- Vercel environment for `--backend vercel`.
- Accepts `production` or `preview`.
- Accepts `production`, `preview`, or a custom environment's slug.

## Web UI

Expand Down
5 changes: 4 additions & 1 deletion docs/content/docs/v5/configuration/worlds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ The Vercel World is configured automatically inside Vercel deployments. The plat

Most applications should not set `WORKFLOW_VERCEL_*` variables on Vercel. They configure tooling that talks to a Vercel Workflow project from outside a deployment, such as the `workflow` CLI, the web UI, CI, or tests. The runtime warns if these variables are set in a deployed Vercel function because they do not control runtime configuration there.

The entries below are the individual knobs. For a walkthrough of authenticating a local script or a CI job with a Vercel auth token — which variables are needed together, and how to pass them to the World factory — see [Connecting from outside a deployment](/worlds/vercel#connecting-from-outside-a-deployment).

Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_DEPLOYMENT_KEY` are read by the runtime inside Vercel deployments. Do not set them yourself.

### `token`
Expand All @@ -200,13 +202,14 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
- CLI flag: `--authToken`
- Default: inferred when possible
- Vercel API token for external tooling. Keep it secret.
- Only meaningful alongside `projectConfig.projectId` and `projectConfig.teamId` — set all three together. Supplying both IDs but no token throws rather than failing later with a `401`.

### `projectConfig.environment`

- Environment variable: `WORKFLOW_VERCEL_ENV`
- CLI flag: `--env` or `-e`
- Default: `production`
- Vercel environment targeted by tooling. Accepts `production` or `preview`.
- Vercel environment targeted by tooling. Accepts `production`, `preview`, or a custom environment's **slug** — configure the slug, not the environment's ID.

### `projectConfig.projectId`

Expand Down
146 changes: 140 additions & 6 deletions docs/content/worlds/v4/vercel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ That's it. Vercel automatically:
- Selects the Vercel World backend
- Configures authentication using OIDC tokens
- Provisions storage and queuing infrastructure
- Isolates data per environment (production, preview, development)
- Isolates data per environment (production, preview, and any custom environments)

<FluidComputeCallout />

Expand Down Expand Up @@ -92,6 +92,137 @@ npx workflow inspect runs \

Learn more in the [Observability](/docs/observability) documentation.

## Connecting from outside a deployment

Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** instead. Vercel checks that token against your team and project permissions, and attributes the request to the environment you name.

<Callout type="warn">
Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into
`.env.local` as your Workflow credential. It is a local artifact of your
linked project, not a credential for the Workflow API: requests made with it
are attributed to the environment carried in the token itself, not to the
`production` or `preview` data you mean to reach. Authenticate with a Vercel
auth token instead. A pulled OIDC token is still the right thing for
[bypassing deployment
protection](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources)
when your script has to fetch a protected preview URL; that is a separate
credential from the one that talks to the Workflow API.
</Callout>

### Environment variables

These four values are what an external client needs. The names are the convention shared by the `workflow` CLI, the web UI, and Workflow's own CI:

| Variable | Value | Notes |
| --- | --- | --- |
| `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. |
| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required. Set it together with the token and the project ID. |
| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required. Set it together with the token and the team ID. |
| `WORKFLOW_VERCEL_ENV` | `production`, `preview`, or a custom environment's slug | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env). |

Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page.

Three more are situational:

- `VERCEL_DEPLOYMENT_ID` — required to **start** runs, not to read them. See [Starting runs](#starting-runs-from-outside-a-deployment).
- `WORKFLOW_VERCEL_PROJECT_NAME` — the project slug, used only to build dashboard links.
- `VERCEL_TOKEN` — a fallback the World reads for the Vercel API calls it makes alongside your Workflow requests (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it.

For a custom environment, set `WORKFLOW_VERCEL_ENV` to the environment's **slug**, not its ID.

<Callout type="info">
The Workflow SDK does not read `WORKFLOW_VERCEL_*` from the environment on
your behalf — the runtime's own `createWorld()` deliberately ignores them, so
that a stray variable on a deployed project cannot redirect a live
application. Read them in your own code and pass them to the World factory,
as below. Setting them on a deployed Vercel project has no effect on runtime
configuration, and the runtime warns when it sees them there.
</Callout>

### Wiring it up in a script

Build the Vercel World with an explicit config and install it with [`setWorld()`](/docs/api-reference/workflow-runtime/set-world) before the first `workflow/api` call:

{/*@skip-typecheck: incomplete code sample*/}

```typescript title="scripts/start-run.ts" lineNumbers
import { start } from "workflow/api";
import { setWorld } from "workflow/runtime";
import { createVercelWorld } from "@workflow/world-vercel";
import { myWorkflow } from "../workflows/my-workflow";

setWorld(
createVercelWorld({
token: process.env.WORKFLOW_VERCEL_AUTH_TOKEN, // [!code highlight]
projectConfig: {
projectId: process.env.WORKFLOW_VERCEL_PROJECT, // [!code highlight]
teamId: process.env.WORKFLOW_VERCEL_TEAM, // [!code highlight]
environment: process.env.WORKFLOW_VERCEL_ENV, // [!code highlight]
},
})
);

const run = await start(myWorkflow, ["input"]);
console.log(run.runId);
```

Supply `token`, `projectId`, and `teamId` together. Setting the two IDs without a token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`.

The same token authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption).

This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked.

### Starting runs from outside a deployment

[`start()`](/docs/api-reference/workflow-api/start) has to name a deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow:

```bash
# `vercel inspect` prints the dpl_... ID for a deployment URL
vercel inspect https://my-app-abc123.vercel.app
```

Reads such as [`getRun()`](/docs/api-reference/workflow-api/get-run) do not need it. `start(..., { deploymentId: "latest" })` does: it resolves the newest deployment sharing that deployment's environment, so it needs one to start from.

<Callout type="warn">
Keep `WORKFLOW_VERCEL_ENV` and the environment of the deployment named by
`VERCEL_DEPLOYMENT_ID` aligned. The run is created in the environment you
authenticate as, but executes the code of the deployment you name — a run
whose two halves land in different environments will not execute as
expected.
</Callout>

### In CI

Wait for the deployment under test, then hand its ID to the test step alongside the four auth variables. This is how Workflow's own end-to-end suite authenticates against its preview and production deployments:

```yaml title=".github/workflows/e2e.yml" lineNumbers
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Wait for the Vercel deployment
id: deployment
uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037
with:
project-slug: my-app
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}

- name: Run end-to-end tests
run: pnpm test:e2e
env:
WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_TOKEN }}
WORKFLOW_VERCEL_TEAM: team_xxxxxxxxxxxxxxxx
WORKFLOW_VERCEL_PROJECT: prj_xxxxxxxxxxxxxxxx
WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
VERCEL_DEPLOYMENT_ID: ${{ steps.deployment.outputs.deployment-id }}
```

Store the token as an encrypted secret and derive `WORKFLOW_VERCEL_ENV` from the branch, so a pull request run never authenticates as `production`.

If the deployment is behind deployment protection, the job also needs a way to reach its URL. Grant the workflow `id-token: write` and mint a short-lived OIDC token from the runner for the `x-vercel-trusted-oidc-idp-token` header; that credential is for [Trusted Sources](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) only and is separate from the Vercel auth token above.

## Testing & Compatibility

<WorldTestingPerformance worldId="vercel" />
Expand All @@ -100,9 +231,11 @@ Learn more in the [Observability](/docs/observability) documentation.

The Vercel World requires no configuration when deployed to Vercel. For advanced use cases, you can override settings programmatically via `createVercelWorld()`.

The `WORKFLOW_VERCEL_*` variables below configure tooling that talks to a Vercel Workflow project from outside a deployment. For the task-oriented version — which ones a local script or CI job needs, and how to hand them to the SDK — see [Connecting from outside a deployment](#connecting-from-outside-a-deployment).

### `WORKFLOW_VERCEL_ENV`

The Vercel environment to use. Options: `production`, `preview`, `development`. Automatically detected.
The Vercel environment to use. Options: `production`, `preview`, or a custom environment's slug. Default: `production`. See [Environment variables](#environment-variables) for how a custom environment is resolved.

### `WORKFLOW_VERCEL_AUTH_TOKEN`

Expand Down Expand Up @@ -134,17 +267,18 @@ Because it routes each run's flow invocations through a dedicated `maxConcurrenc

### Programmatic configuration

`createVercelWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself. `projectId` and `teamId` are IDs, not slugs. To install the result as the World your app uses, see [Wiring it up in a script](#wiring-it-up-in-a-script).

{/*@skip-typecheck: incomplete code sample*/}

```typescript title="workflow.config.ts" lineNumbers
import { createVercelWorld } from "@workflow/world-vercel";

const world = createVercelWorld({
token: process.env.WORKFLOW_VERCEL_AUTH_TOKEN,
baseUrl: "https://api.vercel.com/v1/workflow",
projectConfig: {
projectId: "my-project",
teamId: "my-team",
projectId: "prj_...",
teamId: "team_...",
environment: "production",
},
});
Expand Down Expand Up @@ -197,6 +331,6 @@ The Vercel World uses Vercel's infrastructure for workflow execution:

- **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and [end-to-end encryption](/docs/how-it-works/encryption)
- **Queuing** - Steps are distributed across serverless functions via [Vercel Queues](https://vercel.com/docs/queues) with automatic retries and [consumer function security](#consumer-function-security)
- **Authentication** - OIDC tokens provide secure, automatic authentication
- **Authentication** - OIDC tokens provide secure, automatic authentication inside deployments; clients outside one authenticate with a [Vercel auth token](#connecting-from-outside-a-deployment)

For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflows).
Loading