← Back to all projects

LEARN CHATGPT APPS DEEP DIVE

Learn ChatGPT Apps: From Zero to Published App Developer

Goal: Deeply understand the ChatGPT Apps platform—why it exists, how to build apps using the Apps SDK and Model Context Protocol (MCP), create rich interactive UIs, and publish apps that reach 800+ million ChatGPT users.


What Are ChatGPT Apps?

ChatGPT Apps are interactive applications that extend ChatGPT’s capabilities by connecting it to external tools, data sources, and rich user interfaces. They allow ChatGPT to:

  • Know: Access live data, user-specific information, and external systems
  • Do: Take real actions (create records, schedule events, trigger workflows)
  • Show: Present information through rich, interactive UIs beyond plain text

Apps are built using the Apps SDK, which builds on the Model Context Protocol (MCP)—an open standard that lets AI models connect to external tools and data.

Why Do ChatGPT Apps Exist?

  1. Extend ChatGPT’s Reach: Connect to real-time data and external services
  2. Rich Interactions: Go beyond text with maps, charts, forms, and 3D visualizations
  3. Developer Opportunity: Reach 800+ million ChatGPT users
  4. Open Standard: Built on MCP, apps work across platforms that adopt the standard
  5. Enterprise Ready: Available to Business, Enterprise, and Edu customers

The Ecosystem at a Glance

┌─────────────────────────────────────────────────────────────────────┐
│                      CHATGPT APPS ARCHITECTURE                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                        ChatGPT UI                           │    │
│  │  [Chat Messages]  [Your App's Widget in iframe]             │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                              │                                      │
│                         window.openai                               │
│                              │                                      │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                   Your Web Component                        │    │
│  │    React/Vue/Vanilla JS │ Tailwind CSS │ @openai/apps-sdk-ui│    │
│  │    Rendered in iframe   │ Accesses window.openai API        │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                              │                                      │
│                     MCP (Model Context Protocol)                    │
│                              │                                      │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                     Your MCP Server                         │    │
│  │    Python (FastMCP) or Node.js │ Defines Tools & Resources  │    │
│  │    OAuth 2.1 Authentication    │ Connects to Your Backend   │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                              │                                      │
│                      Your Backend Services                          │
│                  (Databases, APIs, External Services)               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Core Concept Analysis

The Apps SDK Stack

The Apps SDK has two core components:

  1. Web Component (UI): An HTML/CSS/JavaScript interface rendered in an iframe within ChatGPT
  2. MCP Server (Backend): Exposes your app’s capabilities through tools and resources

Model Context Protocol (MCP)

MCP is the “USB-C for AI applications”—a universal connector between LLMs and external systems.

MCP Core Primitives:
├── Tools: Functions the model can execute
│   └── Example: get_weather(city), create_ticket(title, body)
│
├── Resources: Structured documents the model can read
│   └── Example: user_profile, company_settings
│
└── Prompts: Instructions or templates for the model
    └── Example: "When user asks about orders, always include status"

The window.openai API

Your widget communicates with ChatGPT through the injected window.openai object:

// Key APIs available in your widget:

// Read data from tool execution
window.openai.toolOutput        // Data your MCP server returned
window.openai.toolInput         // Parameters passed to the tool

// Take actions
window.openai.callTool(name, params)     // Invoke another tool
window.openai.sendFollowUpMessage(text)  // Send a message as user

// State management
window.openai.widgetState                // Persisted widget state
window.openai.setWidgetState(state)      // Save state across turns

// UI controls
window.openai.requestFullscreen()        // Expand to fullscreen
window.openai.exitFullscreen()           // Return to inline view

// File handling
window.openai.uploadFile(file)           // Upload image/file
window.openai.getFileDownloadUrl(id)     // Get file preview URL

Tools Design Best Practices

Tools are the interface between ChatGPT and your backend:

Good Tool Design:
├── Single Responsibility: One action per tool
│   ✓ get_order_status, cancel_order, update_shipping
│   ✗ manage_order (too broad)
│
├── Clear Naming: Descriptive verb phrases
│   ✓ search_products, add_to_cart, checkout
│   ✗ products, cart, buy (too vague)
│
├── Accurate Descriptions: Start with "Use this when..."
│   ✓ "Use this when user wants to find products by category"
│   ✗ "Product search" (not helpful for model routing)
│
├── Proper Annotations:
│   readOnlyHint: true      // Safe operations (no side effects)
│   destructiveHint: true   // Deletes data (requires confirmation)
│
└── Structured Outputs: Include IDs, timestamps, status
    ✓ { orderId: "123", status: "shipped", eta: "2025-12-21" }
    ✗ "Your order is on the way!" (not reusable)

Component Types

Components are the UI elements users interact with:

Type Purpose Example
Viewer Display read-only data Order status, analytics dashboard
Editor Modify data with forms Create/edit record, settings
Workspace Complex multi-step flows Document editor, design tool
List Dynamic collections Search results, notifications
Map Geographic visualization Store locator, delivery tracking
Carousel Swipeable content Product gallery, recommendations
Shop E-commerce flows Product browsing, checkout

Authentication (OAuth 2.1)

ChatGPT Apps use OAuth 2.1 with PKCE for secure authentication:

OAuth Flow:
1. User clicks "Connect" in ChatGPT
2. ChatGPT redirects to your authorization server
3. User logs in and grants permissions
4. Your server issues access token
5. ChatGPT uses token for MCP requests
6. Your MCP server validates token on every call

Project List

Projects are ordered from understanding the basics to building production-ready apps.


Project 1: MCP Protocol Explorer

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: Python
  • Alternative Programming Languages: TypeScript/Node.js
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: MCP Protocol / Tool Design
  • Software or Tool: FastMCP, MCP Inspector
  • Main Book: “Building APIs You Won’t Hate” by Phil Sturgeon

What you’ll build: A simple MCP server with 3-5 tools that you test with MCP Inspector, understanding how tools are defined, called, and how data flows between ChatGPT and your server.

Why it teaches ChatGPT Apps: Before building UIs, you need to understand MCP—the protocol that powers everything. This project teaches you tool definitions, parameter schemas, and the request/response cycle.

Core challenges you’ll face:

  • Setting up FastMCP → maps to Python MCP SDK basics
  • Defining tool schemas → maps to JSON Schema, parameter types
  • Testing with MCP Inspector → maps to debugging MCP servers
  • Understanding the protocol flow → maps to how ChatGPT invokes tools

Key Concepts:

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic Python, understanding of REST APIs

Real world outcome:

$ uvicorn my_mcp_server:app --port 8000

MCP Server running on http://localhost:8000

Available Tools:
  1. get_weather
     Description: "Use this when user asks about weather conditions"
     Parameters: { city: string, units?: "celsius" | "fahrenheit" }

  2. convert_currency
     Description: "Use this when user wants to convert between currencies"
     Parameters: { amount: number, from: string, to: string }

  3. get_stock_price
     Description: "Use this when user asks about stock prices"
     Parameters: { symbol: string }

# In MCP Inspector:
> invoke get_weather {"city": "San Francisco", "units": "celsius"}

Response:
{
  "temperature": 18,
  "condition": "Partly Cloudy",
  "humidity": 65,
  "wind": "12 km/h NW"
}

> invoke convert_currency {"amount": 100, "from": "USD", "to": "EUR"}

Response:
{
  "amount": 100,
  "from": "USD",
  "to": "EUR",
  "result": 92.45,
  "rate": 0.9245,
  "timestamp": "2025-12-20T10:30:00Z"
}

Implementation Hints:

MCP server structure (Python with FastMCP):

from fastmcp import FastMCP

mcp = FastMCP("My First MCP Server")

@mcp.tool()
def get_weather(city: str, units: str = "celsius") -> dict:
    """Use this when user asks about weather conditions for a city."""
    # Call your weather API here
    return {
        "temperature": 18,
        "condition": "Partly Cloudy",
        "humidity": 65
    }

@mcp.tool()
def convert_currency(amount: float, from_currency: str, to_currency: str) -> dict:
    """Use this when user wants to convert money between currencies."""
    # Call your currency API here
    rate = get_exchange_rate(from_currency, to_currency)
    return {
        "amount": amount,
        "from": from_currency,
        "to": to_currency,
        "result": amount * rate,
        "rate": rate
    }

# Run with: uvicorn main:mcp.app --port 8000

Testing with MCP Inspector:

# Install MCP Inspector
npx @anthropic/mcp-inspector

# Connect to your server
# Enter: http://localhost:8000
# Browse and test each tool

Questions to explore:

  • How does ChatGPT decide which tool to call?
  • What happens if a tool returns an error?
  • How do you validate input parameters?
  • What’s the difference between tools and resources?

Learning milestones:

  1. Server starts and lists tools → You understand MCP server basics
  2. Inspector can invoke tools → You understand the protocol
  3. Tools return structured data → You understand output design
  4. You add error handling → You understand robust tool design

Project 2: Hello World Widget

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/React
  • Alternative Programming Languages: Vanilla JavaScript, Vue
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Web Components / window.openai API
  • Software or Tool: Vite, React, @openai/apps-sdk-ui
  • Main Book: “Learning React” by Eve Porcello & Alex Banks

What you’ll build: A simple widget that displays data from a tool call, uses the window.openai API to read tool output, and can trigger follow-up actions—your first complete ChatGPT app.

Why it teaches ChatGPT Apps: This is the “Hello World” of ChatGPT apps. You’ll understand how widgets are built, how they receive data, and how they communicate with ChatGPT.

Core challenges you’ll face:

  • Setting up the build pipeline → maps to Vite configuration for widgets
  • Using window.openai API → maps to host communication
  • Rendering in ChatGPT’s iframe → maps to sandbox constraints
  • Connecting widget to MCP server → maps to full app architecture

Key Concepts:

Difficulty: Beginner Time estimate: Weekend Prerequisites: Project 1, basic React knowledge

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ ChatGPT                                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ You: What's the weather in Tokyo?                               │
│                                                                 │
│ ChatGPT: Let me check the weather for Tokyo.                    │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  🌤️ Weather in Tokyo                                       │ │
│ │                                                             │ │
│ │  Temperature: 15°C                                          │ │
│ │  Condition: Partly Cloudy                                   │ │
│ │  Humidity: 72%                                              │ │
│ │  Wind: 8 km/h E                                             │ │
│ │                                                             │ │
│ │  [🔄 Refresh]  [📍 Change City]  [📊 5-Day Forecast]        │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ The current weather in Tokyo is 15°C and partly cloudy.        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

Widget component structure:

// src/WeatherWidget.tsx
import { useEffect, useState } from 'react';
import { Button, Badge } from '@openai/apps-sdk-ui';

export function WeatherWidget() {
  const [weather, setWeather] = useState(null);

  useEffect(() => {
    // Read data from tool output
    const data = window.openai?.toolOutput;
    if (data) {
      setWeather(data);
    }
  }, []);

  const handleRefresh = () => {
    // Call the tool again
    window.openai?.callTool('get_weather', {
      city: weather?.city,
      units: 'celsius'
    });
  };

  const handleForecast = () => {
    // Send a follow-up message
    window.openai?.sendFollowUpMessage(
      `Show me the 5-day forecast for ${weather?.city}`
    );
  };

  if (!weather) return <div>Loading...</div>;

  return (
    <div className="p-4 space-y-4">
      <h2 className="text-xl font-bold">
        🌤️ Weather in {weather.city}
      </h2>

      <div className="grid grid-cols-2 gap-2">
        <div>Temperature: {weather.temperature}°C</div>
        <div>Condition: {weather.condition}</div>
        <div>Humidity: {weather.humidity}%</div>
        <div>Wind: {weather.wind}</div>
      </div>

      <div className="flex gap-2">
        <Button onClick={handleRefresh}>🔄 Refresh</Button>
        <Button onClick={handleForecast}>📊 5-Day Forecast</Button>
      </div>
    </div>
  );
}

Vite configuration for widget bundle:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { viteSingleFile } from 'vite-plugin-singlefile';

export default defineConfig({
  plugins: [react(), viteSingleFile()],
  build: {
    outDir: 'dist',
    rollupOptions: {
      output: {
        // Single self-contained HTML file
        inlineDynamicImports: true,
      },
    },
  },
});

MCP server returning widget:

@mcp.tool()
def get_weather(city: str, units: str = "celsius") -> dict:
    """Use this when user asks about weather conditions."""
    weather_data = fetch_weather_api(city, units)

    return {
        "structuredContent": weather_data,  # Goes to window.openai.toolOutput
        "uiComponent": {
            "type": "iframe",
            "url": "https://your-app.com/weather-widget.html"
        }
    }

Learning milestones:

  1. Widget renders in ChatGPT → You understand iframe embedding
  2. Widget reads toolOutput → You understand data flow
  3. Buttons trigger actions → You understand callTool and sendFollowUpMessage
  4. Full app works end-to-end → You’ve built your first ChatGPT app!

Project 3: Interactive List & Search App

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/React
  • Alternative Programming Languages: Vue, Svelte
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: State Management / List Components
  • Software or Tool: React, TanStack Query, @openai/apps-sdk-ui
  • Main Book: “React Query Essentials” (TanStack documentation)

What you’ll build: A searchable product catalog app with list view, filters, pagination, and detail views—demonstrating how to build data-rich apps that maintain state across conversation turns.

Why it teaches ChatGPT Apps: Most real apps involve lists, search, and navigation. This project teaches state management, pagination, and complex UI patterns within the ChatGPT widget environment.

Core challenges you’ll face:

  • Persisting state across turns → maps to widgetState API
  • Handling pagination → maps to efficient data loading
  • Implementing search/filter → maps to tool parameter design
  • Detail view navigation → maps to widget state management

Key Concepts:

  • Widget State: window.openai.setWidgetState() for persistence
  • List Component: Apps SDK UI Components
  • Pagination Patterns: Cursor vs. offset pagination
  • Search UX: Debouncing, loading states

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 1-2, React state management

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ ChatGPT                                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ You: Show me running shoes under $100                           │
│                                                                 │
│ ChatGPT: Here are running shoes under $100:                     │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  🔍 [Search...        ]  [Price ▼] [Brand ▼] [Rating ▼]    │ │
│ │                                                             │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ 🏃 Nike Air Zoom Pegasus 40                         │   │ │
│ │  │ ⭐ 4.5 (2,341 reviews) · $89.99                     │   │ │
│ │  │ [View Details] [Add to Cart]                        │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ 🏃 Adidas Ultraboost Light                          │   │ │
│ │  │ ⭐ 4.7 (1,892 reviews) · $94.99                     │   │ │
│ │  │ [View Details] [Add to Cart]                        │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  Showing 1-10 of 47 results  [← Prev] [1] [2] [3] [Next →] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

State persistence pattern:

function ProductList() {
  // Load persisted state on mount
  const [state, setState] = useState(() => {
    return window.openai?.widgetState || {
      searchQuery: '',
      filters: {},
      page: 1,
      selectedProductId: null
    };
  });

  // Persist state changes
  useEffect(() => {
    window.openai?.setWidgetState(state);
  }, [state]);

  const handleSearch = (query: string) => {
    setState(prev => ({ ...prev, searchQuery: query, page: 1 }));
    // Trigger new search via tool call
    window.openai?.callTool('search_products', {
      query,
      filters: state.filters,
      page: 1
    });
  };

  // ... rest of component
}

MCP tools for list operations:

@mcp.tool()
def search_products(
    query: str = "",
    category: str = None,
    min_price: float = None,
    max_price: float = None,
    page: int = 1,
    per_page: int = 10
) -> dict:
    """Use this when user wants to search or browse products."""
    products = search_database(query, category, min_price, max_price)

    return {
        "structuredContent": {
            "products": products[(page-1)*per_page : page*per_page],
            "total": len(products),
            "page": page,
            "perPage": per_page,
            "hasMore": page * per_page < len(products)
        },
        "uiComponent": {
            "type": "iframe",
            "url": "https://your-app.com/product-list.html"
        }
    }

@mcp.tool()
def get_product_details(product_id: str) -> dict:
    """Use this when user wants to see details of a specific product."""
    product = get_product_by_id(product_id)
    return {
        "structuredContent": product,
        "uiComponent": {
            "type": "iframe",
            "url": "https://your-app.com/product-detail.html"
        }
    }

Pagination component:

function Pagination({ page, total, perPage, onPageChange }) {
  const totalPages = Math.ceil(total / perPage);

  return (
    <div className="flex items-center gap-2">
      <Button
        disabled={page === 1}
        onClick={() => onPageChange(page - 1)}
      >
        ← Prev
      </Button>

      {[...Array(Math.min(5, totalPages))].map((_, i) => (
        <Button
          key={i}
          variant={page === i + 1 ? 'primary' : 'secondary'}
          onClick={() => onPageChange(i + 1)}
        >
          {i + 1}
        </Button>
      ))}

      <Button
        disabled={page === totalPages}
        onClick={() => onPageChange(page + 1)}
      >
        Next →
      </Button>
    </div>
  );
}

Learning milestones:

  1. List renders from API data → You understand structured content
  2. State persists across turns → You understand widgetState
  3. Pagination works → You understand multi-page data
  4. Search/filter work → You understand parameterized tools

Project 4: Map & Location-Based App

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/React
  • Alternative Programming Languages: Vue, Svelte
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Maps / Geolocation / Visualization
  • Software or Tool: Mapbox GL JS, Leaflet, or Google Maps
  • Main Book: “Mapping Hacks” by Schuyler Erle

What you’ll build: A store locator / delivery tracker app with interactive maps, marker clustering, location search, and detail panes—demonstrating geographic visualization in ChatGPT.

Why it teaches ChatGPT Apps: Maps are one of the most visually impressive component types. This project teaches you how to integrate complex third-party libraries and handle geographic data.

Core challenges you’ll face:

  • Integrating map libraries in iframe → maps to third-party library constraints
  • Marker clustering and interaction → maps to performance optimization
  • Geolocation and search → maps to location-aware tools
  • Responsive map sizing → maps to ChatGPT widget dimensions

Key Concepts:

  • Mapbox GL JS: Mapbox Documentation
  • Geocoding APIs: Converting addresses to coordinates
  • Marker Clustering: Handling many points efficiently
  • Map Component: Pizza example in apps-sdk-examples

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 1-3, basic geographic concepts

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ ChatGPT                                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ You: Find coffee shops near Union Square, San Francisco        │
│                                                                 │
│ ChatGPT: Here are coffee shops near Union Square:               │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  📍 Coffee Shops near Union Square                         │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │          [Interactive Map with Markers]              │   │ │
│ │  │     ☕                                               │   │ │
│ │  │        ☕    📍 You are here                         │   │ │
│ │  │    ☕      ☕                                         │   │ │
│ │  │              ☕                                       │   │ │
│ │  │   [+] [-]                                            │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  Selected: Blue Bottle Coffee                              │ │
│ │  ⭐ 4.6 · $$ · 0.2 mi · Open until 7 PM                    │ │
│ │  [Directions] [Call] [Website]                             │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ I found 12 coffee shops within 0.5 miles of Union Square.      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

Map component with Mapbox:

import { useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';

mapboxgl.accessToken = 'your-mapbox-token';

function CoffeeShopMap() {
  const mapContainer = useRef(null);
  const map = useRef(null);
  const [selectedShop, setSelectedShop] = useState(null);

  const data = window.openai?.toolOutput;

  useEffect(() => {
    if (map.current || !data) return;

    map.current = new mapboxgl.Map({
      container: mapContainer.current,
      style: 'mapbox://styles/mapbox/streets-v12',
      center: [data.center.lng, data.center.lat],
      zoom: 14
    });

    // Add markers for each shop
    data.shops.forEach(shop => {
      const marker = new mapboxgl.Marker()
        .setLngLat([shop.lng, shop.lat])
        .addTo(map.current);

      marker.getElement().addEventListener('click', () => {
        setSelectedShop(shop);
      });
    });
  }, [data]);

  return (
    <div className="flex flex-col h-full">
      <div ref={mapContainer} className="flex-1 min-h-[300px]" />

      {selectedShop && (
        <div className="p-4 border-t">
          <h3 className="font-bold">{selectedShop.name}</h3>
          <p>{selectedShop.rating} · {selectedShop.distance} mi</p>
          <div className="flex gap-2 mt-2">
            <Button onClick={() => openDirections(selectedShop)}>
              Directions
            </Button>
            <Button onClick={() => window.openai?.callTool('get_shop_details', { id: selectedShop.id })}>
              Details
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}

MCP tool for location search:

@mcp.tool()
def find_nearby_places(
    query: str,
    location: str,
    radius_miles: float = 1.0,
    limit: int = 20
) -> dict:
    """Use this when user wants to find places near a location."""
    # Geocode the location
    coords = geocode(location)

    # Search for places
    places = search_places_api(
        query=query,
        lat=coords['lat'],
        lng=coords['lng'],
        radius=radius_miles * 1609  # Convert to meters
    )

    return {
        "structuredContent": {
            "center": coords,
            "shops": places[:limit],
            "total": len(places),
            "query": query,
            "location": location
        },
        "uiComponent": {
            "type": "iframe",
            "url": "https://your-app.com/map-widget.html"
        }
    }

Learning milestones:

  1. Map renders with markers → You understand map integration
  2. Markers are interactive → You understand event handling
  3. Selection shows details → You understand component composition
  4. Actions trigger tools → You understand full interaction flow

Project 5: Form-Based Data Entry App

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/React
  • Alternative Programming Languages: Vue, Svelte
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Forms / Validation / Data Entry
  • Software or Tool: React Hook Form, Zod
  • Main Book: “Form Design Patterns” by Adam Silver

What you’ll build: A CRM-style app for creating and editing contacts/leads with multi-step forms, validation, and confirmation flows—demonstrating write operations in ChatGPT.

Why it teaches ChatGPT Apps: Many apps need to create or edit data. This project teaches form design, validation, destructive action handling, and the confirmation flows required by OpenAI’s guidelines.

Core challenges you’ll face:

  • Form validation in widgets → maps to client-side validation patterns
  • Handling destructive actions → maps to destructiveHint annotations
  • Multi-step forms → maps to widget state progression
  • Error handling → maps to graceful failure UX

Key Concepts:

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 1-3, form handling experience

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ ChatGPT                                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ You: Add a new lead named John Smith from Acme Corp            │
│                                                                 │
│ ChatGPT: I'll help you add this lead. Please complete the form: │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  ➕ Add New Lead                                            │ │
│ │                                                             │ │
│ │  Step 1 of 3: Basic Information                            │ │
│ │  ━━━━━━━━━━━━●○○                                           │ │
│ │                                                             │ │
│ │  First Name *                                               │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ John                                                 │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  Last Name *                                                │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ Smith                                                │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  Company                                                    │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ Acme Corp                                            │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │                              [Cancel]  [Next →]             │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

Form with validation:

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button, Input } from '@openai/apps-sdk-ui';

const leadSchema = z.object({
  firstName: z.string().min(1, "First name is required"),
  lastName: z.string().min(1, "Last name is required"),
  email: z.string().email("Invalid email").optional(),
  company: z.string().optional(),
  phone: z.string().optional(),
});

function LeadForm() {
  const [step, setStep] = useState(1);
  const prefill = window.openai?.toolInput;  // Pre-filled from ChatGPT

  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(leadSchema),
    defaultValues: prefill
  });

  const onSubmit = (data) => {
    // Call the create tool
    window.openai?.callTool('create_lead', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="p-4 space-y-4">
      <h2>➕ Add New Lead</h2>

      <div>
        <label>First Name *</label>
        <Input {...register('firstName')} />
        {errors.firstName && (
          <span className="text-red-500">{errors.firstName.message}</span>
        )}
      </div>

      <div>
        <label>Last Name *</label>
        <Input {...register('lastName')} />
        {errors.lastName && (
          <span className="text-red-500">{errors.lastName.message}</span>
        )}
      </div>

      <div>
        <label>Email</label>
        <Input type="email" {...register('email')} />
      </div>

      <div className="flex gap-2">
        <Button type="button" variant="secondary" onClick={handleCancel}>
          Cancel
        </Button>
        <Button type="submit">Create Lead</Button>
      </div>
    </form>
  );
}

MCP tool with destructive hint:

@mcp.tool(
    annotations={
        "readOnlyHint": False,
        "destructiveHint": False  # Create is not destructive
    }
)
def create_lead(
    first_name: str,
    last_name: str,
    email: str = None,
    company: str = None,
    phone: str = None
) -> dict:
    """Use this when user wants to create a new lead in the CRM."""
    lead = create_lead_in_database(first_name, last_name, email, company, phone)
    return {
        "structuredContent": {
            "success": True,
            "lead": lead,
            "message": f"Lead {first_name} {last_name} created successfully"
        }
    }

@mcp.tool(
    annotations={
        "readOnlyHint": False,
        "destructiveHint": True  # Delete IS destructive - requires confirmation
    }
)
def delete_lead(lead_id: str) -> dict:
    """Use this when user wants to delete a lead. This action is irreversible."""
    # ChatGPT will ask for confirmation before calling this
    delete_lead_from_database(lead_id)
    return {
        "structuredContent": {
            "success": True,
            "message": "Lead deleted successfully"
        }
    }

Learning milestones:

  1. Form renders with pre-filled data → You understand toolInput
  2. Validation shows errors → You understand client-side validation
  3. Submit calls create tool → You understand write operations
  4. Delete requires confirmation → You understand destructiveHint

Project 6: OAuth-Protected Integration App

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/Node.js
  • Alternative Programming Languages: Python (FastAPI)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 3: Advanced
  • Knowledge Area: OAuth 2.1 / Authentication / Security
  • Software or Tool: Passport.js, Auth0, or custom OAuth server
  • Main Book: “OAuth 2 in Action” by Justin Richer & Antonio Sanso

What you’ll build: A GitHub-connected app that uses OAuth to access the user’s repositories, list issues, create PRs—demonstrating the full authentication flow required for real integrations.

Why it teaches ChatGPT Apps: Real apps connect to user accounts. This project teaches OAuth 2.1 with PKCE, token management, and how ChatGPT’s authentication flow works.

Core challenges you’ll face:

  • Implementing OAuth server metadata → maps to well-known endpoints
  • Dynamic client registration → maps to DCR flow
  • Token validation → maps to JWT verification
  • Handling auth failures → maps to WWW-Authenticate responses

Key Concepts:

  • Apps SDK Auth Guide: Authentication Documentation
  • OAuth 2.1: Updated OAuth specification with PKCE
  • JWT Validation: Verifying tokens on every request
  • Auth0/Okta Integration: Using existing identity providers

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-5, OAuth experience helpful

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ ChatGPT                                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ You: Show my GitHub repositories                                │
│                                                                 │
│ ChatGPT: To access your GitHub repositories, you'll need to     │
│ connect your account.                                           │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  🔗 Connect to GitHub                                       │ │
│ │                                                             │ │
│ │  This app needs access to:                                  │ │
│ │  • View your repositories                                   │ │
│ │  • Read and write issues                                    │ │
│ │  • Create pull requests                                     │ │
│ │                                                             │ │
│ │              [Connect GitHub Account]                       │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ --- After connecting ---                                        │
│                                                                 │
│ You: Show my GitHub repositories                                │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  📦 Your Repositories (12)                                  │ │
│ │                                                             │ │
│ │  ⭐ my-awesome-project                                      │ │
│ │     TypeScript · Updated 2 hours ago · 3 open issues        │ │
│ │                                                             │ │
│ │  📁 dotfiles                                                │ │
│ │     Shell · Updated 1 week ago · 0 issues                   │ │
│ │                                                             │ │
│ │  [View on GitHub] [Show Issues] [Create Issue]              │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

OAuth metadata endpoint:

# /.well-known/oauth-protected-resource
@app.get("/.well-known/oauth-protected-resource")
def oauth_metadata():
    return {
        "resource": "https://your-mcp-server.com",
        "authorization_servers": [
            "https://your-auth-server.com"
        ],
        "bearer_methods_supported": ["header"],
        "scopes_supported": ["read:repos", "write:issues", "read:user"]
    }

Dynamic Client Registration:

# Your auth server must support DCR
@app.post("/oauth/register")
def register_client(request: ClientRegistrationRequest):
    # ChatGPT registers itself as a client
    client_id = generate_client_id()
    client_secret = generate_client_secret() if request.token_endpoint_auth_method != "none" else None

    save_client(client_id, client_secret, request.redirect_uris)

    return {
        "client_id": client_id,
        "client_secret": client_secret,
        "redirect_uris": request.redirect_uris,
        "token_endpoint_auth_method": request.token_endpoint_auth_method
    }

Token validation on every request:

from jose import jwt

def validate_token(token: str) -> dict:
    try:
        # Verify signature
        payload = jwt.decode(
            token,
            get_public_key(),
            algorithms=["RS256"],
            audience="your-mcp-server",
            issuer="https://your-auth-server.com"
        )

        # Check expiration
        if payload["exp"] < time.time():
            raise HTTPException(status_code=401, detail="Token expired")

        # Check scopes
        if "read:repos" not in payload.get("scope", "").split():
            raise HTTPException(status_code=403, detail="Insufficient scope")

        return payload

    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@mcp.tool(
    annotations={"securitySchemes": ["oauth2"]}
)
def list_repositories(token: str = Header(...)) -> dict:
    """Use this when user wants to see their GitHub repositories."""
    user = validate_token(token)

    repos = fetch_github_repos(user["github_token"])
    return {"structuredContent": {"repositories": repos}}

Handling auth failures:

@app.exception_handler(HTTPException)
def auth_exception_handler(request, exc):
    if exc.status_code == 401:
        return JSONResponse(
            status_code=401,
            content={
                "error": "unauthorized",
                "_meta": {
                    "mcp/www_authenticate": {
                        "scheme": "Bearer",
                        "realm": "github-integration",
                        "error": "invalid_token"
                    }
                }
            }
        )
    raise exc

Learning milestones:

  1. OAuth metadata is served → You understand discovery
  2. DCR creates clients → You understand dynamic registration
  3. Auth flow completes → You understand the full OAuth dance
  4. Protected tools work → You understand token validation

Project 7: Real-Time Dashboard App

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/React
  • Alternative Programming Languages: Vue, Svelte
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Data Visualization / Real-Time Updates
  • Software or Tool: Recharts, D3.js, WebSockets
  • Main Book: “The Visual Display of Quantitative Information” by Edward Tufte

What you’ll build: An analytics dashboard showing real-time metrics, charts, and KPIs—demonstrating data visualization and how to handle updating data in ChatGPT widgets.

Why it teaches ChatGPT Apps: Dashboards are a common use case. This project teaches charting, real-time updates, and how to present complex data in the widget format.

Core challenges you’ll face:

  • Charting in iframe constraints → maps to library compatibility
  • Real-time data updates → maps to polling vs. push strategies
  • Responsive chart sizing → maps to widget dimension handling
  • Data aggregation → maps to backend analytics patterns

Key Concepts:

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-5, data visualization experience

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ ChatGPT                                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ You: Show me our website analytics for this week                │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  📊 Analytics Dashboard - Dec 14-20, 2025                   │ │
│ │                                                    [⛶ Full] │ │
│ │                                                             │ │
│ │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐            │ │
│ │  │  👁️ Views   │ │ 👤 Visitors │ │ ⏱️ Avg Time │            │ │
│ │  │   45,231    │ │   12,847    │ │   3m 42s    │            │ │
│ │  │   ↑ 12.3%   │ │   ↑ 8.7%   │ │   ↓ 5.2%   │            │ │
│ │  └─────────────┘ └─────────────┘ └─────────────┘            │ │
│ │                                                             │ │
│ │  Daily Visitors                                             │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │    ▄                                                 │   │ │
│ │  │   ▄█▄     ▄                                         │   │ │
│ │  │  ▄███    ▄█▄    ▄▄                                  │   │ │
│ │  │ ▄████▄  ▄███▄  ▄██▄  ▄▄                             │   │ │
│ │  │▄██████▄▄█████▄▄████▄▄██▄▄                           │   │ │
│ │  │ Mon Tue Wed Thu Fri Sat Sun                         │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  [📊 More Charts]  [📥 Export]  [🔄 Refresh]                │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ This week saw 45,231 page views with a 12.3% increase from      │
│ last week. Your most popular page was /products.                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

Dashboard with Recharts:

import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { Button } from '@openai/apps-sdk-ui';

function AnalyticsDashboard() {
  const data = window.openai?.toolOutput;
  const [isFullscreen, setIsFullscreen] = useState(false);

  const handleFullscreen = () => {
    if (isFullscreen) {
      window.openai?.exitFullscreen();
    } else {
      window.openai?.requestFullscreen();
    }
    setIsFullscreen(!isFullscreen);
  };

  return (
    <div className="p-4">
      <div className="flex justify-between items-center mb-4">
        <h2>📊 Analytics Dashboard</h2>
        <Button onClick={handleFullscreen}>
          {isFullscreen ? '↙️ Exit' : '⛶ Full'}
        </Button>
      </div>

      {/* KPI Cards */}
      <div className="grid grid-cols-3 gap-4 mb-6">
        <KPICard
          title="Views"
          value={data.totalViews}
          change={data.viewsChange}
          icon="👁️"
        />
        <KPICard
          title="Visitors"
          value={data.uniqueVisitors}
          change={data.visitorsChange}
          icon="👤"
        />
        <KPICard
          title="Avg Time"
          value={formatDuration(data.avgSessionTime)}
          change={data.timeChange}
          icon="⏱️"
        />
      </div>

      {/* Chart */}
      <div className="h-64">
        <h3>Daily Visitors</h3>
        <ResponsiveContainer width="100%" height="100%">
          <BarChart data={data.dailyData}>
            <XAxis dataKey="day" />
            <YAxis />
            <Tooltip />
            <Bar dataKey="visitors" fill="#3b82f6" />
          </BarChart>
        </ResponsiveContainer>
      </div>

      <div className="flex gap-2 mt-4">
        <Button onClick={() => window.openai?.callTool('get_detailed_analytics', {})}>
          📊 More Charts
        </Button>
        <Button onClick={() => window.openai?.callTool('export_analytics', { format: 'csv' })}>
          📥 Export
        </Button>
      </div>
    </div>
  );
}

MCP tool for analytics:

@mcp.tool()
def get_analytics(
    start_date: str,
    end_date: str,
    metrics: list[str] = ["views", "visitors", "session_time"]
) -> dict:
    """Use this when user wants to see website analytics."""
    analytics = fetch_analytics_data(start_date, end_date, metrics)

    return {
        "structuredContent": {
            "totalViews": analytics.total_views,
            "viewsChange": calculate_change(analytics.views, analytics.prev_views),
            "uniqueVisitors": analytics.unique_visitors,
            "visitorsChange": calculate_change(analytics.visitors, analytics.prev_visitors),
            "avgSessionTime": analytics.avg_session_time,
            "timeChange": calculate_change(analytics.time, analytics.prev_time),
            "dailyData": [
                {"day": "Mon", "visitors": 1800},
                {"day": "Tue", "visitors": 2100},
                # ...
            ]
        },
        "uiComponent": {
            "type": "iframe",
            "url": "https://your-app.com/analytics-dashboard.html"
        }
    }

Learning milestones:

  1. Charts render correctly → You understand visualization in widgets
  2. Fullscreen toggle works → You understand display modes
  3. KPIs update from data → You understand dynamic content
  4. Export/refresh work → You understand tool chaining

Project 8: E-Commerce Shopping App

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript/React
  • Alternative Programming Languages: Vue, Next.js
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 3: Advanced
  • Knowledge Area: E-Commerce / Cart / Checkout
  • Software or Tool: Stripe (for demo), Cart state management
  • Main Book: “Designing Web Usability” by Jakob Nielsen

What you’ll build: A complete shopping experience: browse products, add to cart, view cart, and checkout—demonstrating the Shop component pattern and commerce restrictions.

Why it teaches ChatGPT Apps: E-commerce is a primary use case for ChatGPT Apps (physical goods only, per guidelines). This project teaches cart state, multi-step flows, and commerce compliance.

Core challenges you’ll face:

  • Cart state persistence → maps to widgetSessionId pattern
  • Multi-widget checkout flow → maps to tool chaining
  • Commerce restrictions → maps to OpenAI guidelines compliance
  • Order confirmation → maps to destructive action patterns

Key Concepts:

  • Shopping Cart Example: apps-sdk-examples
  • Widget Session ID: Persisting cart across widget instances
  • Commerce Guidelines: Physical goods only, no digital products
  • Stripe Integration: Payment handling (demo mode)

Difficulty: Advanced Time estimate: 3-4 weeks Prerequisites: Projects 1-7, e-commerce experience helpful

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ You: I want to buy some running shoes                           │
│                                                                 │
│ [Product search widget showing shoes]                           │
│                                                                 │
│ You: Add the Nike Pegasus to my cart                            │
│                                                                 │
│ ChatGPT: Added to cart! Here's your current cart:               │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  🛒 Your Cart (1 item)                                      │ │
│ │                                                             │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ [IMG] Nike Air Zoom Pegasus 40                       │   │ │
│ │  │       Size: 10 · Color: Black                        │   │ │
│ │  │       $89.99                          [- 1 +] [🗑️]   │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  ───────────────────────────────────────────────────────   │ │
│ │  Subtotal:                                     $89.99      │ │
│ │  Shipping:                                      $5.99      │ │
│ │  Tax:                                           $7.92      │ │
│ │  ───────────────────────────────────────────────────────   │ │
│ │  Total:                                       $103.90      │ │
│ │                                                             │ │
│ │  [Continue Shopping]           [Proceed to Checkout →]     │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ You: Checkout                                                   │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  📦 Checkout                                                │ │
│ │                                                             │ │
│ │  Shipping Address:                                          │ │
│ │  ┌─────────────────────────────────────────────────────┐   │ │
│ │  │ John Doe                                             │   │ │
│ │  │ 123 Main St, Apt 4                                   │   │ │
│ │  │ San Francisco, CA 94102                              │   │ │
│ │  └─────────────────────────────────────────────────────┘   │ │
│ │                                                             │ │
│ │  Payment: •••• 4242                       [Change]          │ │
│ │                                                             │ │
│ │                                    [← Back] [Place Order]   │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Implementation Hints:

Cart state with widgetSessionId:

function ShoppingCart() {
  // widgetSessionId persists across widget instances in same conversation
  const sessionId = window.openai?.widgetSessionId;
  const [cart, setCart] = useState([]);

  useEffect(() => {
    // Load cart from server using session ID
    if (sessionId) {
      fetch(`/api/cart/${sessionId}`)
        .then(res => res.json())
        .then(setCart);
    }
  }, [sessionId]);

  const updateQuantity = async (itemId, quantity) => {
    // Update on server
    await fetch(`/api/cart/${sessionId}/items/${itemId}`, {
      method: 'PATCH',
      body: JSON.stringify({ quantity })
    });

    // Refresh cart
    const updated = await fetch(`/api/cart/${sessionId}`).then(r => r.json());
    setCart(updated);
  };

  const checkout = () => {
    window.openai?.callTool('start_checkout', {
      sessionId,
      items: cart.items
    });
  };

  // ... render cart UI
}

MCP tools for shopping:

@mcp.tool()
def add_to_cart(
    session_id: str,
    product_id: str,
    quantity: int = 1,
    size: str = None,
    color: str = None
) -> dict:
    """Use this when user wants to add a product to their cart."""
    cart = get_or_create_cart(session_id)
    cart.add_item(product_id, quantity, size, color)

    return {
        "structuredContent": {
            "success": True,
            "cart": cart.to_dict(),
            "message": f"Added {quantity} item(s) to cart"
        },
        "uiComponent": {
            "type": "iframe",
            "url": f"https://your-app.com/cart.html?session={session_id}"
        }
    }

@mcp.tool(
    annotations={
        "readOnlyHint": False,
        "destructiveHint": True  # Placing order is irreversible
    }
)
def place_order(
    session_id: str,
    shipping_address_id: str,
    payment_method_id: str
) -> dict:
    """Use this when user confirms they want to place their order."""
    cart = get_cart(session_id)
    order = create_order(cart, shipping_address_id, payment_method_id)

    # Clear cart after successful order
    clear_cart(session_id)

    return {
        "structuredContent": {
            "success": True,
            "orderId": order.id,
            "total": order.total,
            "estimatedDelivery": order.estimated_delivery,
            "message": "Order placed successfully!"
        }
    }

Learning milestones:

  1. Cart persists across turns → You understand widgetSessionId
  2. Add/remove items work → You understand cart operations
  3. Checkout flow completes → You understand multi-step processes
  4. Order confirmation works → You understand commerce patterns

Project 9: Full App Store Submission

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript + Python
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 5. The “Industry Disruptor”
  • Difficulty: Level 4: Expert
  • Knowledge Area: Production Deployment / App Store
  • Software or Tool: Vercel/Railway, monitoring, analytics
  • Main Book: “Release It!” by Michael Nygard

What you’ll build: Take one of your previous projects to production quality: proper error handling, logging, monitoring, tests, documentation, and submit to the ChatGPT app store.

Why it teaches ChatGPT Apps: The final step is shipping to real users. This project teaches production readiness, the review process, and reaching 800+ million ChatGPT users.

Core challenges you’ll face:

  • Meeting submission guidelines → maps to quality bar requirements
  • Developer verification → maps to identity and contact info
  • Error handling and reliability → maps to production resilience
  • Review process → maps to addressing feedback

Key Concepts:

  • Submission Guidelines: App Submission Guidelines
  • What Makes a Great App: Best Practices
  • Developer Verification: OpenAI Platform Dashboard
  • Production Monitoring: Error tracking, uptime monitoring

Difficulty: Expert Time estimate: 2-4 weeks Prerequisites: All previous projects, production deployment experience

Real world outcome:

App Submission Checklist ✓
═══════════════════════════════════════════════════

PRE-SUBMISSION
══════════════
✓ Developer identity verified on OpenAI Platform
✓ Customer support email configured
✓ Privacy policy URL active
✓ Terms of service URL active

FUNCTIONALITY
═════════════
✓ App serves clear purpose beyond native ChatGPT
✓ All tools work reliably without crashes
✓ Error states handled gracefully
✓ Loading states shown appropriately
✓ No demo/placeholder content

TOOL QUALITY
════════════
✓ Tool names are descriptive verbs
✓ Descriptions start with "Use this when..."
✓ Parameters use appropriate types/enums
✓ Annotations correctly mark read-only vs destructive
✓ Outputs are structured and reusable

SECURITY
════════
✓ OAuth 2.1 implemented correctly (if applicable)
✓ Token validation on every request
✓ No sensitive data in tool outputs
✓ Minimal data collection
✓ No payment card info, health data, or API keys

COMMERCE (if applicable)
════════════════════════
✓ Physical goods only
✓ No prohibited items
✓ Clear pricing displayed

TESTING
═══════
✓ Tested on ChatGPT web
✓ Tested on ChatGPT mobile
✓ Tested both direct and indirect prompts
✓ Tested error scenarios
✓ Tested with real user accounts

SUBMISSION
══════════
✓ App name and description written
✓ Icon/logo uploaded
✓ Category selected
✓ Screenshots captured

[Submit for Review]

───────────────────────────────────────────────────

Review Status: Under Review
Submitted: Dec 20, 2025
Expected response: 5-10 business days

Update (Dec 24, 2025):
  ✓ App Approved!
  🎉 Now available to 800M+ ChatGPT users

Implementation Hints:

Submission preparation checklist:

1. VERIFY DEVELOPER IDENTITY
   - Go to platform.openai.com
   - Complete identity verification
   - Add customer support email

2. PREPARE METADATA
   - App name (unique, descriptive)
   - Short description (1-2 sentences)
   - Long description (features, use cases)
   - Category selection
   - Icon (512x512px)
   - Screenshots (1280x720px)

3. LEGAL REQUIREMENTS
   - Privacy policy URL
   - Terms of service URL
   - Third-party license compliance

4. QUALITY ASSURANCE
   - Test all tools thoroughly
   - Verify error handling
   - Check mobile compatibility
   - Test authentication flows
   - Validate destructive actions

Common rejection reasons to avoid:

1. TOOL ANNOTATIONS
   ✗ Missing readOnlyHint/destructiveHint
   ✗ Incorrect annotations (marking delete as read-only)

2. RELIABILITY
   ✗ Crashes or hangs
   ✗ Inconsistent behavior
   ✗ Unhandled errors

3. QUALITY
   ✗ Demo/placeholder content
   ✗ Incomplete features
   ✗ Misleading descriptions

4. SECURITY
   ✗ Missing OAuth token validation
   ✗ Overly broad data requests
   ✗ Sensitive data exposure

5. COMPLIANCE
   ✗ Digital products for sale
   ✗ Age-inappropriate content
   ✗ Prohibited items

Production infrastructure:

# Example deployment architecture

Frontend (Widget):
  - Vercel / Cloudflare Pages
  - CDN for assets
  - Error tracking (Sentry)

Backend (MCP Server):
  - Railway / Render / AWS
  - Auto-scaling
  - Health checks
  - Logging (Datadog/Logtail)

Monitoring:
  - Uptime monitoring
  - Error rate alerts
  - Response time tracking
  - Usage analytics

Learning milestones:

  1. All guidelines met → You understand quality requirements
  2. Submission accepted → You pass initial review
  3. Feedback addressed → You handle revision requests
  4. App published → You’ve reached 800M+ users!

Project Comparison Table

Project Difficulty Time Depth of Understanding Fun Factor
1. MCP Protocol Explorer Beginner Weekend ⭐⭐⭐ ⭐⭐⭐
2. Hello World Widget Beginner Weekend ⭐⭐⭐ ⭐⭐⭐⭐
3. List & Search App Intermediate 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
4. Map & Location App Intermediate 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
5. Form-Based Data Entry Intermediate 1-2 weeks ⭐⭐⭐ ⭐⭐⭐
6. OAuth Integration Advanced 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐
7. Real-Time Dashboard Advanced 2-3 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
8. E-Commerce App Advanced 3-4 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
9. App Store Submission Expert 2-4 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

If you’re new to ChatGPT Apps:

Start with: Project 1 → 2 → 3

This path teaches MCP fundamentals, then your first widget, then list patterns—the foundation for any app.

If you’re a frontend developer:

Start with: Project 2 → 3 → 4 → 7

Focus on widget development, skipping deeper backend topics initially.

If you’re a backend developer:

Start with: Project 1 → 6 → 5 → 8

Focus on MCP servers, authentication, and data operations.

If you want to publish an app ASAP:

Fast path: Projects 1 → 2 → 3 → 9

Build fundamentals quickly, then polish one app for submission.


Final Capstone Project: AI-Powered Productivity Suite

  • File: LEARN_CHATGPT_APPS_DEEP_DIVE.md
  • Main Programming Language: TypeScript + Python
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 5. The “Industry Disruptor”
  • Difficulty: Level 5: Master
  • Knowledge Area: Full-Stack AI Integration
  • Software or Tool: All previous + AI APIs
  • Main Book: “Designing AI Products” + all previous

What you’ll build: A comprehensive productivity app connecting multiple services (calendar, email, tasks, notes) with AI-powered features like smart scheduling, email drafting, and task prioritization—the ultimate ChatGPT app.

Why this is the capstone: This combines everything: OAuth for multiple services, complex UIs, real-time updates, forms, commerce (for premium features), and production deployment.


Summary

# Project Main Language
1 MCP Protocol Explorer Python
2 Hello World Widget TypeScript/React
3 Interactive List & Search App TypeScript/React
4 Map & Location-Based App TypeScript/React
5 Form-Based Data Entry App TypeScript/React
6 OAuth-Protected Integration App TypeScript/Node.js
7 Real-Time Dashboard App TypeScript/React
8 E-Commerce Shopping App TypeScript/React
9 Full App Store Submission TypeScript + Python
Capstone AI-Powered Productivity Suite Full Stack

Additional Resources

Official Documentation

GitHub Repositories

Model Context Protocol

Tutorials

Community


Generated for your ChatGPT Apps learning journey. Build something amazing and reach 800 million users!