Docs
Guidesince cloud@2026-09-10

Deploy to Vercel

Two ways in: let a build or a function authenticate as the project it runs in, or sync values into Vercel's own environment from the console.

Vercel offers two ways in. Which one you need depends on whether anything of yours runs before your code does.

A build runs a command you choose, so penv can wrap it. A function is your code from the first line, so the values have to be in Vercel's own environment before it starts.

Path one: Vercel proves who it is

Vercel mints a signed token for a build or a function that says which project and which environment it is running in. penv exchanges that token for a credential and stores no key anywhere.

Open Machine Identities then Connect a Platform in the console and pick Vercel. Three fields.

FieldWhat to put in it
Team slugYour team's slug, such as acme.
ProjectThe project's name as Vercel shows it. A deployment URL will not match, and a rename means a new trust.
Environmentproduction or preview. Defaults to production, and sits under Advanced.

Set Project Settings, then Security, then Issuer Mode to Team before you connect. Global issuer mode shares https://oidc.vercel.com with every Vercel customer, so only the subject separates one workspace from another and a mistyped slug becomes cross-tenant rather than broken.

development is absent from the environment list on purpose. That token carries the person who ran it, so it identifies a human rather than a deployment.

What the trust holds:

PartValue
Issuerhttps://oidc.vercel.com/<team-slug>
Subjectowner:<team-slug>:project:<project>:environment:<environment>
AudienceYour workspace id

The subject is matched exactly, with no wildcards. Ask for this audience and no other. A token requested for two audiences fails every exchange.

Minting the token in a request handler

A custom audience is reachable only through @vercel/oidc, so the token is minted in JavaScript. This is the snippet the console prints, and it is a request handler: the code runs when a request arrives, never at module scope.

app/api/example/route.ts
// Runs in a build or a function's request handler, never at module scope.
// JavaScript only: a custom audience has no documented HTTP form.
import { getVercelOidcToken } from '@vercel/oidc';

export async function GET() {
  const T = await getVercelOidcToken({ audience: '<ORG_ID>' });
  const r = await fetch('https://penv.cloud/api/v1/auth/oidc', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ token: T }),
  });
  process.env.PENV_TOKEN = (await r.json()).credential;
}

The exchange answers 201 with a credential that lives fifteen minutes, capped by the trust's own expiry.

A handler holding a credential still needs something to spend it on, and penv ships no library for reading a value at request time. Where a function only needs its settings, take path two below.

Minting the token in a build

A build is the case where penv has a command to wrap. Make your build command a Node script that mints the token, exchanges it, and starts the real build with the credential in that child's environment.

scripts/penv-build.mjs
import { spawnSync } from 'node:child_process';
import { getVercelOidcToken } from '@vercel/oidc';

const T = await getVercelOidcToken({ audience: '<ORG_ID>' });
const r = await fetch('https://penv.cloud/api/v1/auth/oidc', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ token: T }),
});
const { credential } = await r.json();

const build = spawnSync('penv', ['run', '--', 'next', 'build'], {
  stdio: 'inherit',
  env: { ...process.env, PENV_TOKEN: credential },
});
process.exit(build.status ?? 1);

Set that file as the vercel-build script. A credential written to process.env in one process does not reach a sibling command, which is why the script starts the build itself.

The build image carries no penv, so install it in whatever step runs before the build and put $HOME/.penv/bin on the PATH. See install the CLI.

Vercel documents the custom audience option and no URL, method or body behind it, so a build in a runtime that is not JavaScript has no path to the token. Mint it in JavaScript and pass it into that command's environment as PENV_TOKEN.

With PENV_TOKEN set, penv run wraps your build command and puts the values in that one child process. penv pull writes a plain .env instead, for a tool that reads a file and nothing else.

Path two: sync values into Vercel's environment

The console writes your values into the project's own environment variables, and penv is absent at runtime. This is the path for functions.

Open Integrations in the console and connect Vercel. It needs a Vercel access token, minted at https://vercel.com/account/settings/tokens. A team account also needs the team id, which starts team_.

Then map one penv environment onto one Vercel target.

FieldWhat to put in it
ProjectThe project id or the project name.
Deploymentsproduction, preview or development.
Git branchPreview only. Pins the variables to one branch.
Custom environmentAn id starting env_, added to the deployments above.

penv verifies four things against the token before a sync runs, and a Vercel token carries no scopes, so these are probes rather than claims: it reads the account, lists the project, reads variable values for an import, and writes for an export. A probe that fails blocks the sync and says which one.

Vercel binds variables at build time. An existing deployment keeps the values it was built with, so a value you change reaches traffic on the next deploy. This is the difference that matters between the two paths: penv run picks up a rotated value on a restart, and an export sync needs a redeploy.

Export writes in one of three ways:

  • Create only leaves an existing Vercel variable alone.
  • Overwrite replaces it.
  • Overwrite and prune replaces it and deletes any Vercel variable your penv environment does not have.

Pick prune where penv is the only writer.

Which path to use

You are deployingUse
A build step, or anything you start with a commandPath one, then penv run
A function or an edge handlerPath two, the export sync
A tool that reads a file at build timePath one, then penv pull

Do it in order

  1. Set Issuer Mode to Team in Project Settings, then Security.
  2. Open Machine Identities, then Connect a Platform, and connect Vercel with the team slug and the project.
  3. Note the workspace id the console shows. It is the audience.
  4. Mint the token with getVercelOidcToken, exchange it, and put the credential in PENV_TOKEN.
  5. Wrap your build command in penv run, or write the file with penv pull.
  6. For a function, connect Vercel under Integrations instead, map the environment, and run a dry run before you apply.
  7. Redeploy. A synced value does not reach a deployment that was already built.

Serverless functions