React hooks and devtools for OpenAI Apps SDK
Complete API documentation for react-openai-apps-sdk.
Floating toolbar for debugging OpenAI Apps SDK widgets.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
initialIsOpen |
boolean |
false |
Show toolbar initially open |
buttonPosition |
'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' |
'bottom-right' |
Toggle button position |
enableMock |
boolean |
true |
Enable mock when window.openai doesn't exist |
mockConfig |
Partial<OpenAiGlobals> |
{} |
Initial mock configuration |
showToolbar |
boolean |
true |
Show toolbar controls |
showHotkeys |
boolean |
true |
Show keyboard shortcuts hint |
styleNonce |
string |
- | CSP nonce for inline styles |
Example:
<OpenAIDevTools
initialIsOpen={true}
buttonPosition="top-right"
mockConfig={{
theme: 'dark',
displayMode: 'fullscreen',
}}
/>Embedded mode - renders toolbar inline.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
style |
React.CSSProperties |
- | Custom styles |
onClose |
() => void |
- | Callback when panel closes |
showToolbar |
boolean |
true |
Show toolbar controls |
Example:
<OpenAIDevToolsPanel
style={{ height: '400px' }}
onClose={() => setShowPanel(false)}
/>Container that automatically respects safe area insets from mobile devices, system UI (notches, rounded corners), and ChatGPT's chat input bar.
WHY IT EXISTS: Mobile devices have notches, rounded corners, and system UI that obscure content. ChatGPT's chat input bar takes vertical space. This component automatically adjusts your widget's dimensions to fit within the safe, visible area.
DESIGN DECISIONS:
- Top inset uses padding (pushes content down from notch, maintains background)
- Bottom inset reduces height (prevents content from being hidden by chat input)
- Left/right insets use padding (safe area from rounded corners)
- Fullscreen: percentage-based height (responsive)
- Inline: fixed pixel height (embedded in chat)
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
- | Child components to render within safe area |
className |
string |
'' |
Additional CSS classes (Tailwind, CSS modules, etc.) |
style |
CSSProperties |
{} |
Inline styles (merged with safe area styles) |
fallbackHeight |
number |
600 |
Height in pixels for inline mode |
applyInsets |
object |
All sides | Which insets to apply: { top?, bottom?, left?, right? } |
Examples:
// Basic usage - handles all safe area concerns automatically
<SafeArea>
<YourWidget />
</SafeArea>
// Custom fallback height for inline mode
<SafeArea fallbackHeight={800}>
<TallerWidget />
</SafeArea>
// Apply only top and bottom insets (full-width widget)
<SafeArea applyInsets={{ top: true, bottom: true, left: false, right: false }}>
<FullWidthWidget />
</SafeArea>
// Combine with className for styling
<SafeArea className="bg-gray-50 flex flex-col" fallbackHeight={600}>
<Header />
<Content />
<Footer />
</SafeArea>WHEN TO USE:
- Root container for your widget
- When content should avoid being obscured by device notches or system UI
- When you need consistent padding on mobile devices
WHEN NOT TO USE:
- For nested containers (only use at root level)
- If you're manually managing device-specific layouts
- For desktop-only widgets that don't need mobile consideration
Returns the current window.openai object.
Returns: OpenAI | undefined
Example:
const openai = useOpenAI();
await openai?.callTool('my_tool', { arg: 'value' });
await openai?.sendFollowUpMessage({ prompt: 'Follow up...' });Safe wrappers for OpenAI API actions with built-in error handling and null checks. Eliminates the need for manual window.openai availability checks and try-catch blocks.
Returns:
{
sendFollowUpMessage: (options: SendFollowUpMessageOptions) => Promise<boolean>;
callTool: <T>(options: CallToolOptions<T>) => Promise<{ success: boolean; data?: T }>;
requestDisplayMode: (options: RequestDisplayModeOptions) => Promise<boolean>;
openExternal: (options: OpenExternalOptions) => boolean;
isAvailable: boolean;
}Example:
const { sendFollowUpMessage, callTool, requestDisplayMode, openExternal, isAvailable } = useOpenAIActions();
// Send a follow-up message with error handling
const handleShare = async () => {
const success = await sendFollowUpMessage({
prompt: 'Here is my exported file: [Download](https://example.com/file.png)',
fallbackMessage: 'ChatGPT API not available. Try copying the URL instead.',
onSuccess: () => console.log('Shared successfully!'),
onError: (error) => console.error('Failed to share:', error),
});
};
// Call a tool with typed result
const handlePreview = async (postId: string) => {
const { success, data } = await callTool<PostData>({
name: 'social_media.preview_post',
args: { postId },
onSuccess: (result) => console.log('Preview loaded:', result.structuredContent),
onError: (error) => alert(`Failed to load preview: ${error.message}`),
});
if (success && data) {
setPreviewData(data);
}
};
// Request display mode change
const toggleFullscreen = async () => {
await requestDisplayMode({
mode: isFullscreen ? 'inline' : 'fullscreen',
onSuccess: (newMode) => setIsFullscreen(newMode === 'fullscreen'),
});
};
// Open external link
const openDocs = () => {
openExternal({
href: 'https://docs.example.com',
onError: (error) => alert(`Failed to open link: ${error.message}`),
});
};Benefits:
- ✅ No more null checks - Handles
window.openaiavailability automatically - ✅ Built-in error handling - Consistent error handling across the app
- ✅ Type-safe - Full TypeScript support with generics
- ✅ Cleaner code - Reduces boilerplate by 50-70%
Options Types:
interface SendFollowUpMessageOptions {
prompt: string;
onSuccess?: () => void;
onError?: (error: Error) => void;
fallbackMessage?: string;
}
interface CallToolOptions<T = unknown> {
name: string;
args: Record<string, unknown>;
onSuccess?: (result: { structuredContent?: T; result?: string }) => void;
onError?: (error: Error) => void;
fallbackMessage?: string;
}
interface RequestDisplayModeOptions {
mode: 'inline' | 'fullscreen' | 'pip';
onSuccess?: (mode: 'inline' | 'fullscreen' | 'pip') => void;
onError?: (error: Error) => void;
fallbackMessage?: string;
}
interface OpenExternalOptions {
href: string;
onError?: (error: Error) => void;
fallbackMessage?: string;
}Returns a specific global from window.openai.
Parameters:
key: Key of the global to access
Returns: OpenAiGlobals[K] | null
Example:
const theme = useOpenAIGlobal('theme');
const displayMode = useOpenAIGlobal('displayMode');
const maxHeight = useOpenAIGlobal('maxHeight');
const toolOutput = useOpenAIGlobal('toolOutput');
const widgetState = useOpenAIGlobal('widgetState');Based on OpenAI Apps SDK official examples
Convenience hook for accessing display mode. Equivalent to useOpenAIGlobal('displayMode').
Returns: 'inline' | 'fullscreen' | 'pip' | null
Example:
const displayMode = useDisplayMode();
const isFullscreen = displayMode === 'fullscreen';Convenience hook for accessing max height. Equivalent to useOpenAIGlobal('maxHeight').
Returns: number | null
Example:
const maxHeight = useMaxHeight();
return <div style={{ height: maxHeight || 600 }}>Content</div>;Get tool output data passed to the widget. Equivalent to useOpenAIGlobal('toolOutput') with fallback support.
Parameters:
defaultState(optional): Default state iftoolOutputis null
Returns: T (typed based on default state)
Example:
const props = useWidgetProps({ platform: 'instagram', text: '' });
// props.platform, props.text are now type-safePersistent widget state shared with the model. Returns state and setter function.
Parameters:
defaultState(optional): Default state ifwidgetStateis null
Returns: readonly [T | null, (state: SetStateAction<T | null>) => void]
Example:
const [favorites, setFavorites] = useWidgetState<string[]>([]);
// Update state (persisted and shared with model)
setFavorites(prev => [...prev, newItem]);Manually create a mock window.openai object.
Parameters:
config(optional): Initial configuration
Returns: OpenAI
Example:
window.openai = createMockOpenAI({
theme: 'dark',
displayMode: 'fullscreen',
});Update a specific value in the mock.
Parameters:
key: The key to updatevalue: The new value
Example:
updateMockState('theme', 'light');
updateMockState('maxHeight', 800);Dispatch a custom OpenAI event.
Parameters:
type: Event typedetail: Event details
Example:
dispatchOpenAIEvent('set_globals', {
theme: 'dark',
displayMode: 'fullscreen',
});Complete type definition for window.openai.
interface OpenAI extends OpenAiGlobals, OpenAIAPI {
__devMock?: boolean;
}Global state available from ChatGPT.
interface OpenAiGlobals {
theme: 'light' | 'dark';
displayMode: 'inline' | 'fullscreen' | 'pip';
maxHeight: number;
locale: string;
userAgent: UserAgent;
safeArea: SafeArea;
toolInput: unknown | null;
toolOutput: unknown | null;
toolResponseMetadata: unknown | null;
widgetState: unknown | null;
setWidgetState: (state: unknown) => Promise<void>;
}API methods available on window.openai.
interface OpenAIAPI {
callTool: (name: string, args: Record<string, unknown>) => Promise<any>;
sendFollowUpMessage: (args: { prompt: string }) => Promise<void>;
openExternal: (payload: { href: string }) => void;
requestDisplayMode: (args: { mode: DisplayMode }) => Promise<{ mode: DisplayMode }>;
}When DevTools are active:
- E - Toggle display mode (inline ↔ fullscreen)
- T - Toggle theme (light ↔ dark)
In production (NODE_ENV=production):
- DevTools automatically removed (tree-shaken)
- Hooks still work (access real
window.openai) - Zero bundle size impact
Enable DevTools in production ChatGPT builds for debugging:
# Build with DevTools enabled (read-only inspector)
VITE_ENABLE_OPENAI_DEVTOOLS=true pnpm buildBehavior with flag enabled:
- DevTools render in production ChatGPT
- Read-only inspector for real
window.openaistate - No mock creation (safe for real environment)
- Inspect
toolOutput,widgetState, and all globals - Useful for debugging production issues
Note: This only works with ESM builds (Vite, webpack 5+). CJS builds always tree-shake DevTools.