Development tools for building and testing ChatGPT Apps using ChatGPT Apps SDK
A powerful, zero-configuration development environment for building ChatGPT apps. Simulate the ChatGPT production environment locally with interactive controls for testing different states, themes, and device types.
- 🎭 Mock ChatGPT Environment - Simulates
window.openaiAPI exactly like production - 🎨 Theme Switching - Test light and dark themes instantly
- 📱 Device Simulation - Desktop (768px), Tablet (640px), Mobile (375px) viewports
- 🔄 State Testing - Loading, data, and error states with customizable delays
- 🐛 Debug Overlays - Visual boundary indicators for development
- 🚀 Zero Configuration - Works out of the box with sensible defaults
- 📦 Production Ready - Clean separation between dev tools and widget code
- 🔌 ChatGPT Apps SDK Compatible - Works with any ChatGPT Apps SDK implementation
- React 18.0.0 or higher
- Node.js 18.0.0 or higher
- Modern browser with ES2020+ support
- Chrome 80+, Firefox 75+, Safari 13.1+, Edge 80+
- @ainativekit/ui 0.10.0 or higher (optional, but recommended for enhanced theming)
- DevTools includes basic theme support out of the box
- For full design system integration, install
@ainativekit/uiand useThemeProvider - See usage examples below for both standalone and integrated approaches
npm install --save-dev @ainativekit/devtoolsor with yarn:
yarn add -D @ainativekit/devtoolsDevTools works out of the box with built-in theme support:
import { DevContainer } from '@ainativekit/devtools';
import App from './App';
// Only use DevContainer in development
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
import.meta.env.DEV ? (
<DevContainer>
<App />
</DevContainer>
) : (
<App />
)
);For full design system integration and advanced theming:
import { DevContainer } from '@ainativekit/devtools';
import { ThemeProvider } from '@ainativekit/ui';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider>
{import.meta.env.DEV ? (
<DevContainer>
<App />
</DevContainer>
) : (
<App />
)}
</ThemeProvider>
);<DevContainer
dataLoader={async () => {
// Return your widget-specific mock data
const response = await fetch('/api/mock-data');
return response.json();
}}
loadingDelay={3000} // Test loading states
theme="dark" // Start with dark theme
>
<YourWidget />
</DevContainer>DevContainer automatically detects when you have multiple widgets and shows a selector:
import { DevContainer, createMockData } from '@ainativekit/devtools';
import CarouselWidget from './widgets/CarouselWidget';
import MapWidget from './widgets/MapWidget';
import SearchWidget from './widgets/SearchWidget';
// Create mock data with automatic empty states
const restaurantData = createMockData(
{ restaurants: [...], totalResults: 9 },
{
emptyTransform: (data) => ({
...data,
restaurants: [],
totalResults: 0
})
}
);
function App() {
return (
<DevContainer
widgets={[
{ id: 'carousel', name: 'Restaurant Carousel', component: CarouselWidget },
{ id: 'map', name: 'Location Map', component: MapWidget },
{ id: 'search', name: 'Search Results', component: SearchWidget }
]}
dataLoaders={{
restaurants: () => restaurantData.full,
locations: () => ({ lat: 40.7128, lng: -74.0060 })
}}
emptyDataLoaders={{
restaurants: () => restaurantData.empty
}}
defaultWidget="carousel"
loadingDelay={2000}
theme="light"
/>
);
}Note: Wrap with
<ThemeProvider>from@ainativekit/uifor enhanced theming support.
Features:
- Single dev server for all widgets
- Automatic widget selector (only shows when multiple widgets)
- URL support (
?widget=map) for deep linking - Persistent widget selection
- Shared data loaders across widgets
| Prop | Type | Default | Description |
|---|---|---|---|
children |
React.ReactNode |
- | Single widget component |
dataLoader |
() => Promise<any> | any |
- | Data loader function |
emptyDataLoader |
() => Promise<any> | any |
- | Empty state data loader |
| Prop | Type | Default | Description |
|---|---|---|---|
widgets |
Widget[] |
- | Array of widget configurations |
dataLoaders |
Record<string, Function> |
{} |
Map of data loader functions |
emptyDataLoaders |
Record<string, Function> |
{} |
Map of empty data loader functions |
defaultDataLoader |
string |
- | Key for default data loader |
defaultWidget |
string |
- | ID of default widget to show |
| Prop | Type | Default | Description |
|---|---|---|---|
loadingDelay |
number |
2000 |
Delay (ms) before loading data |
theme |
'light' | 'dark' |
'light' |
Initial theme |
autoLoad |
boolean |
true |
Auto-load data on mount |
toolbarPosition |
'top' | 'bottom' |
'top' |
Toolbar position |
createMockData<T>(fullData: T, config?: MockDataConfig<T>): MockData<T>Creates type-safe mock data with automatic empty state generation.
Config Options:
emptyData: Explicit empty state dataemptyTransform: Function to derive empty state from full data- If neither provided, generates empty object automatically
The DevContainer automatically mocks the window.openai API with these methods:
window.openai = {
callTool: async (name, args) => { /* mocked */ },
sendFollowUpMessage: async ({ prompt }) => { /* mocked */ },
openExternal: ({ href }) => { /* mocked */ },
setWidgetState: (state) => { /* mocked */ },
// Plus all OpenAiGlobals properties
theme: 'light' | 'dark',
toolOutput: any,
locale: string,
maxHeight: number,
userAgent: { device: { type }, capabilities: { hover, touch } }
}- State Controls: Switch between Loading, Instant Data, Delayed Data, Empty, and Error states
- Theme Toggle: Switch between light and dark themes
- Device Simulation: Test desktop, tablet, and mobile viewports
- Debug Border: Toggle visual boundary indicators
- Collapsible UI: Hide/show dev tools with a single click
<DevContainer
loadingDelay={5000} // 5 second delay
dataLoader={async () => {
// Simulate slow API
await new Promise(resolve => setTimeout(resolve, 2000));
return { data: 'loaded' };
}}
>
<App />
</DevContainer><DevContainer
emptyDataLoader={() => {
// Return widget-specific empty state
return {
type: 'search-results',
properties: [],
searchInfo: { totalResults: 0, location: 'Sydney, NSW' }
};
}}
dataLoader={async () => {
// Regular data when not in empty state
return await fetchMockData();
}}
>
<SearchWidget />
</DevContainer><DevContainer
dataLoader={() => {
// Return error data
return { error: 'Something went wrong' };
}}
>
<App />
</DevContainer>// mockData.ts
export const mockSearchResults = {
type: 'search-results',
items: [
{ id: 1, title: 'Result 1' },
{ id: 2, title: 'Result 2' }
]
};
// App.tsx
import { mockSearchResults } from './mockData';
<DevContainer
dataLoader={() => mockSearchResults}
>
<SearchWidget />
</DevContainer>The DevContainer follows these principles:
- Zero Widget Contamination - Your widget code contains no dev-specific logic
- Production Parity - Uses the same APIs as production ChatGPT Apps
- External Debugging - All debug overlays are applied from outside the widget
- Type Safety - Full TypeScript support with AINativeKit types
- Clean Separation - Dev tools are never included in production builds
npm run buildnpm run devnpm run type-checkContributions are welcome! Please feel free to submit a Pull Request.
MIT © Jake Lin
Built with ❤️ for the ChatGPT app developer community. This tool helps developers build and test ChatGPT Apps using the ChatGPT Apps SDK, making development faster and more enjoyable.