definePlatform is the entry point for building your own provider. It takes a platform ID and a definition object, and returns a callable that:
- exposes a
.config()method for registering the provider onSpectrum() - accepts the app, a , or a for narrowing
- carries any
staticproperties you declare (like iMessage’s effect constants)
definePlatform. The optional static object is an additional definePlatform input whose properties are copied onto the returned provider.
Platform IDs
Platform IDs are stable, developer-facing identifiers. Spectrum exposes the ID onmessage.platform and __platform, and uses it for provider registration, webhook routing, telemetry, and environment-variable prefixes.
An ID must start with a lowercase letter and contain only lowercase letters, numbers, and single underscores between segments. In other words, it must match /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/. Examples include discord, whatsapp_business, and my_platform. definePlatform rejects invalid IDs instead of silently normalizing them, so names containing uppercase letters, spaces, or hyphens must be corrected by the platform author.
Shape
Field reference
Return a fromsend when the content produces a message. Return undefined for fire-and-forget signals.
PlatformDef
The full definition of a platform adapter.
PlatformDef
The full definition of a platform adapter.
Nested definition fields
Configure platform resolution, lifecycle hooks, schemas, and actions.
Nested definition fields
Configure platform resolution, lifecycle hooks, schemas, and actions.
Message direction
Records yielded frommessages are wrapped as inbound values. Records returned from send are wrapped as outbound messages. When a provider knows a record’s actual direction, it can set direction on the raw record and Spectrum uses it instead of the wrapping context:
direction, nested targets default to the outer record’s direction.
Event producers
Every event generator receives{ client, config, projectConfig, store } and returns an AsyncIterable. The signature is .
The core messages stream lives at the top level of the definition. Optional custom event streams (presence, read receipts, etc.) live inside events:
app.presence) and the narrowed platform instance (myPlatform(app).presence).
Message extras
Declare amessage.schema to add extra typed fields to every incoming message. The extractor surfaces them through a narrowed message:
Instance actions
Methods declared inactions are projected onto the platform instance returned by myPlatform(app). The framework injects ctx = { client, config, store } as the first argument, so callers only pass the trailing args.
Two tiers share the actions slot:
- Platform-wise actions (
getMessage,getMembers,getAvatar,getDisplayName) — framework-recognized names. Always present on every platform instance. When omitted, the framework wires a default that throws an . - Platform-specific actions — free-form keys for platform ergonomics (e.g. iMessage’s
getAttachment). Only present when declared.
Fusor-backed providers
When your platform receives inbound messages through webhooks (rather than a persistent connection), usefusor(...) as the client in lifecycle.createClient. A Fusor client handles webhook signature verification and delivers parsed payloads to a handler:
definePlatform replaces the top-level messages async generator with a per-webhook-delivery handler. It receives a . Call respond() to set the HTTP response sent back to the webhook caller.
FusorMessagesCtx
Context passed to a Fusor message handler for one webhook delivery.
FusorMessagesCtx
Context passed to a Fusor message handler for one webhook delivery.
Registering your platform
Exported platforms work like the built-ins — register with.config() and use narrowing for the typed surface: