
A zero-boilerplate framework for building interactive ChatGPT widgets
📚 Documentation: https://www.fastapps.org/
👥 Community: Join Our Discord
python -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
pip install fastapps
fastapps init my-app
This generates the complete project structure:
my-app/
├── server/
│ ├── __init__.py
│ ├── main.py # Auto-discovery server
│ ├── tools/ # Widget backends
│ │ └── __init__.py
│ └── api/ # (optional) Shared APIs
│ └── __init__.py
├── widgets/ # Widget frontends (empty initially)
├── requirements.txt # Python dependencies
├── package.json # JavaScript dependencies
├── .gitignore
└── README.md
cd my-app
pip install -r requirements.txt
npm install
fastapps create my-widget
This adds to your project:
my-app/
├── server/
│ └── tools/
│ └── my_widget_tool.py # ← Generated: Widget backend
└── widgets/
└── my-widget/
└── index.jsx # ← Generated: Widget frontend
You only need to edit these 2 files:
from fastapps import BaseWidget, Field, ConfigDict
from pydantic import BaseModel
from typing import Dict, Any
class MyWidgetInput(BaseModel):
model_config = ConfigDict(populate_by_name=True)
name: str = Field(default="World")
class MyWidgetTool(BaseWidget):
identifier = "my-widget"
title = "My Widget"
input_schema = MyWidgetInput
invoking = "Processing..."
invoked = "Done!"
widget_csp = {
"connect_domains": [], # APIs you'll call
"resource_domains": [] # Images/fonts you'll use
}
async def execute(self, input_data: MyWidgetInput) -> Dict[str, Any]:
# Your logic here
return {
"name": input_data.name,
"message": f"Hello, {input_data.name}!"
}
import React from 'react';
import { useWidgetProps } from 'fastapps';
export default function MyWidget() {
const props = useWidgetProps();
return (
<div style={{
padding: '40px',
textAlign: 'center',
background: '#4A90E2',
color: 'white',
borderRadius: '12px'
}}>
<h1>{props.message}</h1>
<p>Welcome, {props.name}!</p>
</div>
);
}
That's it! These are the only files you need to write.
npm run build
Option A: Using fastapps dev
(Recommended)
The easiest way to run and expose your server:
fastapps dev
On first run, you'll be prompted for your ngrok auth token:
- Get it free at: https://dashboard.ngrok.com/get-started/your-authtoken
- Token is saved and won't be asked again
You'll see:
🚀 FastApps Development Server
┌─────────┬─────────────────────────┐
│ Local │ http://0.0.0.0:8001 │
│ Public │ https://xyz.ngrok.io │
└─────────┴─────────────────────────┘
📡 MCP Server Endpoint: https://xyz.ngrok.io
Use the public URL in ChatGPT Settings > Connectors.
Option B: Manual Setup
# Start server
python server/main.py
# In a separate terminal, create tunnel
ngrok http 8001
Every widget has exactly 2 files you write:
-
Python Tool (
server/tools/*_tool.py
)- Define inputs with Pydantic
- Write your logic in
execute()
- Return data as a dictionary
-
React Component (
widgets/*/index.jsx
)- Get data with
useWidgetProps()
- Render your UI
- Use inline styles
- Get data with
Everything else is automatic:
- Widget discovery
- Registration
- Build process
- Server setup
- Mounting logic
from fastapps import Field, ConfigDict
from pydantic import BaseModel
class MyInput(BaseModel):
model_config = ConfigDict(populate_by_name=True)
name: str = Field(default="", description="User's name")
age: int = Field(default=0, ge=0, le=150)
email: str = Field(default="", pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
Allow external resources:
widget_csp = {
"connect_domains": ["https://api.example.com"], # For API calls
"resource_domains": ["https://cdn.example.com"] # For images/fonts
}
import { useWidgetProps, useWidgetState, useOpenAiGlobal } from 'fastapps';
function MyWidget() {
const props = useWidgetProps(); // Data from Python
const [state, setState] = useWidgetState({}); // Persistent state
const theme = useOpenAiGlobal('theme'); // ChatGPT theme
return <div>{props.message}</div>;
}
- Quick Start Guide - Detailed setup instructions
- Tutorial - Step-by-step widget examples
- Python API - Programmatic dev server control
- API Reference - Complete API documentation
- Examples - Real-world code examples
# Initialize new project
fastapps init my-app
# Create new widget (auto-generates both files)
fastapps create mywidget
# Start development server with ngrok tunnel
fastapps dev
# Start on custom port
fastapps dev --port 8080
# Reset ngrok auth token
fastapps reset-token
# View authentication guide
fastapps auth-info
Tip: If fastapps
command is not found, use:
python -m fastapps.cli.main <command>
When you run python -m fastapps.cli.main create my-widget
, you get:
my-app/
├── server/
│ ├── __init__.py
│ ├── main.py # Already setup (no edits needed)
│ ├── tools/
│ │ ├── __init__.py
│ │ └── my_widget_tool.py # ← Edit this: Your widget logic
│ └── api/ # (optional: for shared APIs)
│
├── widgets/
│ └── my-widget/
│ └── index.jsx # ← Edit this: Your UI
│
├── assets/ # Auto-generated during build
│ ├── my-widget-HASH.html
│ └── my-widget-HASH.js
│
├── requirements.txt # Python dependencies
├── package.json # JavaScript dependencies
└── build-all.mts # Auto-copied from fastapps
You only edit the 2 files marked with ←
- Zero Boilerplate - Just write your widget code
- Auto-Discovery - Widgets automatically registered
- Type-Safe - Pydantic for Python, TypeScript for React
- CLI Tools - Scaffold widgets instantly
- Python API - Programmatic server control
- ngrok Integration - Public URLs with one command
- React Hooks - Modern React patterns via
fastapps
- MCP Protocol - Native ChatGPT integration
Start dev servers programmatically:
from fastapps import start_dev_server
# Simple usage
start_dev_server()
# With configuration
start_dev_server(
port=8080,
auto_reload=True,
ngrok_token="your_token"
)
# Get server info without starting
from fastapps import get_server_info
info = get_server_info(port=8001)
print(f"Public URL: {info.public_url}")
See Python API Documentation for more details.
# server/tools/hello_tool.py
class HelloTool(BaseWidget):
identifier = "hello"
title = "Hello"
input_schema = HelloInput
async def execute(self, input_data):
return {"message": "Hello World!"}
// widgets/hello/index.jsx
export default function Hello() {
const props = useWidgetProps();
return <h1>{props.message}</h1>;
}
async def execute(self, input_data):
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
data = response.json()
return {"data": data}
function Counter() {
const [state, setState] = useWidgetState({ count: 0 });
return (
<button onClick={() => setState({ count: state.count + 1 })}>
Count: {state.count}
</button>
);
}
Real-World Use Cases:
Use Case | Scenario | Benefits |
---|---|---|
🤖 ChatGPT Development | Develop ChatGPT custom actions locally | Test widgets in ChatGPT without deployment |
🪝 Webhook Testing | Test webhooks from Stripe, GitHub, Slack | Get real webhook events on localhost |
👥 Remote Collaboration | Share your local dev server with team | Instant demos without pushing code |
📱 Mobile Testing | Test mobile apps against local backend | Access localhost from phone/tablet |
🔌 API Integration | Third-party APIs need public callback URLs | Receive OAuth callbacks locally |
🏢 Client Demos | Show work-in-progress to clients | Professional public URL instantly |
🎓 Workshops/Teaching | Students access instructor's local server | Share examples in real-time |
🌐 Cross-Browser Testing | Test on BrowserStack, Sauce Labs | Cloud browsers access your localhost |
🔄 CI/CD Preview | Preview branches before deployment | Test PRs with temporary URLs |
🛠️ IoT Development | IoT devices callback to local server | Hardware talks to dev environment |
from fastapps import start_dev_server
# Start with automatic ngrok tunnel
start_dev_server(port=8001)
# With custom configuration
start_dev_server(
port=8080,
ngrok_token="your_token",
auto_reload=True
)
from fastapps import get_server_info
# Create tunnel and get URLs
info = get_server_info(port=8001)
print(f"Local: {info.local_url}")
print(f"Public: {info.public_url}")
# Use in integration tests
import requests
response = requests.get(f"{info.public_url}/health")
import os
from fastapps import start_dev_server
# Get token from environment (great for CI/CD)
token = os.getenv("NGROK_TOKEN")
start_dev_server(ngrok_token=token)
from fastapps import start_dev_server, DevServerError, NgrokError
try:
start_dev_server(port=8001)
except NgrokError as e:
print(f"ngrok tunnel failed: {e}")
except DevServerError as e:
print(f"Server error: {e}")
#!/usr/bin/env python3
from fastapps import start_dev_server
import sys
if __name__ == "__main__":
try:
print("🚀 Starting FastApps with ngrok...")
start_dev_server(
port=8001,
auto_reload=True,
log_level="info"
)
except KeyboardInterrupt:
print("\n✅ Server stopped")
sys.exit(0)
Scenario 1: ChatGPT Custom Action Development
# Start server for ChatGPT testing
from fastapps import start_dev_server
# Auto-reload when you edit widgets
start_dev_server(
port=8001,
auto_reload=True # Restart on code changes
)
# Copy ngrok URL → ChatGPT Settings → Connectors → Add your URL
# Test widgets live in ChatGPT while developing!
Scenario 2: Webhook Testing (Stripe, GitHub, etc.)
from fastapps import get_server_info
# Get public URL for webhook configuration
info = get_server_info(port=8001)
print(f"Configure webhook URL: {info.public_url}/webhooks")
# Add this URL to Stripe Dashboard → Webhooks
# Receive real webhook events on your localhost!
Scenario 3: Team Demo / Client Preview
from fastapps import start_dev_server
# Share work-in-progress with team
start_dev_server(port=8001)
# Send the ngrok URL to your team/client
# They can access your local server instantly!
# No deployment needed
Scenario 4: Mobile App Testing
from fastapps import get_server_info
info = get_server_info(port=8001)
# Use public URL in your mobile app config
print(f"API Base URL for mobile app: {info.public_url}")
# Test your iOS/Android app against local backend
# No need for deployed staging server
Scenario 5: OAuth Callback (Third-Party APIs)
from fastapps import start_dev_server
# OAuth providers need public callback URL
start_dev_server(port=8001)
# Register callback: https://your-ngrok-url.ngrok.io/auth/callback
# Test OAuth flow locally:
# 1. User clicks "Login with Google"
# 2. Google redirects to your ngrok URL
# 3. Your local server receives the callback
Scenario 6: CI/CD Integration Testing
# .github/workflows/test.yml
import os
from fastapps import start_dev_server
import threading
import requests
# Start server in background
def run_server():
start_dev_server(
port=8001,
ngrok_token=os.getenv("NGROK_TOKEN")
)
# Run server in separate thread
server_thread = threading.Thread(target=run_server, daemon=True)
server_thread.start()
# Run integration tests against public URL
# Perfect for testing webhooks, OAuth, etc. in CI
Scenario 7: IoT/Embedded Device Testing
from fastapps import get_server_info
# IoT devices need to callback to your server
info = get_server_info(port=8001)
# Configure IoT device with this URL
print(f"Configure device callback: {info.public_url}/iot/callback")
# Your Raspberry Pi, Arduino, etc. can now reach your localhost!
Widget not loading?
- Check
identifier
matches folder name - Rebuild:
npm run build
- Restart:
python server/main.py
Import errors?
pip install --upgrade fastapps
npm install fastapps@latest
Need help? Check our docs or open an issue
We welcome contributions! Please see our contributing guidelines:
- Contributing Guide - How to contribute to FastApps
- Code Style Guide - Code formatting and style standards
- GitHub Workflows - CI/CD documentation
# Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/FastApps.git
cd FastApps
# Install development dependencies
pip install -e ".[dev]"
# Install pre-commit hooks
pip install pre-commit
pre-commit install
# Make changes and ensure they pass checks
black .
ruff check --fix .
pytest
# Submit a pull request
MIT © FastApps Team
- PyPI: https://pypi.org/project/fastapps/
- ChatJS Hooks: https://www.npmjs.com/package/fastapps
- GitHub: https://github.com/DooiLabs/FastApps
- MCP Spec: https://modelcontextprotocol.io/