The Integrations tab is a static catalogue (apps/server/config/integrations-registry.ts) built entirely on primitives you already have: edge-function secrets plus prebuilt edge functions. There is no hidden runtime — connecting an integration saves its secrets and, optionally, installs its function templates.
Catalogue
| Key | Category | What it gives you |
|---|---|---|
stripe | payments | Query customers, receive Stripe webhooks |
shopify | payments | Read orders from a store |
calendly | scheduling | List scheduled events, receive booking webhooks |
airtable | data | Read records from a base |
notion | data | Query a Notion database |
posthog | data | Send product analytics events from the backend |
algolia | data | Search and index records |
hubspot | data | Read and create CRM contacts |
slack | messaging | Post messages to a channel via incoming webhook |
discord | messaging | Post messages to a channel via webhook |
twilio | messaging | Send SMS |
telegram | messaging | Send messages from a bot, receive updates |
resend | Transactional email through Resend | |
sendgrid | Transactional email through SendGrid | |
postmark | Transactional email through Postmark | |
openai | ai | Call chat completions from an edge function |
anthropic | ai | Call Claude from an edge function |
github | devtools | List repo issues, receive repo webhooks |
linear | devtools | Query and create Linear issues |
sentry | devtools | Read unresolved issues from a project |
cloudflare | devtools | Purge the CDN cache for a zone |
Shape of an integration
interface Integration {
key: string
name: string
description: string
category: 'payments'|'scheduling'|'data'|'messaging'|'email'|'ai'|'devtools'
docsUrl: string
secrets: { name: string; label: string; placeholder: string; optional?: boolean }[]
functions: {
name: string; route: string; description: string
schedule?: string
readiness?: 'production' | 'example'
code: string
}[]
}Secrets are stored as edge-function secrets, so inside the sandbox they are read as env.NAME — e.g. env.STRIPE_SECRET_KEY. See Edge Functions.
A function template marked readiness: 'example' is a starting point, not a finished endpoint. It needs provider verification (signature checks) and your own business logic before it is safe in production. Templates marked production are complete as shipped.
Connected state
IntegrationsService.list() returns, per integration:
- each secret with
set: boolean— is it stored? - each function with
installed: booleanand its edge-functionidwhen installed, matched by route connected: truewhen every non-optional secret is set
Optional secrets never block the connected state.
Endpoints
| Method | Path | Action |
|---|---|---|
GET | /api/admin/integrations | Catalogue with status |
POST | /api/admin/integrations/:key/connect | Store secrets, optionally install functions |
DELETE | /api/admin/integrations/:key | Disconnect |
All three require adminOnly (apps/server/controllers/integrations.controller.ts).
Adding your own
There is no plugin loader: the catalogue is a TypeScript array. Adding an entry to INTEGRATIONS — with its secrets and its function templates — is the whole extension mechanism. Anything more custom is just an edge function you write yourself, with secrets you set directly.