OpenAI Apps SDK Guide: Build a ChatGPT App That Still Holds Up in 2026
A practical OpenAI Apps SDK guide covering MCP Apps compatibility, public /mcp setup, Developer Mode testing, and what changes when you submit for review.
The OpenAI Apps SDK is the layer that turns an MCP server into a real ChatGPT app. In practice, that means you define tools on your server, attach a UI resource that ChatGPT can render in an iframe, and test the full flow in Developer Mode before you think about directory submission. The current OpenAI docs still label the Apps SDK as preview, but app submissions are open through the dashboard flow, so the practical question is no longer "is this real?" It is "which parts should I build against the open MCP Apps standard, and which parts should stay ChatGPT-specific?"
The short answer is: build the core app around MCP first, use ChatGPT-specific extensions only when they add clear value, and treat Developer Mode as your real test harness. If you want the broader category context first, start with What Are ChatGPT Apps?.
What the Apps SDK actually adds
OpenAI's October 6, 2025 launch post positions the Apps SDK as the way to build apps that live inside ChatGPT, and the current Help Center guide still describes it as a preview toolkit built on MCP.
That wording matters. The Apps SDK is not a replacement for MCP. It is the ChatGPT-facing layer around it.
| Layer | Job | What you build against |
|---|---|---|
| MCP server | Defines tools, auth, data access, and tool results | Tool descriptors, handlers, auth, transport |
| MCP Apps standard | Defines how the UI resource talks to the host | _meta.ui.resourceUri, ui/*, tools/call |
| ChatGPT runtime | Adds ChatGPT-specific UI features and distribution flow | window.openai, Developer Mode, submission |
The current MCP Apps compatibility guide
is explicit about the recommendation: use the MCP Apps standard keys and bridge
first, then layer on window.openai only when you need ChatGPT-specific
capabilities.
flowchart LR U["User prompt"] --> M["ChatGPT model"] M --> T["MCP tool call"] T --> S["Your MCP server"] S --> R["structuredContent + content + _meta"] R --> I["UI iframe via MCP Apps bridge"] I --> M
That separation is the clean mental model. The server owns business logic. The UI resource owns rendering. The model decides when to call the tool and how to narrate the result.
Build against the standard first
If you are starting a new app in 2026, the safest default is:
- Attach UI with
_meta.ui.resourceUri. - Communicate through the
ui/*JSON-RPC bridge overpostMessage. - Call tools through the MCP tool surface.
- Add ChatGPT-only extensions later, behind feature detection.
That is straight from the current compatibility guide and UI guide.
The split looks like this:
| Need | Standard path | ChatGPT-only path |
|---|---|---|
| Link a tool to a UI | _meta.ui.resourceUri | _meta["openai/outputTemplate"] compatibility alias |
| Receive tool results | ui/notifications/tool-result | window.openai.toolOutput |
| Call a tool from the UI | tools/call | window.openai.callTool |
| Send follow-up context | ui/message / ui/update-model-context | window.openai.sendFollowUpMessage / setWidgetState |
You can still support the OpenAI aliases. In fact, the current OpenAI docs say
ChatGPT honors openai/outputTemplate as a compatibility alias. But if you
lead with the standard form, your UI is easier to move and easier to reason
about.
The mistake I see most often is treating window.openai like the foundation.
It is not. It is a compatibility layer plus an extension surface.
A minimal build path that still scales
The current server guide reduces the build flow to a few core steps:
- Register an HTML UI resource with the MCP Apps MIME type.
- Define tools with clean names, input schemas, and UI metadata.
- Return
structuredContent, optional textcontent, and widget-only_meta. - Expose a public
/mcpendpoint over HTTPS before testing in ChatGPT.
That endpoint detail matters. The current Connect from ChatGPT guide
is explicit that ChatGPT wants the public /mcp URL of your server, not just a
homepage domain. The current Help Center beta article also says local MCP
servers are not supported in ChatGPT, so a localhost-only setup is fine for
Inspector but not enough for the real ChatGPT connection.
This is the part worth getting right early:
const TEMPLATE_URI = "ui://widget/orders.html";
registerAppTool(
server,
"list_orders",
{
title: "List orders",
description:
"Use this when the user wants current order data filtered by status.",
inputSchema: { status: z.enum(["open", "shipped"]) },
_meta: {
ui: { resourceUri: TEMPLATE_URI },
},
},
async ({ status }) => {
const orders = await fetchOrders(status);
return {
structuredContent: {
orders: orders.map((order) => ({
id: order.id,
customer: order.customer,
total: order.total,
status: order.status,
})),
},
content: [
{
type: "text",
text: `Showing ${orders.length} ${status} orders.`,
},
],
_meta: {
ordersById: Object.fromEntries(
orders.map((order) => [order.id, order]),
),
},
};
},
);The important part is not the syntax. It is the contract:
structuredContentis the model-readable data.contentis the optional narration._metais for the widget, not the model.
That three-part split is easy to gloss over, but it is one of the cleanest parts of the SDK. Keep it clean and your app gets easier to debug.
The current UI guide adds one more practical recommendation that is worth following: split data-processing tools from render tools when the UI is heavy. In other words, let one tool fetch or compute, and a separate tool own the widget template.
Why? Because attaching a widget to every tool call makes the iframe rerender too often. The model is usually better off seeing the data first and deciding when a UI is actually useful.
OpenAI's newer Define tools and Optimize metadata guides make the next step just as clear: treat tool metadata as part of the build, not a last-minute copy pass. Draft the name, description, parameter annotations, and hints early, then replay a small direct/indirect/negative prompt set in Developer Mode after every metadata change. That is how you catch misrouting before review.
Want the shortest path to a first working app?
If you want to learn the code-first path end to end, read Build Custom ChatGPT Tools with MCP next. If you already have a running server and only need the ChatGPT setup flow, How to Add MCP Tools to ChatGPT is the shorter route.
If your real goal is to skip the server scaffolding and publish a working MCP endpoint faster, drio's Quickstart and Connect to AI clients cover the builder path directly.
When window.openai is actually the right tool
The current compatibility guide
and UI guide
both make the same point: use window.openai for optional power, not for your
baseline architecture.
Today that mostly means ChatGPT-specific features such as:
- file upload and file-library access
- host-owned modals
- display mode changes like fullscreen
- Instant Checkout when enabled
Those are good reasons to use window.openai. They are not good reasons to
build your whole UI around ChatGPT-only assumptions.
The safer rule is simple:
- If a capability exists in the MCP Apps bridge, use the bridge.
- If it does not, feature-detect
window.openaiand degrade cleanly.
That keeps your app portable without pretending every host is identical.
Learn from Pizzaz without cloning it blindly
OpenAI's current examples page still points developers to the Pizzaz demo and the examples repository as the main reference implementation. The more useful detail is on the current ChatGPT UI guide, which explains what those examples are actually good for.
The current Pizzaz component gallery covers a few distinct UI patterns:
- list views with ranked cards and actions
- carousels for media-heavy layouts
- map-based browsing
- album or detail views
- video-oriented experiences
That is the right way to use the examples. Do not clone them because they are "official." Copy the pattern that matches your output shape, then replace the demo data and trim the surface area.
The examples are also good for a second reason: they show how the tool result, host bridge, and iframe state fit together in a real app, which is usually more valuable than reading a reference page in isolation.
Developer Mode is the real test harness
The current Connect from ChatGPT guide and Developer Mode guide make the development flow pretty explicit:
- Turn on Developer Mode in ChatGPT settings.
- Create an app or connector from your remote MCP server.
- Review the discovered tools.
- Test in a new conversation.
- Refresh metadata when you change tool descriptions or availability.
Three details matter more than they first appear.
First, the server URL is specific. The current connect guide asks for the
public /mcp endpoint of your server. If your deployed app lives at
https://example.com/mcp, that full path is what ChatGPT expects.
Second, remote-only still means remote-only. The current docs recommend tunneling localhost during development, but the Help Center beta article says local MCP servers are not supported in ChatGPT itself. The thing ChatGPT connects to must still be a public HTTPS endpoint.
Third, plan and capability gating are not one layer. The current Developer Mode guide says the beta is available on the web for Pro, Plus, Business, Enterprise, and Education accounts. The Help Center beta article adds a more important nuance: Pro users can connect custom apps with read/fetch permissions in Developer Mode, while full MCP write access is currently limited to Business and Enterprise/Edu. The same Help Center article also says search and fetch are no longer required for connected servers, agent mode will not use custom apps, and deep research can use them only for read/fetch.
The current Developer Mode guide also confirms a few operational details that are easy to miss:
- supported transports are SSE and streaming HTTP
- auth modes include OAuth, No Authentication, and Mixed Authentication
- tool-call payloads are visible in the ChatGPT UI for debugging
- write actions require confirmation by default
That is enough to make Developer Mode the real integration environment. If your app only works in Inspector or only works against a local process, it is not ready yet.
Submission changes the bar
The current submission guide is the cleanest source for what changes when you move from testing to public distribution.
The big shift is this: Developer Mode is for private iteration. Submission is for public distribution. If you only need an internal tool or a private app, stop at Developer Mode. Do not submit just because you can.
Once you do submit, the requirements get stricter:
- the MCP server must be on a public domain
- local or testing endpoints are not allowed
- the app needs a CSP that only allows the exact domains it fetches from
- you need
api.apps.writeto create and submit drafts, andapi.apps.readto inspect them in the OpenAI Platform Dashboard - the submission flow requires screenshots, test prompts, privacy policy details, and other review metadata
The same guide also confirms a few non-obvious constraints:
- projects with EU data residency cannot currently submit
- only one version of a given app can be in review at a time
- approved apps are published from the dashboard flow, and publishing creates a plugin for Codex distribution
The companion app submission guidelines explain why these checks exist: tool names and descriptions need to be precise, annotations need to match real behavior, and apps need to minimize both inputs and returned data.
That part is where a lot of apps fail review. If you want the lived-in version of that friction after the official checklist, read ChatGPT App Submission Gotchas.
What most builders get wrong
They treat the Apps SDK like a magic wrapper
It is still your job to define the tools cleanly, keep handlers idempotent, and separate model-readable data from widget-only data. The SDK gives you the shape. It does not rescue a vague app design.
They start with ChatGPT-only globals
That makes the app harder to port and harder to reason about. Build around the MCP Apps bridge first.
They attach UI to every tool
The current UI guide is right about this. Heavy UIs behave better when render tools are explicit and data tools stay reusable.
They underestimate tool descriptions
The model chooses tools from your metadata. Weak names and weak descriptions are not a copy problem. They are a routing problem.
They confuse a successful dev test with submission readiness
Passing one test in ChatGPT is not the same thing as being ready for review. Submission adds CSP, privacy, screenshot, test-case, and operational requirements that do not show up in the first local prototype.
If you want the same flow without hand-rolling the SDK
Some teams genuinely should stay code-first. If you need custom backend logic, fine-grained auth control, or a bespoke UI runtime, the SDK is the right tool.
If your app is mostly "define tools, connect an API, return a useful UI, then publish an MCP endpoint," the builder path is usually faster. The practical docs path is:
That gives you the same server-to-UI-to-endpoint lifecycle without rebuilding the plumbing yourself.
Summary
The OpenAI Apps SDK is best understood as the bridge between an MCP server and a
real ChatGPT app experience. The durable build strategy is to keep the core app
aligned with the MCP Apps standard, expose a public /mcp endpoint, use
window.openai only for truly ChatGPT-specific capabilities, test the real flow
in Developer Mode, and treat submission as a separate operational step with a
stricter bar.
If you keep those layers separate, the docs stop feeling contradictory. They are describing different parts of the same stack.
FAQ
Is the OpenAI Apps SDK the same thing as MCP?
No. The Apps SDK is built on MCP, but it is not identical to it. MCP gives you the protocol and the host-app UI standard, while the Apps SDK packages that flow for ChatGPT and adds optional ChatGPT-specific capabilities.
When should I use window.openai?
Use it when you need a ChatGPT-only capability such as file upload, a host modal, display-mode changes, or checkout. Do not use it as the foundation for basic tool I/O if the MCP Apps bridge already covers the job.
Can I test an Apps SDK app locally?
You can build and inspect it locally, but ChatGPT itself expects a remote HTTPS
endpoint at your public /mcp URL. For development, OpenAI's docs recommend
exposing localhost through a tunnel such as ngrok or Cloudflare Tunnel, but the
Help Center beta article is explicit that ChatGPT itself does not connect to
local MCP servers.
Does Developer Mode mean my app is ready to publish?
No. Developer Mode proves the app can be connected and exercised in ChatGPT. It does not prove that your CSP, privacy policy, screenshots, test prompts, tool annotations, and submission metadata are ready for review.
Should every tool render a widget?
Usually no. OpenAI's current UI guidance is to keep data tools reusable and let explicit render tools own the widget when the UI is substantial. That reduces unnecessary rerenders and gives the model more room to decide when a UI is worth showing.


