Platform Template
The wall-to-wall reference for building an AI app builder on Vercel. Sandboxes, AI Gateway, deployments, Sign in With Vercel, and project transfers
The Platform Template is the wall-to-wall reference for building an AI app builder on Vercel. It covers every layer of the stack: running AI agents in sandboxes, routing LLM calls through AI Gateway, previewing apps live, deploying to Vercel, and transferring project ownership to users via Vercel Apps and claim deployments.
Check out the live demo at platform-template.labs.vercel.dev and the GitHub source
What This Covers
This template shows every Vercel platform primitive working together end-to-end:
- Vercel Sandbox - agents write code and run dev servers allowing for app previews
- AI Gateway - secure LLM access via OIDC, proxied from sandboxes that can't hold credentials
- Vercel Deployments - deploy sandbox contents to production with the Vercel SDK
- Vercel Apps - OAuth flow that installs an app in the users Vercel account during the claim flow allowing for continued updating
- Project Transfers - allow users to get their websites deployed and then later take ownership of them
Architecture Overview
The template is a Next.js application with five major subsystems:
How It Works
1. User sends a prompt
The user types a prompt in the chat interface. The frontend calls rpc.chat.send - a streaming oRPC procedure that orchestrates the entire flow.
2. Sandbox is created and set up
If this is a new session, a Vercel Sandbox is provisioned. The setup process installs bun, scaffolds the chosen template (Next.js, Vite, or TanStack Start), installs the agent CLI, and starts the dev server:
export async function* setupSandbox(
sandbox: Sandbox,
options: SetupOptions,
): AsyncGenerator<SetupProgress> {
yield { stage: "installing-bun", message: "Installing bun..." };
await run(sandbox, {
cmd: "sh",
args: ["-c", "curl -fsSL https://bun.sh/install | bash && ..."],
sudo: true,
}, "bun install");
// Run template-specific setup (create-next-app, create-vite, etc.)
for await (const progress of template.setup(sandbox)) {
yield { stage: progress.stage, message: progress.message };
}
// Install agent CLI and wait for dev server in parallel
yield { stage: "installing-agent", message: "Installing agent..." };
await Promise.all([agentInstallPromise, devServerPromise]);
yield { stage: "ready", message: "Sandbox ready" };
}3. Agent executes inside the sandbox
Native agent coding CLIs like Codex and Claude Codex are run inside of the sandbox
const env = {
ANTHROPIC_BASE_URL: proxyConfig.baseUrl, // Points to our proxy
ANTHROPIC_API_KEY: proxyConfig.sessionId, // Short-lived session ID
};
const cmd = await sandbox.runCommand({
cmd: "sh",
args: ["-c", `claude --print --verbose --output-format stream-json ...`],
cwd: SANDBOX_BASE_PATH,
env,
detached: true,
});
for await (const log of cmd.logs()) {
// Parse NDJSON and convert to unified StreamChunk format
}4. LLM calls go through AI Gateway
The agent CLI's base URL is configured to the platform's proxy route, which acts as a provider endpoint. The proxy validates the session, attaches a Vercel OIDC token, and forwards the request to AI Gateway.
If you wanted to implement things like tracking token spend per user this is where you're able to implement it:
// 1. Extract session ID from x-api-key or Authorization header
const data = await redis.get(`session:${sessionId}`);
if (!session) return new Response("Invalid session", { status: 401 });
// 2. Get OIDC token from the Vercel deployment
const gatewayToken = await getVercelOidcToken();
// 3. Forward to AI Gateway with only allowed headers
headers.set("authorization", `Bearer ${gatewayToken}`);
const response = await fetch(`${AI_GATEWAY_URL}${apiPath}`, {
method: request.method,
headers,
body: await request.arrayBuffer(),
});5. User previews and deploys
The sandbox dev server is exposed via an iframe. When the user clicks Deploy, the platform reads all files from the sandbox and creates a Vercel deployment:
const { client, teamId, ownership } = await getDeploymentClient(projectId);
const files = await readFilesForDeploy(sandbox, filePaths);
await vercel.deployments.createDeployment({
name,
files,
target: "production",
project: projectId ?? undefined,
});6. User claims the deployment
For signed-out users, the deployment lives on the partner team. The user can sign in with Vercel to claim it:
- Platform creates a transfer request via the Vercel API, getting a
transferCode - OAuth URL includes
transfer_code- Vercel transfers the project during authorization - Post-OAuth callback stores per-project tokens (JWE-encrypted) in Redis
- Future deploys use the stored tokens to deploy directly to the user's account
AI Gateway Proxy Pattern
Sandboxes cannot hold secrets. The proxy pattern solves this with short-lived capability tokens:
Security properties:
- Sandbox never sees the real OIDC token or any API key
- Proxy sessions are stored in Redis with a 1-hour TTL
- Only
accept,content-type, andanthropic-versionheaders are forwarded - CORS checks reject cross-origin requests
Deployment Authorization Model
The template implements three tiers of deployment authorization:
| Tier | When | Token Source | Deploys To |
|---|---|---|---|
| Partner | Signed-out user | VERCEL_PARTNER_TOKEN env var | Partner team |
| User | Signed-in user | OAuth session cookie | User's account |
| Project | Post-claim | Stored per-project tokens (Redis) | User's account |
Priority order: project tokens > user session > partner token. This means once a user claims a project, all subsequent deploys go to their account even if they're on a different browser session, because the platform stored the tokens during the claim flow.
Sign in With Vercel + Claim Flow
The claim flow combines OAuth authorization with project transfer in a single redirect:
- User clicks "Claim" on a partner-owned deployment
- Platform calls
projects.createProjectTransferRequest()to get atransferCode - User is redirected to Vercel OAuth with
transfer_codein the URL - User authorizes - Vercel transfers the project and returns an auth code
- Callback exchanges the code for tokens, stores them (JWE-encrypted) in Redis
- User is redirected back with
?sandboxId=xxxto restore their session
State recovery is seamless: the sandbox ID is encoded in the redirect URL, and usePersistedChat() restores messages, preview URL, and deployment state from Redis.
Getting Started
Step 1: Clone and Set Up
git clone https://github.com/vercel/platform-template
cd platform-template
pnpm installThe template is built with:
- Next.js 16 with App Router
- oRPC for type-safe streaming RPC
- Vercel Sandbox for secure code execution
- Vercel SDK for deployments and project management
- Zustand + SWR for state management
- Upstash Redis for session persistence
Step 2: Configure Environment Variables
# Required for AI Gateway proxy
PROXY_BASE_URL="http://localhost:3000/api/ai/proxy"
# Required for deployments (partner-tier)
VERCEL_PARTNER_TOKEN=""
VERCEL_PARTNER_TEAM_ID=""
# Required for Sign in With Vercel
VERCEL_CLIENT_ID=""
VERCEL_CLIENT_SECRET=""
SESSION_SECRET=""
# Required for session persistence
UPSTASH_REDIS_REST_URL=""
UPSTASH_REDIS_REST_TOKEN=""Step 3: Run the Development Server
pnpm devOpen http://localhost:3000 to see the platform. You'll see:
- Chat interface - describe what you want to build
- Agent selector - choose between Claude Code and Codex
- Template selector - pick Next.js, Vite, or TanStack Start
- Live preview - watch your app being built in real-time
- File explorer - browse generated files
- Deploy button - deploy to Vercel with one click
Step 4: Build Something
Try a prompt like:
"Build a dashboard with a sidebar navigation, a main content area showing some charts, and a dark mode toggle. Use shadcn/ui components."The platform will create a sandbox, set up the template, run the agent, and show you a live preview as the code is being written.
Key Patterns
Unified Stream Protocol
Both Claude Code and Codex produce vastly different output formats. The harness layer normalizes them into a single StreamChunk protocol that the UI consumes. This means adding a new agent (e.g., Gemini CLI, OpenCode) requires no UI changes - just implement the AgentProvider interface and parse the CLI output.
oRPC Generator Streaming
The RPC layer uses async function* generators for streaming. The client gets typed chunks as they're yielded:
export const sendMessage = os.input(schema).handler(async function* ({ input }) {
yield { type: "sandbox-id", sandboxId: sandbox.sandboxId };
for await (const progress of setupSandbox(sandbox, options)) {
yield events.sandboxStatus(sandbox.sandboxId, ...);
}
for await (const chunk of agent.execute({ prompt, sandboxContext, proxyConfig })) {
yield chunk;
}
});Template System
Each template implements a setup generator and provides framework-specific agent instructions:
interface Template {
id: TemplateId;
name: string;
devPort: number;
instructions: string; // System prompt for the agent
setup(sandbox: Sandbox): AsyncGenerator<SetupProgress>;
}Templates handle all scaffolding: create-next-app, create-vite, installing shadcn, configuring Tailwind, and starting the dev server. The agent receives framework-specific instructions so it generates idiomatic code.
The Platform Template demonstrates what's possible when you combine Vercel's infrastructure primitives - sandboxes, AI Gateway, deployments, and project transfers - into a cohesive product experience. It's the reference architecture for building platforms where AI writes code, users preview it live, and deploy it to production with a single click.