Plugin example — Discord
The existing Discord integration is a separate unit written in TypeScript. An application using it does not have to implement the Discord API. The unit exposes the discord-integration.v1 contract and consumes the notification.raised event convention.
This guide is based on the implementation. Notification delivery in an external developer’s environment has not been tested here.
Connection and configuration
register returns a URL to open in the browser. Calling the operation alone does not complete the connection: Discord must redirect to the unit’s callback.
The /discord/oauth/callback callback is a separate integration endpoint outside the exposition. The operator must provide a reachable unit address and the correct redirect_uri in the Discord application configuration.
Contract for application UI
| Operation | Purpose |
|---|---|
discordintegrationstatus.get | Reads configuration and connection status for the Discord server. |
discordintegrationstatus.register | Starts OAuth linking and returns LinkUrl. |
discordintegrationstatus.disconnect | Disconnects the server and clears the instance’s connection state. |
discordroute.channels | Lists channels for a UI selector. |
discordroute.list | Reads configured event routes. |
discordroute.set | Maps eventType to channelId. |
discordroute.remove | Removes an event type mapping. |
Example request body for discordroute.set; replace the values with your own event type and channel identifier:
{
"eventType": "order.created",
"channelId": "YOUR_DISCORD_CHANNEL_ID"
}
This is an operation body, rather than a complete HTTP request. The environment’s API configuration supplies the address, authorization and tenant context.
Notification delivery
The application supplies eventType in the event’s data field, for example order.created. The unit treats the value as a route key, rather than a business rule. This is an integration convention, rather than a required field of every protocol envelope.
The message title comes from title or subject, its description from body or description, and its link from optional url. The integration itself has no knowledge of the application’s order domain or other business concepts.
Example limitations
- Without
eventTypeor a configured route, no message is sent. This does not automatically mean the source operation failed. - The integration sends notifications. It does not handle inbound messages, slash commands or Discord component interactions.
- The channel list checks channel types, rather than the full Discord permission model. A delivery denial may only become visible when sending.
- The example declares one instance per tenant and one Discord server connection. This is not a product-wide multi-tenancy guarantee.
- Pending OAuth and deduplication state in memory do not survive a restart. Restart unfinished linking; notification delivery may happen again.
Applying this pattern to your own plugin
Separate application configuration operations from endpoints required by the external provider. Describe operations through a contract, then implement a handler and event consumer. Test invalid inputs, missing configuration and provider responses.
The team discussed configuring integrations through expositions available in Catalog and adding descriptions for frontend tooling. The current Protocol schema allows optional description annotations on definitions. Those descriptions are absent from the inspected Discord contract. Schema support for annotations does not promise a complete automatic configuration panel for every plugin or compatibility of any version set.
Next: testing and troubleshooting.