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.

LayerJobWhat you build against
MCP serverDefines tools, auth, data access, and tool resultsTool descriptors, handlers, auth, transport
MCP Apps standardDefines how the UI resource talks to the host_meta.ui.resourceUri, ui/*, tools/call
ChatGPT runtimeAdds ChatGPT-specific UI features and distribution flowwindow.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.

Mermaid diagram source:
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:

  1. Attach UI with _meta.ui.resourceUri.
  2. Communicate through the ui/* JSON-RPC bridge over postMessage.
  3. Call tools through the MCP tool surface.
  4. Add ChatGPT-only extensions later, behind feature detection.

That is straight from the current compatibility guide and UI guide.

The split looks like this:

NeedStandard pathChatGPT-only path
Link a tool to a UI_meta.ui.resourceUri_meta["openai/outputTemplate"] compatibility alias
Receive tool resultsui/notifications/tool-resultwindow.openai.toolOutput
Call a tool from the UItools/callwindow.openai.callTool
Send follow-up contextui/message / ui/update-model-contextwindow.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:

  1. Register an HTML UI resource with the MCP Apps MIME type.
  2. Define tools with clean names, input schemas, and UI metadata.
  3. Return structuredContent, optional text content, and widget-only _meta.
  4. Expose a public /mcp endpoint 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:

  • structuredContent is the model-readable data.
  • content is the optional narration.
  • _meta is 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.openai and 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:

  1. Turn on Developer Mode in ChatGPT settings.
  2. Create an app or connector from your remote MCP server.
  3. Review the discovered tools.
  4. Test in a new conversation.
  5. 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.write to create and submit drafts, and api.apps.read to 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.