A ChatGPT app isn’t a normal web app. It’s a Next.js application that ChatGPT calls over MCP and renders inside its own iframe, which means the distance between “hello world” and “a widget that actually displays in ChatGPT” is a minefield of undocumented browser quirks. Your static assets 404 because Next.js resolves them against the wrong origin. Client-side navigation breaks on cross-origin preflight. The ChatGPT host mutates your HTML before React hydrates, so you get hydration errors. None of this is in a tidy guide; you find it the hard way.
I found it the hard way. OpenAI’s widget sandbox is fragile and rule-bound, and a lot of my time went to CORS, asset-prefix, and fetch-origin-rewriting issues that have no obvious fix. This template is that knowledge solidified into a repo you can clone and have a working ChatGPT app running in minutes, instead of spending a day fighting the host environment before you write a single line of your actual product. I didn’t extract it from a shipped app. I built it deliberately as a public, reusable base so that the day I do want a ChatGPT app, the painful part is already done. It’s open for anyone to clone.
What it actually solves
The value isn’t the example app; it’s the plumbing around it, every piece of which exists because it was a problem the first time:
- An MCP server with the right OpenAI metadata.
app/mcp/route.tsregisters tools and widget resources with the OpenAI-specific fields ChatGPT looks for (openai/outputTemplate, thetoolInvocationloading/loaded states,resultCanProduceWidget) and cross-links a tool to its widget viatemplateUriso a tool call can actually produce a rendered UI. - A widget that lives inside ChatGPT. The root page reads structured tool output through the Apps SDK hooks and renders it as interactive cards, supports fullscreen mode, and degrades to a helpful banner when someone opens it outside ChatGPT.
- The asset-prefix fix. A one-line
assetPrefixconfig that stops Next.js from trying to load/_next/assets from the iframe URL: the difference between a styled widget and a wall of 404s. This was the single biggest time-sink for me; CSS just silently refused to load until I tracked down that the assets were resolving against the wrong origin. - CORS for React Server Components. Middleware that answers the preflight requests RSC fires during client-side navigation, which otherwise silently break.
- A browser bootstrap for the iframe.
<NextChatSDKBootstrap>patches the quirks of running inside the ChatGPT host:history.pushState/replaceState,fetchorigin rewriting, and the<html>attribute mutations the host injects before hydration. - Vercel-aware base URLs.
baseUrl.tsauto-detects Vercel’s production and preview environment variables so assets and runtime fetches resolve correctly across every deployment.
Why Next.js, when OpenAI recommends plain React
A ChatGPT app doesn’t have to be a Next.js app; OpenAI’s own starter leans toward plain React. I went Next.js on purpose, for a reason that’s really the whole thesis of the project: a ChatGPT app is a web app that happens to live inside ChatGPT, and I wanted it to be able to leave. Building on the framework I use for everything means this isn’t a dead-end widget; it’s the seed of a proper Next.js application. The same codebase can grow real pages and routes, deploy to Vercel as a standalone site, and let a user expand out of the ChatGPT iframe to the actual website. Most templates get you a widget; this one gets you a widget that can become a product.
A concrete example, not abstract SDK code
Rather than ship empty scaffolding, the template includes a small Doctor Finder app: a find_doctor MCP tool searches mock data by symptom or specialty, returns structured output plus the widget templateUri, and ChatGPT renders the results as doctor cards in the iframe. It makes the end-to-end flow from tool call to structured result to rendered widget legible at a glance, and it’s built to be torn out: swap the sample domain model and UI for your own tools, whether that’s commerce, scheduling, an ops dashboard, or an internal assistant.
It’s Next.js 15 and React 19 on Bun, Tailwind CSS 4, Apache-2.0 licensed, and deploys cleanly to Vercel. Connect the /mcp URL as a ChatGPT connector and you’re live. It’s also the practical companion to my deeper Apps SDK work in Agentbox, where I reverse-engineered the same SDK’s file model to move data out of ChatGPT.
