ctx is the entire platform surface a server-side handler gets. There is no raw database client, no Supabase, no request object. Every field is scoped and authorized before your code runs.
The context object
Storage
Access to schema-validated per-account and per-user state:get returns null for missing keys and expired entries. User scope throws for machine actors.
Secrets
Named credentials resolved by the host across its tiers:set() writes only to the account tier for this {account, product}. Source- and global-tier credentials are admin-issued and throw SecretAccessError if you try to write them.
Fetch
HTTP client that refuses anything outside youregress allowlist:
EgressDeniedError synchronously, before opening a socket. Redirect targets are also checked.
Logger
Structured logging with pre-bound context:{integration, accountId, actor} to every line.
Actor
Who is calling — a human user or a machine (workflow):ctx.storage.user call throws.
Identity fields
All read-only and already authorized:Common patterns
Logging with context
Always include relevant IDs in the context object:Handling missing credentials
Not configured is an expected state — log and skip, don’t fail:Error handling
Log before throwing so errors reach your observability:Where ctx is available
- Tools —
tools[].run(ctx, args) - Webhooks —
webhooks[].handler(ctx, event) - Schedules —
schedules[].handler(ctx, invocation) - Activation hooks —
onActivate(ctx) - Actions —
actions[name].handler(ctx, input) - OAuth —
oauth.onTokens(ctx, tokens)
{account, integration}.