All posts

How to Build a ChatGPT App: A Complete Technical Plan (April 2026)

Abe Wheeler
ChatGPT Apps MCP Apps MCP App Framework ChatGPT App Framework ChatGPT App Testing MCP App Testing Getting Started
How it feels to develop ChatGPT Apps (good).

How it feels to develop ChatGPT Apps (good).

Here’s the plan I’d follow to build a ChatGPT App, from the first commit through production. Skip the parts you’ve already figured out.

TL;DR: ChatGPT Apps are MCP Apps: React components (or any web app) rendered in sandboxed iframes inside ChatGPT conversations. Use sunpeak to scaffold the project, develop against the local inspector at localhost:3000, test with simulation files and Playwright, then deploy your MCP server and submit to the ChatGPT App Directory.

Understand the Architecture

Before writing code, get the core abstraction right. It makes everything easier.

A ChatGPT App (technically an MCP App) has two pieces:

  • Resources — your UI, rendered in a sandboxed iframe inside the ChatGPT conversation
  • Tools — API actions on your MCP server that trigger those resources

When you connect your app to ChatGPT (either via ngrok for development or a production URL), ChatGPT fetches your MCP server and pre-loads all declared resource bundles into sandboxed iframes. The resources are loaded at registration time, not on demand. This is why deploying a new version requires clicking Refresh in the Connector modal: ChatGPT has already pre-loaded the old bundle and needs to fetch the new one.

When a user has a conversation and ChatGPT decides to invoke your tool, the flow is:

  1. ChatGPT calls your tool and gets back structured data
  2. ChatGPT injects the tool’s output, the display mode (inline, fullscreen, or picture-in-picture), and the current theme into the already-loaded resource iframe
  3. Your UI renders with that data, the user interacts with it, and any follow-up actions go back through the model

Your app communicates with ChatGPT over JSON-RPC 2.0 messages sent through window.postMessage. The sandbox is strict: no access to the ChatGPT page, no reading the conversation, no arbitrary network requests beyond what you explicitly configure via CSP headers.

The conceptual hierarchy is: an App contains one or more Resources, and each Resource has one or more Tools. Get these boundaries right in your design before you start building. If you want a deeper look at how these MCP primitives work, see MCP Tools and Resources explained.

Choose Your Stack

The fastest path is sunpeak, an open-source MCP App framework. You’ll use:

You can build a ChatGPT App without a framework. The @modelcontextprotocol/sdk package gives you the underlying protocol. But then you need to build the local inspector yourself, set up your own testing infrastructure, figure out resource discovery, and handle the protocol details. Most teams don’t need that flexibility, and the dev loop without an inspector is painful because every change requires connecting to the real ChatGPT to see it. For a comparison of the options, see how to choose an MCP App framework.

If you already have an MCP server, sunpeak can add the UI layer. If you’re starting fresh, scaffold the whole thing with the CLI.

Set Up the Project

npx sunpeak new sunpeak-app
cd sunpeak-app

This gives you a project with TypeScript, React, Vite, and the MCP server configured. Your resource components go in src/resources/, simulation files go in tests/simulations/, and the build system knows how to find them.

Node.js 20+ required.

Develop Locally

Start the development environment:

pnpm dev

Two things start:

The inspector is the most important part of the workflow. It renders your components exactly as they’d appear in ChatGPT, with all display modes (inline, fullscreen, picture-in-picture), light and dark themes, and mock tool invocations. Changes hot-reload without a browser refresh.

You don’t need a ChatGPT account or any paid subscription during local development. Build and iterate entirely against the inspector.

Writing a Resource

A resource is a React component exported alongside a configuration object:

// src/resources/dashboard/dashboard.tsx
import { useToolData } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';

export const resource: ResourceConfig = {
  description: 'Analytics dashboard for your site',
};

export default function DashboardResource() {
  const { output } = useToolData<{ visits: number; conversions: number }>();

  return (
    <div>
      <h2>{output.visits.toLocaleString()} visits</h2>
      <p>{output.conversions} conversions</p>
    </div>
  );
}

The resource export tells sunpeak to register this as an MCP resource. useToolData gives you the typed output from the tool that triggered the render. For more on how data flows through the MCP App lifecycle, see the docs.

Writing a Tool

The Tool is what ChatGPT calls to trigger the resource. sunpeak auto-discovers tools from src/tools/*.ts:

// src/tools/get-dashboard.ts
import { z } from 'zod';
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
export const tool: AppToolConfig = {
  resource: 'dashboard',
  title: 'Get Dashboard',
  description: 'Show analytics dashboard for a given time range',
  annotations: { readOnlyHint: true },
};

export const schema = {
  timeRange: z.string().describe('Time range for the dashboard (e.g. "7d", "30d")'),
};

type Args = z.infer<z.ZodObject<typeof schema>>;

export default async function (args: Args, _extra: ToolHandlerExtra) {
  return {
    structuredContent: { visits: 4218, conversions: 83 },
  };
}

The tool references the resource by name string, connecting the two. The schema defines the tool’s input parameters with Zod, and the default export is the handler that returns structuredContent for the resource to render. sunpeak wires up the MCP server registration, resource discovery, and host communication automatically.

Making Your App Interactive

If your app needs to maintain state across user interactions (not just display static tool output), use the useAppState hook. This lets the user interact with your UI, and those interactions persist through the conversation. For example, a form where the user fills in fields, a dashboard with filters, or a multi-step wizard.

Test Your App

MCP Apps need a testing approach that accounts for the host environment, because your UI depends on tool data pushed from outside, and it needs to work across display modes and themes. You can’t test this by opening index.html.

sunpeak’s answer is simulation files: JSON files that define deterministic UI states.

{
  "tool": "get_dashboard",
  "toolInput": { "timeRange": "7d" },
  "toolResult": { "visits": 4218, "conversions": 83 },
  "userMessage": "Show me last week's dashboard"
}

These feed three levels of testing:

  1. Unit tests (Vitest): Render components against simulation data, assert on specific elements
  2. End-to-end tests (Playwright): Run the full inspector, interact with the UI like a real user
  3. Manual validation: Connect to the real ChatGPT once you’re confident in the automated tests
pnpm test          # Unit + e2e tests
pnpm test:visual   # E2E + visual regression tests

Both run in CI without a live AI host, so you get automated coverage on every push. You can also add visual regression testing to catch unintended layout changes across themes and display modes, and wire everything into a GitHub Actions pipeline for continuous integration.

For a full walkthrough of all testing levels, see the complete guide to testing ChatGPT Apps.

Connect to the Real ChatGPT

When you’re ready to validate against the actual product, you need:

  • A ChatGPT Business, Enterprise, or Edu plan
  • Developer mode enabled (Settings > Apps & Connectors > Advanced settings)

Create a tunnel so ChatGPT can reach your local server:

ngrok http 8000

Copy the ngrok URL, add /mcp at the end, and register it in ChatGPT under Settings > Apps & Connectors > Create.

Now you’re running in the real ChatGPT. A few things to know:

HMR works in the real ChatGPT too. sunpeak’s dev MCP server registers a resource that connects back to the local Vite HMR server, so the pre-loaded iframe has a live connection to your dev environment. Save a file, the UI updates in ChatGPT without clicking Refresh. You only need to Refresh the Connector when you change the MCP server itself, like adding or removing tools, changing resource names, or modifying server configuration.

The runtime has more than the docs cover. The official API documentation covers about two-thirds of what’s actually available. sunpeak exposes the full surface through its hooks, so you don’t need to dig into undocumented globals yourself.

Deploy

When you’re done building:

pnpm build

Deploy the built resource bundles with your production MCP server. The pnpm build command creates production-ready files in the dist/ directory that your MCP server will serve.

Your MCP server needs to run somewhere: a VPS, a serverless function, or any Node.js host. The built resource files are deployed alongside your MCP server. For a complete walkthrough of deployment options, see how to deploy an MCP App.

Submit to the ChatGPT App Directory

Once your app is deployed and publicly accessible, you can submit it to the ChatGPT App Directory so all ChatGPT users can install it. You’ll need a verified OpenAI developer account with the Owner role, and you’ll submit through the OpenAI Developer Platform with your MCP server URL, app metadata (name, description, logo, screenshots), test prompts, and a privacy policy URL. Apps go through automated scans and manual review before they’re listed.

The Build Order That Works

Given the architecture, this sequence gets you to a working app fastest:

  1. Define your data shape. What does your tool return? What does the component need to render? Get this typed before building anything.
  2. Write simulation files. One file per meaningful UI state: happy path, empty state, error state, edge cases.
  3. Write the Resource component. Build against simulation files in the local inspector. Get it looking right before you worry about the real tool.
  4. Write tests. Vitest for component-level assertions, Playwright for user interactions.
  5. Wire up the real tool. Connect your resource to an actual MCP tool and validate end-to-end in the inspector.
  6. Connect to ChatGPT. Once the inspector tests pass, validate in the real ChatGPT with ngrok.
  7. Deploy and submit.

Step 3 before step 5 is the key one. Build the UI against fake data first. It’s much faster to iterate on a component without a live backend dependency, and simulation files give you repeatable test coverage you can’t get by testing against live ChatGPT.

Cross-Host Portability

One thing worth planning for: your app doesn’t have to be ChatGPT-only. The MCP App standard (ext-apps specification, stable since January 2026) runs in ChatGPT, Claude, VS Code, Goose, Postman, and MCPJam as of April 2026. If you build against the standard rather than ChatGPT’s proprietary API, you get those other hosts for free.

sunpeak’s core hooks (useToolData, useHostContext, useDisplayMode) work on every MCP host. ChatGPT-specific features are available through sunpeak/chatgpt imports, separate from your portable base code. For a guide on targeting multiple hosts from one codebase, see building one MCP App for ChatGPT and Claude.

Get Started

Documentation →
npx sunpeak new

Further Reading

Frequently Asked Questions

What is the best way to build a ChatGPT App?

Use sunpeak, an open-source MCP App framework. Run "npx sunpeak new" to scaffold a project with TypeScript, React, Vite, and a local inspector configured. Develop against the inspector at localhost:3000 without needing a paid ChatGPT account. When ready for production, connect to the real ChatGPT with ngrok or deploy to your own server.

What is the architecture of a ChatGPT App?

ChatGPT Apps (MCP Apps) consist of Resources (UI views rendered in sandboxed iframes) and Tools (API actions that trigger them). When ChatGPT calls a tool, the host loads the corresponding resource into an iframe inside the conversation. The app communicates with the host over JSON-RPC 2.0 via window.postMessage, receiving tool output data, display mode, and theme context.

Do I need a paid ChatGPT subscription to develop a ChatGPT App?

No. You can build and test a ChatGPT App entirely offline using sunpeak's local inspector at localhost:3000. A ChatGPT account with developer mode enabled (available on Business, Enterprise, and Edu plans) is only needed when you want to connect your app to the real ChatGPT for final integration testing.

What framework should I use to build a ChatGPT App?

sunpeak is the most complete ChatGPT App framework available. It provides a local inspector, 20+ typed React hooks, pre-built UI components, simulation files for deterministic testing, a built-in testing framework for unit and end-to-end testing, and CLI tools for scaffolding and deployment. It targets the MCP App standard, so your app also works on Claude, VS Code, Goose, Postman, and MCPJam.

How do I test a ChatGPT App?

Use three testing levels: (1) simulation files that define deterministic UI states with tool inputs and outputs, (2) Vitest unit tests that render components against those simulations, and (3) Playwright end-to-end tests that run against the sunpeak local inspector. All three run in CI without a live ChatGPT connection. For final validation, test manually against the real ChatGPT with ngrok.

How do I connect a ChatGPT App to the real ChatGPT?

Run your MCP server with "pnpm dev", create a tunnel with ngrok ("ngrok http 8000"), then add the ngrok URL with /mcp path in ChatGPT under Settings > Apps & Connectors > Create. You need a ChatGPT Business, Enterprise, or Edu plan with developer mode enabled. When you deploy a new version, click Refresh in the Connector modal to clear cached resources.

How do I deploy a ChatGPT App to production?

Run "pnpm build" to create production bundles, then deploy the built files with your production MCP server to any Node.js host. To make your app available to all ChatGPT users, submit it to the ChatGPT App Directory through the OpenAI Developer Platform with your publicly accessible MCP server URL, metadata, and a privacy policy.

What programming languages and frameworks can I use to build a ChatGPT App?

ChatGPT Apps are web applications that run in sandboxed iframes, so you can use any HTML, CSS, and JavaScript. React is the most common choice because of the sunpeak framework, but vanilla JavaScript, Vue, Svelte, or any other framework works. The MCP server itself can be written in any language that supports the protocol, though TypeScript with sunpeak is the fastest path.

Can I build one ChatGPT App that works on other AI hosts too?

Yes. ChatGPT Apps are built on the MCP Apps standard (ext-apps specification, stable since January 2026). The same MCP server and UI resources run in ChatGPT, Claude, VS Code, Goose, Postman, and MCPJam. A portable framework like sunpeak abstracts host differences through shared hooks, so your core code works everywhere without modification.