Skip to main content
Every platform provider exports a callable — imessage, localIMessage, slack, terminal, whatsappBusiness, telegram — that narrows generic Spectrum types into platform-specific ones. The same function handles three different inputs.

Narrowing the app

Pass a to get a for that platform. The platform instance gives you user() and space.create() / space.get() resolvers, plus access to any custom events the provider emits.
If the platform isn’t registered in the providers array, the type of imessage(app) resolves to never — the call is a compile-time error.

Narrowing a space

Pass an existing space to access platform-specific fields:
Narrowing a space from the wrong platform logs a structured warning at runtime. Always gate on message.platform (or a similar signal) first to avoid unexpected behavior. The local macOS provider is a separate platform. Gate on message.platform === "local_imessage" and narrow with localIMessage(...); the cloud provider continues to use "imessage" and imessage(...).

Narrowing a message

Same idea for messages — useful when a provider declares a message.schema to attach extra properties:

Creating group conversations

space.create(...) accepts a single user or an array of users. On iMessage:
Some platforms support an additional params argument for extra space creation options — the shape of those params is defined per-provider through space.params on the platform definition.

Why narrowing matters

The generic and interfaces are deliberately small — just enough to send, react, and reply across every platform. Narrowing is the escape hatch for everything else: typed access to iMessage chat types, WhatsApp phone numbers, or any extra field your custom platform exposes.