Capabilities Model
Not all features are available in every host environment. PancakeJS provides a capabilities system for graceful degradation and feature detection.
Available Capabilities
| Capability | MCP Apps | ChatGPT Apps | Description |
|---|---|---|---|
followups | ✅ | ⚠️* | Send follow-up prompts |
modals | ❌ | ❌ | Open modal dialogs |
files | ❌ | ❌ | Access file system |
theme | ✅ | ✅ | Get theme (light/dark) |
locale | ✅ | ✅ | Get user locale |
persistentState | ✅ | ⚠️* | Persist widget state |
*May vary based on host implementation
Checking Capabilities
Using useHost()
function MyWidget() {
const host = useHost();
return (
<div>
{host.capabilities.followups && (
<button onClick={() => sendFollowUp('...')}>
Continue Conversation
</button>
)}
</div>
);
}Using useCapability()
function MyWidget() {
const hasFollowups = useCapability('followups');
if (hasFollowups) {
// Feature available
}
}Using Specific Hooks
Capability-specific hooks return support status:
function MyWidget() {
const followUp = useFollowUp();
if (followUp.supported) {
await followUp.send('Tell me more about this');
} else {
// Show alternative UI or message
}
}Strict Mode
Enable strict mode to catch unsupported capability usage during development:
<UniversalAppProvider strict={true}>
<MyWidget />
</UniversalAppProvider>In strict mode, using an unsupported capability throws an error:
CapabilityNotSupportedError: Capability "followups" is not supported by chatgptGraceful Degradation Patterns
Conditional Rendering
function FollowUpButton() {
const followUp = useFollowUp();
if (!followUp.supported) {
return null; // Don't render
}
return (
<button onClick={() => followUp.send('...')}>
Ask More
</button>
);
}Alternative UI
function ActionButton() {
const followUp = useFollowUp();
if (followUp.supported) {
return (
<button onClick={() => followUp.send('...')}>
Ask AI
</button>
);
}
// Fallback: copy to clipboard
return (
<button onClick={() => copyToClipboard('...')}>
Copy Response
</button>
);
}Feature Flags
function MyWidget() {
const host = useHost();
const features = {
canAskFollowups: host.capabilities.followups,
canPersistState: host.capabilities.persistentState,
hasTheme: host.capabilities.theme,
};
return <WidgetContent features={features} />;
}Theme Support
Access the host's theme:
function MyWidget() {
const theme = useTheme('light'); // default if not provided
return (
<div className={theme === 'dark' ? 'dark-mode' : 'light-mode'}>
{/* Content */}
</div>
);
}Or use useHost():
function MyWidget() {
const host = useHost();
return (
<div style={{
background: host.context.theme === 'dark' ? '#1a1a1a' : '#ffffff',
color: host.context.theme === 'dark' ? '#ffffff' : '#000000',
}}>
{/* Content */}
</div>
);
}Locale Support
Access the user's locale:
function MyWidget() {
const locale = useLocale('en'); // default if not provided
return (
<div>
<FormattedDate locale={locale} date={new Date()} />
</div>
);
}Adding New Capabilities
When new host features become available, the SDK will add them to the capabilities model. Check the changelog for updates.
Your widgets can gracefully handle new capabilities by defaulting to false:
// Future-proof capability check
const hasNewFeature = host.capabilities.newFeature ?? false;Best Practices
- Always check before using — Don't assume a capability exists
- Provide fallbacks — Give users an alternative when features aren't available
- Use strict mode in dev — Catch issues before production
- Test in both harnesses — Verify behavior in MCP and ChatGPT environments
Next Steps
- Auto-Bridge — Understand platform detection
- Troubleshooting — Debug capability issues