Build a v0-style app builder with OpenAI Agents API and Vercel Sandbox | Vercel Knowledge Base

Build an app that turns a prompt into a working Next.js application, then lets you refine the result through chat. Forma is a single-user app builder with saved projects, a conversation history, and a live preview you can pause and reopen later.

The OpenAI Agents API manages the coding agent's session and streams updates as it works. In this project, the agent connects to an executor in your own compute environment to edit files, install packages, and run commands.

Vercel Sandbox provides that environment: an isolated Linux workspace for each generated app. Its persistent file system lets you return to a project and continue editing the same code after its compute has stopped. It also provides a stable preview URL for the running app.

In this guide, you’ll learn how to make your own app builder like v0, understand its architecture, and deploy it to Vercel for everyone to use.

Deploy the template now, or follow the steps below to build it.

Quick start with an AI coding agent

Agent Prompt

Help me set up and deploy Forma, a v0-style app builder, from https://github.com/vercel-labs/openai-agents-api-v0-clone. Read README.md, ARCHITECTURE.md, AGENTS.md, and .env.example before making changes.

Use Node.js 24 and pnpm. Help me configure OpenAI Agents API preview access, an application key and a separate restricted executor key, a saved agent, Vercel Sandbox, and Neon Postgres. Explain how to obtain any missing credentials without putting secrets into chat or source files.

Run the app locally with pnpm dev, which starts Next.js and the database worker. For deployment, configure both Vercel Queue consumers, the five-minute Cron job, pnpm vercel-build, and the signed OpenAI webhook. Use separate production credentials and a production database or Neon branch.

Explain how a prompt becomes a background job, how the agent edits the sandbox workspace, and how the browser receives progress and displays the preview. Preserve each project's session and sandbox across follow-up edits. Help me verify generation, a follow-up edit, and Save & pause, followed by reopening the project.

The template repository contains the complete implementation. This guide focuses on the main integration points and the steps to run your own instance.

Vercel Plugin

Give your coding agent Vercel-specific guidance with the optional Vercel Plugin. It provides product context, skills, and commands for working with Vercel, including Next.js and deployment tasks. Install it in your coding agent's development environment. The plugin is optional.

Terminal

npx plugins add vercel/vercel-plugin

Prerequisites

Before you begin, make sure you have:

How it works

The builder and each generated app are separate Next.js applications. The builder is deployed on Vercel; the generated apps run inside Sandbox.

Integration walkthrough

1. Set up the Next.js app

Start with an App Router project using TypeScript and Tailwind CSS:

Terminal

pnpm create next-app@16.3.4 forma --ts --tailwind --eslint --app --use-pnpm --no-src-dir --import-alias '@/*'

cd forma

pnpm add @vercel/sandbox@3.2.1 @vercel/queue@0.5.1 openai@7.15.0 undici@7.29.1 pg zod lucide-react react-markdown

pnpm add -D @types/pg tsx

The template implements the builder interface as a Client Component with a prompt composer, saved project list, conversation, and preview panel. Route Handlers under app/api handle sign-in, project reads, and requests to edit, pause, or resume a project.

Keep the handlers small: validate the request and ownership, save a job, and return its identifier. Put database operations in lib/projects.ts, background execution in lib/worker.ts, and external service calls in their own modules. This lets the deployed Queue consumer and the local worker use the same execution logic.

The template also includes shared-password authentication and same-origin checks for browser mutations. Everyone who signs in shares the same workspace. For production workflows, you should set up sign-up and sign-in flows for individual users.

2. Configure the OpenAI Agents API

Create a saved agent with instructions to build Next.js apps in /workspace, preserve the managed preview server, and check its changes before finishing. The template's creation script selects gpt-5.6 and saves the returned identifier as OPENAI_AGENT_ID.

The application uses client.beta.agents from the OpenAI TypeScript SDK, which adds the OpenAI-Beta: agents=v1 header automatically. For a new project, client.beta.agents.sessions.create() sends the following body to POST https://api.openai.com/v1/agents/sessions:

{
  "agent_id": "<OPENAI_AGENT_ID>",
  "environment": {
    "type": "self_hosted",
    "workspace_directory": "/workspace"
  }
}

Save the returned session ID against the project. Start the Codex executor in that project's sandbox with --environment-id set to session.environment.id and --remote set to session.environment.remote_url. Save both values and reuse the returned URL unchanged when reconnecting. Subsequent prompts go to the same session, so the agent can continue working on the existing app.

Subscribe to the session's event stream before submitting a prompt, then save assistant text and progress as events arrive. Give each submission a stable job ID as its idempotency key so a retried request can refer to the same input.

3. Give each project a persistent sandbox

The agent needs a place to write source files, install dependencies, and run the generated app. Create a named Vercel Sandbox for each project so that those operations happen in an isolated workspace, and later edits can retrieve the same files.

For a project without an existing agent session, lib/sandbox.ts uses the following sandbox configuration:

const sandbox = await Sandbox.getOrCreate({
  name: project.sandbox_name,
  image: "vercel/sandbox/node:24",
  ports: [3000],
  persistent: true,
  timeout: 30 * 60_000,
  snapshotExpiration: 0,
  keepLastSnapshots: { count: 2, expiration: 0, deleteEvicted: true },
  networkPolicy: {
    allow: [
      "api.openai.com",
      "codex-cloud-environments.chatgpt.com",
      "registry.npmjs.org",
    ],
  },
});

On first use, prepare /workspace, install the executor, write the starter Next.js app, and install its packages. For a project with an existing agent session, use Sandbox.get({ name }). A missing saved workspace must surface an error instead of silently creating an empty replacement.

Persistent sandboxes snapshot their file system on stop and restore it on resume. Forma retains two snapshots without automatic expiration, so Save & pause stops compute while retained storage can still incur charges. Running processes are restarted when the project reopens.

Only the restricted executor key is passed into the sandbox. The application's OpenAI key, database connection string, and authentication secrets stay in the builder backend.

4. Store projects and jobs with Postgres

Connect a Neon Postgres database and use its pooled connection string as DATABASE_URL. The template uses pg for database access and keeps its repeatable schema in db/schema.sql.

The following four tables cover the application's state:

Table What it stores
projects Ownership, sandbox name, OpenAI session, preview URL, and current status.
jobs Edit, resume, and stop operations, including execution phase and worker lease.
messages User prompts, assistant responses, progress, and errors.
login_attempts Counters for shared-password login throttling.

When accepting an edit, lock the project and create its job, user message, and active-job pointer in one transaction. A repeated request ID returns the existing job. So, a new edit is rejected while another operation is active.

The worker claims a job with a time-limited lease. This gives duplicate queue deliveries a way to detect work already in progress. The database also preserves unfinished jobs if publishing to the queue fails.

5. Process background work with Vercel Queues

Configure two topics using Vercel Queues: studio-jobs for project operations and studio-provision for session reconciliation. The private consumers are declared in vercel.json:

{
  "functions": {
    "app/api/queues/jobs/route.ts": {
      "experimentalTriggers": [{ "type": "queue/v2beta", "topic": "studio-jobs" }]
    },
    "app/api/queues/provision/route.ts": {
      "experimentalTriggers": [{ "type": "queue/v2beta", "topic": "studio-provision" }]
    }
  }
}

After committing a job, publish its ID. The jobs consumer loads its state and calls the worker:

import { queue } from "@/lib/queue";

import { runJob } from "@/lib/worker";

export const maxDuration = 800;

export const POST = queue.handleCallback<{ jobId: string }>(
  async ({ jobId }) => runJob(jobId),
  { visibilityTimeoutSeconds: 900, retry: () => ({ afterSeconds: 30 }) },
);

OpenAI lifecycle notifications enter through /api/webhook. Verify the signature against the raw request body, then send the relevant session ID to studio-provision. Its consumer checks that the session belongs to a saved project before reconnecting an executor or handling a failed session.

6. Add recovery and idle cleanup with Vercel Cron

Add the Cron schedule alongside the Queue configuration in vercel.json:

{
  "crons": [{ "path": "/api/cron", "schedule": "*/5 * * * *" }]
}

You need the Vercel Pro or Enterprise plan to schedule cron jobs with a 5-minute frequency. To learn more about cron limits, see the Vercel documentation.

Every five minutes, /api/cron republishes unfinished jobs whose leases are absent or expired. It also enqueues stop operations for inactive projects with no active job and removes expired login-throttling records. The route checks the bearer token supplied through CRON_SECRET.

Projects become eligible for an idle shutdown after 10 minutes of inactivity. Shutdown happens after the next cron run and queue delivery. An active sandbox also has its own compute timeout.

7. Connect progress and previews to the UI

Start the generated Next.js development server on 0.0.0.0:3000 inside the sandbox. Wait for a successful HTTP response before saving sandbox.domain(3000) as the project's preview URL. The studio loads that URL in an iframe, and the development server updates the preview as source files change.

After the agent finishes, the worker runs a separate production build. The starter uses BUILD_CHECK=1 to write verification output to .next-build, leaving the live server's .next directory separate. A successful build and preview readiness check mark the project ready; a failed build appears in chat so the user can request a fix.

The browser connects to an SSE endpoint that reads saved project state and messages from Neon. Refreshing or closing the page leaves the background job running. Reconnecting restores the saved conversation and status.

The iframe restricts navigation and runs on a separate origin. Its URL is accessible to anyone who has it; the builder's password does not protect generated previews.

8. Journey of a request

The complete request flow is:

  1. A signed-in user submits a prompt. The app saves the edit job and publishes its ID for workers.
  2. The worker claims the job, prepares the sandbox, and creates or retrieves the project's agent session.
  3. The executor connects to OpenAI, and the worker submits the prompt while recording streamed progress.
  4. The agent edits the app. The preview updates, and the browser receives saved chat and status changes.
  5. The worker checks the build and marks the operation complete. A follow-up prompt repeats the flow with the same session and files.

Save & pause is available when no operation is active and creates a stop job. Reopening the project creates a resume job that restores the workspace and restarts its preview without sending a new coding prompt.

Set up locally

Clone the template and link it to your Vercel project:

Terminal

git clone https://github.com/vercel-labs/openai-agents-api-v0-clone.git

cd openai-agents-api-v0-clone

pnpm install

vercel link

Configure credentials

In the Vercel project's Storage tab, connect Neon through the Marketplace. Select a database or branch for development and connect it to the Development environment. Pull the environment variables and local Vercel authentication:

Terminal

vercel env pull .env.local

Use .env.example as the checklist for the remaining settings:

Variable How to obtain it
DATABASE_URL Use the Neon integration's pooled Postgres connection string for development.
OPENAI_API_KEY Create an application key in your OpenAI project with Agents API preview access.
OPENAI_EXECUTOR_API_KEY Create an environment key from the OpenAI dashboard’s Agents tab, under Environments → Keys.
OPENAI_AGENT_ID Run pnpm agent:create after setting the application key. The script saves the ID in .env.local.
OPENAI_WEBHOOK_SECRET Register a reachable /api/webhook endpoint in the OpenAI project's webhook settings and save the signing secret. The deployment steps below explain how to establish that endpoint.
APP_PASSWORD Choose a strong password for signing in to the shared workspace.
AUTH_SECRET Generate an independent random value for signing cookies and deriving workspace ownership.
CRON_SECRET Generate another independent random value for authenticating Cron requests.
VERCEL_OIDC_TOKEN Obtain it through vercel env pull; the Sandbox SDK uses it for local authentication.

You can generate a random secret with openssl rand -hex 32; run it separately for each secret. Keep .env.local out of version control. Leave APP_URL unset locally so requests use the local origin.

For the webhook, subscribe to agent.session.action_required and agent.session.failed. OpenAI provides the signing secret when you register the endpoint. If you don't have a deployed URL yet, complete the initial deployment below, then save the returned secret locally before starting generation.

The local worker can establish executor connections directly. A production webhook only reconciles sessions present in its own database; it does not process projects stored exclusively in a development branch.

Run the app

Once the environment is configured, create the saved agent, apply the schema, and start development:

Terminal

pnpm agent:create

pnpm db:migrate

pnpm dev

The agent script keeps an existing OPENAI_AGENT_ID. The development command starts both Next.js and the database worker; running Next.js by itself leaves local jobs unprocessed.

Open localhost:3000, sign in with APP_PASSWORD, and try:

Build a reading list app with book cards, a form to add a book, and filters for books I want to read and books I've finished.

Wait for the build check, then request a change such as adding a rating control. Refresh to confirm the conversation returns. Use Save & pause, reopen the project, and check that the generated code is still there. Data entered into the generated app needs its own storage if it should survive reloads.

If local Sandbox authentication expires, pull fresh variables into a separate temporary file and copy the new VERCEL_OIDC_TOKEN into .env.local, preserving your other settings. Restart pnpm dev after changing credentials.

Deploy the app

Before proceeding with production deployment, create a production database or Neon branch and a separate set of production credentials. Within each environment, the OpenAI application and executor keys must belong to the same organization and project. Use an agent accessible to that production OpenAI project and a webhook registered for the production endpoint.

In the Vercel project settings:

  1. Select Next.js as the framework and Node.js 24 as the runtime.
  2. Set the Build Command to pnpm vercel-build. It applies db/schema.sql to the deployment’s DATABASE_URL before building Next.js.
  3. Add the application settings from .env.example to the Production environment, using the production database and keys. Create the production agent with pnpm agent:create in a checkout configured with the API credentials, then copy the returned ID into Vercel.

Make the initial deployment to establish its URL:

Terminal

vercel deploy --prod

Generation remains unconfigured until the webhook secret is installed. In the production OpenAI project's webhook settings, register https://your-domain.example/api/webhook for agent.session.action_required and agent.session.failed. Add the returned OPENAI_WEBHOOK_SECRET to the Vercel project’s environment variables and redeploy.

Finally, sign in and verify a new project, a follow-up edit, and pause/resume. Inspect OpenAI's webhook delivery history and the provision consumer logs to confirm actual lifecycle events arrive.

Next steps