Widget Rendering Flow
Here’s what happens when ChatGPT renders a widget:- User asks ChatGPT to perform an action (e.g., “Show me flight options to Paris”)
- ChatGPT calls your MCP tool (e.g.,
search_flights) - Your tool returns a result with data and a reference to a UI resource
- ChatGPT fetches the resource (your compiled React component) and renders it in an iframe
- The widget is hydrated with your tool’s
structuredContentand_metaproperties

The window.openai API
Widgets run inside an iframe and have access to a special window.openai global that enables communication with the host:
| 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 widget sees it, never the model |
widgetState / setWidgetState() | 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 <App>” button in fullscreen mode |
Skybridge Hook Mapping
Skybridge wraps the rawwindow.openai API with React hooks:
| Raw API | Skybridge Hook | Purpose |
|---|---|---|
window.openai.toolInput, toolOutput, toolResponseMetadata | useToolInfo() | Access tool input, output, and _meta |
window.openai.widgetState and window.openai.setWidgetState | useWidgetState() | Persistent widget state |
window.openai.callTool() | useCallTool() | Make additional tool calls |
window.openai.sendFollowUpMessage() | useSendFollowUpMessage() | Send follow-up messages |
window.openai.openExternal() | useOpenExternal() | Open external URLs |
window.openai.view and window.openai.requestModal() | useRequestModal() | Request modal display |
window.openai.theme, maxHeight, safeArea | useLayout() | Theme, max height, safe area insets |
window.openai.locale, userAgent | useUser() | Locale and device/capabilities |
window.openai.displayMode and window.openai.requestDisplayMode | useDisplayMode() | Access/change display mode |
window.openai.uploadFile(), getFileDownloadUrl() | useFiles() | Upload and download files |
window.openai.setOpenInAppUrl() | useSetOpenInAppUrl() | ”Open in App” button URL in fullscreen |
Apps SDK APIs not yet supported in Skybridge
The following Apps SDK widget APIs are not yet wrapped by Skybridge hooks:| Raw API | Purpose |
|---|---|
window.openai.notifyIntrinsicHeight(...) | Report dynamic widget heights to the host to avoid scroll clipping |
window.openai.requestClose() | Close the widget from the UI (e.g. user dismisses) |
window.openai.requestCheckout(...) | Open ChatGPT Instant Checkout UI (monetization) |
window.openai when running in ChatGPT, if needed. Support may be added in a future release.
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:Open in App URL
Set a URL for the “Open in App” button that appears in fullscreen mode:Testing in ChatGPT
Please see the Test Your App guide for more information.Related
- MCP Apps - The open alternative runtime
- Write Once, Run Everywhere - How Skybridge handles both runtimes
- useSetOpenInAppUrl - Open in App URL API reference
- useFiles - File operations API reference