---
title: Apps SDK (ChatGPT)
sidebarTitle: Apps SDK
description: "Deep dive into ChatGPT's runtime: MCP Apps compatibility, the window.openai API, and ChatGPT-only features."
---

The [OpenAI Apps SDK](https://developers.openai.com/apps-sdk/) is the ChatGPT runtime layer for ChatGPT Apps, [announced in October 2025](https://alpic.ai/blog/inside-openai-s-apps-sdk-how-to-build-interactive-chatgpt-apps-with-mcp). ChatGPT supports the MCP Apps UI model, and also provides `window.openai` for compatibility and ChatGPT-specific extensions.

## View Rendering Flow

Here's what happens when ChatGPT renders a view:

1. User asks ChatGPT to perform an action (e.g., "Show me flight options to Paris")
2. ChatGPT calls your MCP tool (e.g., `search_flights`)
3. Your tool returns a result with data and a reference to a UI resource
4. ChatGPT fetches the resource (your compiled React component) and renders it in an iframe
5. The view is hydrated with your tool's `structuredContent` and `_meta` properties

<img src="/images/fundamentals-chatgpt-apps.png" alt="ChatGPT Apps Rendering Flow" style={{ width: "100%", maxWidth: "800px", display: "block", margin: "0 auto" }} />



## The `window.openai` API

Views run inside an iframe and have access to a special [`window.openai`](https://developers.openai.com/apps-sdk/build/chatgpt-ui#understand-the-windowopenai-api) global. In ChatGPT, this acts as a compatibility layer plus an extension surface for host-specific features:

| API | Description |
|-----|-------------|
| `toolInput` | Arguments supplied when the tool was invoked |
| `toolOutput` | Your `structuredContent` — initial data passed from your tool |
| `toolResponseMetadata` | The `_meta` payload; only the view sees it, never the model |
| `viewState` / `setViewState()` | Persist UI state across interactions |
| `callTool()` | Trigger additional tool calls from the UI |
| `sendFollowUpMessage()` | Send messages back into the chat |
| `requestDisplayMode()` | Request inline, PiP, or fullscreen display |
| `requestModal()` | Open a modal window outside the iframe |
| `uploadFile()` | Upload files to host-managed storage |
| `getFileDownloadUrl()` | Get download URLs for uploaded files |
| `openExternal()` | Open external URLs safely |
| `setOpenInAppUrl()` | Set URL for the "Open in &lt;App&gt;" button in fullscreen mode |

This API is powerful but low-level and imperative—which is where Skybridge comes in.

## Skybridge Hook Mapping

Skybridge wraps the raw `window.openai` API with React hooks:

| Raw API | Skybridge Hook | Purpose |
|---------|----------------|---------|
| `window.openai.toolInput`, `toolOutput`, `toolResponseMetadata` | [`useToolInfo()`](/api-reference/use-tool-info) | Access tool input, output, and `_meta` |
| `window.openai.widgetState` and `window.openai.setWidgetState` | [`useViewState()`](/api-reference/use-view-state) | Persistent view state |
| `window.openai.callTool()` | [`useCallTool()`](/api-reference/use-call-tool) | Make additional tool calls |
| `window.openai.sendFollowUpMessage()` | [`useSendFollowUpMessage()`](/api-reference/use-send-follow-up-message) | Send follow-up messages |
| `window.openai.openExternal()` | [`useOpenExternal()`](/api-reference/use-open-external) | Open external URLs |
| `window.openai.view` and `window.openai.requestModal()` | [`useRequestModal()`](/api-reference/use-request-modal) | Request modal display |
| `window.openai.theme`, `maxHeight`, `safeArea` | [`useLayout()`](/api-reference/use-layout) | Theme, max height, safe area insets |
| `window.openai.locale`, `userAgent` | [`useUser()`](/api-reference/use-user) | Locale and device/capabilities |
| `window.openai.displayMode` and `window.openai.requestDisplayMode` | [`useDisplayMode()`](/api-reference/use-display-mode) | Access/change display mode |
| `window.openai.uploadFile()`, `getFileDownloadUrl()` | [`useFiles()`](/api-reference/use-files) | Upload and download files |
| `window.openai.setOpenInAppUrl()` | [`useSetOpenInAppUrl()`](/api-reference/use-set-open-in-app-url) | "Open in App" button URL in fullscreen |
| `window.openai.requestClose()` | [`useRequestClose()`](/api-reference/use-request-close) | Close the view from the UI (e.g. user dismisses) |

## Skybridge Apps SDK Exclusive Features

These features are **only available in ChatGPT** and not supported in MCP Apps:

### File Operations

Upload and download files with host-managed storage:

```tsx
import { useFiles } from "skybridge/web";

function View() {
  const { upload, getDownloadUrl } = useFiles();

  const handleUpload = async (file: File) => {
    const { fileId } = await upload(file);
    const { downloadUrl } = await getDownloadUrl({ fileId });
    // Use downloadUrl...
  };

  return <input type="file" onChange={handleUpload} />;
}
```

### Open in App URL

Set a URL for the "Open in App" button that appears in fullscreen mode:

```tsx
import { useSetOpenInAppUrl } from "skybridge/web";

function View() {
  const { setOpenInAppUrl } = useSetOpenInAppUrl();

  useEffect(() => {
    setOpenInAppUrl("https://myapp.com/view/123");
  }, []);

  return <div>View content</div>;
}
```

## Testing in ChatGPT

Please see the [Test Your App](/quickstart/test-your-app#testing-in-chatgpt) guide for more information.

<Warning>
ChatGPT has aggressive caching. Use [DevTools](/devtools) for fast iteration, then test in ChatGPT for final validation. See [Fast Iteration](/concepts/fast-iteration).
</Warning>

## Related

- [MCP Apps](/fundamentals/mcp-apps) - The open alternative runtime
- [Write Once, Run Everywhere](/concepts/write-once-run-everywhere) - How Skybridge handles both runtimes
- [useSetOpenInAppUrl](/api-reference/use-set-open-in-app-url) - Open in App URL API reference
- [useFiles](/api-reference/use-files) - File operations API reference
