ChatGPT Apps Builder
Complete workflow for building, testing, and deploying ChatGPT Apps from concept to production.
Commands
/chatgpt-apps new- Create a new ChatGPT App/chatgpt-apps add-tool- Add an MCP tool to your app/chatgpt-apps add-widget- Add a widget to your app/chatgpt-apps add-auth- Configure authentication/chatgpt-apps add-database- Set up database/chatgpt-apps validate- Validate your app/chatgpt-apps test- Run tests/chatgpt-apps deploy- Deploy to production/chatgpt-apps resume- Resume working on an app
Table of Contents
- Create New App
- Add MCP Tool
- Add Widget
- Add Authentication
- Add Database
- Generate Golden Prompts
- Validate App
- Test App
- Deploy App
- Resume App
1. Create New App
Purpose: Create a new ChatGPT App from concept to working code.
Workflow
Phase 1: Conceptualization
Ask for the app idea "What ChatGPT App would you like to build? Describe what it does and the problem it solves."
Analyze against UX Principles
- Conversational Leverage: What can users accomplish through natural language?
- Native Fit: How does this integrate with ChatGPT's conversational flow?
- Composability: Can tools work independently and combine with other apps?
Check for Anti-Patterns
- Static website content display
- Complex multi-step workflows requiring external tabs
- Duplicating ChatGPT's native capabilities
- Ads or upsells
Define Use Cases Create 3-5 primary use cases with user stories.
Phase 2: Design
Tool Topology
- Query tools (readOnlyHint: true)
- Mutation tools (destructiveHint: false)
- Destructive tools (destructiveHint: true)
- Widget tools (return UI with _meta)
- External API tools (openWorldHint: true)
Widget Design For each widget:
id- unique identifier (kebab-case)name- display namedescription- what it showsmockData- sample data for preview
Data Model Design entities and relationships.
Auth Requirements
- Single-user (no auth needed)
- Multi-user (Auth0 or Supabase Auth)
Phase 3: Implementation
Generate complete application with this structure:
{app-name}/
โโโ package.json
โโโ tsconfig.server.json
โโโ setup.sh
โโโ START.sh
โโโ .env.example
โโโ .gitignore
โโโ server/
โโโ index.ts
Critical Requirements:
Serverclass from@modelcontextprotocol/sdk/server/index.jsStreamableHTTPServerTransportfor session management- Widget URIs:
ui://widget/{widget-id}.html - Widget MIME type:
text/html+skybridge structuredContentin tool responses_metawithopenai/outputTemplateon tools
Phase 4: Testing
- Run setup:
./setup.sh - Start dev:
./START.sh --dev - Preview widgets:
http://localhost:3000/preview - Test MCP connection
Phase 5: Deployment
- Generate Dockerfile and render.yaml
- Deploy to Render
- Configure ChatGPT connector
2. Add MCP Tool
Purpose: Add a new MCP tool to your ChatGPT App.
Workflow
Gather Information
- What does this tool do?
- What inputs does it need?
- What does it return?
Classify Tool Type
- Query (readOnlyHint: true) - Fetches data
- Mutation (destructiveHint: false) - Creates/updates data
- Destructive (destructiveHint: true) - Deletes data
- Widget - Returns UI content
- External (openWorldHint: true) - Calls external APIs
Design Input Schema Create Zod schema with appropriate types and descriptions.
Generate Tool Handler Use
chatgpt-mcp-generatoragent to create:- Tool handler in
server/tools/ - Zod schema export
- Type exports
- Database queries (if needed)
- Tool handler in
Register Tool Update
server/index.tswith metadata:{ name: "my-tool", _meta: { "openai/toolInvocation/invoking": "Loading...", "openai/toolInvocation/invoked": "Done", "openai/outputTemplate": "ui://widget/my-widget.html", // if widget } }Update State Add tool to
.chatgpt-app/state.json.
Tool Naming
Use kebab-case: list-items, create-task, show-recipe-detail
Annotations Guide
| Scenario | readOnlyHint | destructiveHint | openWorldHint |
|---|---|---|---|
| List/Get | true | false | false |
| Create/Update | false | false | false |
| Delete | false | true | false |
| External API | varies | varies | true |
3. Add Widget
Purpose: Add inline HTML widgets with HTML/CSS/JS and Apps SDK integration.
5 Widget Patterns
- Card Grid - Multiple items in grid
- Stats Dashboard - Key metrics display
- Table - Tabular data
- Bar Chart - Simple visualizations
- Detail Widget - Single item details
Workflow
Gather Information
- Widget purpose and data
- Visual design (cards, table, chart, etc.)
- Interactivity needs
Define Data Shape Document expected structure with TypeScript interface.
Add Widget Config
const widgets: WidgetConfig[] = [ { id: "my-widget", name: "My Widget", description: "Displays data", templateUri: "ui://widget/my-widget.html", invoking: "Loading...", invoked: "Ready", mockData: { /* sample */ }, }, ];Add Widget HTML Generate HTML with:
- Preview mode support (
window.PREVIEW_DATA) - OpenAI Apps SDK integration (
window.openai.toolOutput) - Event listeners (
openai:set_globals) - Polling fallback (100ms, 10s timeout)
- Preview mode support (
Create/Update Tool Link tool to widget via
widgetId.Test Widget Preview at
/preview/{widget-id}with mock data.
Widget HTML Structure
(function() {
let rendered = false;
function render(data) {
if (rendered || !data) return;
rendered = true;
// Render logic
}
function tryRender() {
if (window.PREVIEW_DATA) { render(window.PREVIEW_DATA); return; }
if (window.openai?.toolOutput) { render(window.openai.toolOutput); }
}
window.addEventListener('openai:set_globals', tryRender);
const poll = setInterval(() => {
if (window.openai?.toolOutput || window.PREVIEW_DATA) {
tryRender();
clearInterval(poll);
}
}, 100);
setTimeout(() => clearInterval(poll), 10000);
tryRender();
})();
4. Add Authentication
Purpose: Configure authentication using Auth0 or Supabase Auth.
When to Add
- Multiple users
- Persistent private data per user
- User-specific API credentials
Providers
Auth0:
- Enterprise-grade
- OAuth 2.1, PKCE flow
- Social logins (Google, GitHub, etc.)
Supabase Auth:
- Simpler setup
- Email/password default
- Integrates with Supabase database
Workflow
Choose Provider Ask user preference based on needs.
Guide Setup
- Auth0: Create application, configure callback URLs, get credentials
- Supabase: Already configured with database setup
Generate Auth Code Use
chatgpt-auth-generatoragent to create:- Session management middleware
- User subject extraction
- Token validation
Update Server Add auth middleware to protect routes.
Update Environment
# Auth0 AUTH0_DOMAIN=your-tenant.auth0.com AUTH0_CLIENT_ID=... AUTH0_CLIENT_SECRET=... # Supabase (from database setup) SUPABASE_URL=... SUPABASE_ANON_KEY=...Test Verify login flow and user isolation.
5. Add Database
Purpose: Configure PostgreSQL database using Supabase.
When to Add
- Persistent user data
- Multi-entity relationships
- Query/filter capabilities
Workflow
Check Supabase Setup Verify account and project exist.
Gather Credentials
- Project URL
- Anon key (public)
- Service role key (server-side)
Define Entities For each entity, specify:
- Fields and types
- Relationships
- Indexes
Generate Schema Use
chatgpt-database-generatoragent to create SQL with:id(UUID primary key)user_subject(varchar, indexed)created_at(timestamptz)updated_at(timestamptz)- RLS policies for user isolation
Setup Connection Pool
import { createClient } from '@supabase/supabase-js'; const supabase = createClient( process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY! );Apply Migrations Run SQL in Supabase dashboard or via migration tool.
Query Pattern
Always filter by user_subject:
const { data } = await supabase
.from('tasks')
.select('*')
.eq('user_subject', userSubject);
6. Generate Golden Prompts
Purpose: Generate test prompts to validate ChatGPT correctly invokes tools.
Why Important
- Measure precision/recall
- Enable iteration
- Post-launch monitoring
3 Categories
Direct Prompts - Explicit tool invocation
- "Show me my task list"
- "Create a new task called..."
Indirect Prompts - Outcome-based, ChatGPT should infer tool
- "What do I need to do today?"
- "Help me organize my work"
Negative Prompts - Should NOT trigger tool
- "What is a task?"
- "Tell me about project management"
Workflow
Analyze Tools Review each tool's purpose and inputs.
Generate Prompts For each tool, create:
- 5+ direct prompts
- 5+ indirect prompts
- 3+ negative prompts
- 2+ edge case prompts
Best Practices
- Tool descriptions start with "Use this when..."
- State limitations clearly
- Include examples in descriptions
Save Output Write to
.chatgpt-app/golden-prompts.json:{ "toolName": { "direct": ["prompt1", "prompt2"], "indirect": ["prompt1", "prompt2"], "negative": ["prompt1", "prompt2"], "edge": ["prompt1", "prompt2"] } }
7. Validate App
Purpose: Validation suite before deployment.
10 Validation Checks
Required Files
- package.json
- tsconfig.server.json
- setup.sh (executable)
- START.sh (executable)
- server/index.ts
- .env.example
Server Implementation
- Uses
Serverfrom MCP SDK - Has
StreamableHTTPServerTransport - Session management with Map
- Correct request handlers
- Uses
Widget Configuration
widgetsarray exists- Each has id, name, description, templateUri, mockData
- URIs match pattern
ui://widget/{id}.html
Tool Response Format
- Returns
structuredContent(not justcontent) - Widget tools have
_metawithopenai/outputTemplate
- Returns
Resource Handler Format
- MIME type:
text/html+skybridge - Returns
_metawith serialization and CSP
- MIME type:
Widget HTML Structure
- Preview mode support
- Event listeners for Apps SDK
- Polling fallback
- Render guard
Endpoint Existence
/health- Health check/preview- Widget index/preview/:widgetId- Widget preview/mcp- MCP endpoint
Package.json Scripts
- Has
build:server - Has
startwith HTTP_MODE=true - Has
devwith watch mode - NO web build scripts (web/, ui/, client/)
- Has
Annotation Validation
- readOnlyHint set correctly
- destructiveHint for delete operations
- openWorldHint for external APIs
Database Validation (if enabled)
- Tables have required fields
- user_subject indexed
- RLS policies enabled
Common Errors
| Error | Fix |
|---|---|
| Missing structuredContent | Add to tool response |
| Wrong widget URI | Use ui://widget/{id}.html |
| No session management | Add Map<string, Transport> |
| Missing _meta | Add to tool definition and response |
| Wrong MIME type | Use text/html+skybridge |
Critical: Check file existence FIRST before other validations!
8. Test App
Purpose: Run automated tests using MCP Inspector and golden prompts.
4 Test Categories
MCP Protocol
- Server starts without errors
- Handles initialize
- Lists tools correctly
- Lists resources correctly
Schema Validation
- Tool schemas are valid Zod
- Required fields marked
- Types match implementation
Widget Tests
- All widgets render in preview mode
- Mock data loads correctly
- No console errors
Golden Prompt Tests
- Direct prompts trigger correct tools
- Indirect prompts work as expected
- Negative prompts don't trigger tools
Workflow
Start Server in Test Mode
HTTP_MODE=true NODE_ENV=test npm run devRun MCP Inspector Test protocol compliance:
- Initialize connection
- List tools
- Call each tool with valid inputs
- Check responses
Schema Validation Verify schemas compile and match implementation.
Golden Prompt Tests Use ChatGPT to test prompts:
- Record which tool was called
- Compare to expected tool
- Calculate precision/recall
Generate Report
{ "passed": 42, "failed": 3, "categories": { "mcp": "โ ", "schema": "โ ", "widgets": "โ ", "prompts": "โ ๏ธ 3 failures" }, "timing": "2.3s" }
Fixing Failures
For each failure, explain:
- What failed
- Why it failed
- How to fix (with code example)
9. Deploy App
Purpose: Deploy ChatGPT App to Render with PostgreSQL and health checks.
Prerequisites
- โ Validation passed
- โ Tests passed
- โ Git repository clean
- โ Environment variables ready
Workflow
Pre-flight Check
- Run validation
- Run tests
- Check database connection (if enabled)
Generate render.yaml
services: - type: web name: {app-name} runtime: docker plan: free healthCheckPath: /health envVars: - key: PORT value: 3000 - key: HTTP_MODE value: true - key: NODE_ENV value: production - key: WIDGET_DOMAIN generateValue: true # Add auth/database vars if neededGenerate Dockerfile
FROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY dist ./dist EXPOSE 3000 CMD ["node", "dist/server/index.js"]Deploy Option A: Automated (if Render MCP available) Use Render MCP agent to deploy.
Option B: Manual
- Push to GitHub
- Connect repo in Render dashboard
- Set environment variables
- Deploy
Verify Deployment
- Health check:
https://{app}.onrender.com/health - MCP endpoint:
https://{app}.onrender.com/mcp - Tool discovery works
- Widgets render
- Health check:
Configure ChatGPT Connector
- URL:
https://{app}.onrender.com/mcp - Test in ChatGPT
- URL:
10. Resume App
Purpose: Resume building an in-progress ChatGPT App.
Workflow
Load State Read
.chatgpt-app/state.json:{ "appName": "My Task Manager", "phase": "Implementation", "tools": ["list-tasks", "create-task"], "widgets": ["task-list"], "auth": false, "database": true, "validated": false, "deployed": false }Display Progress Show current status:
- App name
- Current phase
- Completed items (tools, widgets)
- Pending items (auth, validation, deployment)
Offer Next Steps Based on phase:
Concept Phase:
- "Let's design the tools and widgets"
- "Shall we start implementation?"
Implementation Phase:
- "Add another tool?"
- "Add a widget?"
- "Set up authentication?"
- "Set up database?"
Testing Phase:
- "Generate golden prompts?"
- "Run validation?"
- "Run tests?"
Deployment Phase:
- "Deploy to Render?"
- "Configure ChatGPT connector?"
Continue Work Based on user's choice, invoke the appropriate workflow section.
Best Practices
- Always save state after each major step
- Validate before moving forward (especially before deployment)
- Use agents for code generation (chatgpt-mcp-generator, chatgpt-auth-generator, etc.)
- Test at every phase (preview widgets, test tools, run golden prompts)
- Keep it conversational - guide the user naturally through the workflow
- Explain trade-offs when offering choices (Auth0 vs Supabase, etc.)
- Show examples when introducing new concepts
State Management
The .chatgpt-app/state.json file tracks progress:
{
"appName": "string",
"description": "string",
"phase": "Concept" | "Implementation" | "Testing" | "Deployment",
"tools": ["tool-name"],
"widgets": ["widget-id"],
"auth": {
"enabled": boolean,
"provider": "auth0" | "supabase" | null
},
"database": {
"enabled": boolean,
"entities": ["entity-name"]
},
"validated": boolean,
"tested": boolean,
"deployed": boolean,
"deploymentUrl": "string | null",
"goldenPromptsGenerated": boolean,
"lastUpdated": "ISO timestamp"
}
Command Reference
# Setup
./setup.sh
# Development
./START.sh --dev # Dev mode with watch
./START.sh --preview # Open preview in browser
./START.sh --stdio # STDIO mode (testing)
./START.sh # Production mode
# Testing
npm run validate # Type checking
curl http://localhost:3000/health
# Deployment
git push origin main # Trigger Render deploy
Getting Started
When the user invokes any chatgpt-app command:
- Check if
.chatgpt-app/state.jsonexists - If yes โ use Resume App workflow
- If no โ use Create New App workflow
Always guide users through the natural progression: Concept โ Implementation โ Testing โ Deployment