Post Providers
Let the core /post command and dashboard Post page send your module's panels into a channel.
Modules never ship their own /<module> post or /<module> panel subcommands. Instead they declare post providers, and Core exposes every provider through one /post slash command and one dashboard Post page.
Post providers reside in resources/posts/ and load from every *.js file located directly in that directory.
Base Class Definition
import { Posts } from "#medusa/modules";
export class PanelPost extends Posts {
constructor(medusa) {
super(medusa, { name: "panel" });
}
panels() {
return this.medusa.modules.handlers.get(this.module, "panels");
}
items(guildId) {
return this.panels().keys(guildId).map(key => ({ value: key, label: this.panels().panel(guildId, key).title }));
}
async message(guild, item) {
const panel = this.panels().panel(guild.id, item);
return panel ? this.panels().panelMessage(panel) : null;
}
async posted(message, { guild, channel, item, actor }) {
await this.medusa.logs.post(this.module, "panelPosted", { guildId: guild.id, actor, tokens: { panel: item, channel: `<#${channel.id}>` } });
}
}Constructor Options
| Property | Type | Default | Description |
|---|---|---|---|
name | string | Required | Provider identifier, unique within the module. The public id is <module>:<name>. |
label | string | "" | Optional suffix shown after the module name. Only needed when a module has more than one provider. The dashboard translates it through schema.<module>.config.<name>.label when that key exists. |
order | number | 0 | Sort priority between providers of the same module. |
The type shown to users is the module display name (the module.<id>.name translation on the dashboard), plus the label when set.
Methods
| Method | Returns | Description |
|---|---|---|
items(guildId) | Array<{ value, label }> | Entries the user picks from, such as configured panels. Return [] when the module has a single message to post; the item option and selector are then hidden. |
target(guild) | Channel | null | Optional default channel, for example a channel in a connected appeal server. Used when no channel is chosen and always listed on the dashboard. |
message(guild, item) | Promise<object | null> | Builds the message payload. Return null when there is nothing to post. |
posted(message, context) | Promise<void> | Runs after a successful send. Store the message, add reactions, or write a log. context holds guild, channel, item and actor (the Discord user, or null). |
message and posted run inside the guild scope, so config reads resolve to the server the post was made in.
Post Manager Methods
The root this.medusa.posts service validates and sends posts for both the command and the dashboard:
const types = this.medusa.posts.types(guildId);
const provider = this.medusa.posts.entry("ticket:panel", guildId);
const { message, channel } = await this.medusa.posts.send({
id: "ticket:panel",
item: "support",
guildId,
channelId,
actor: interaction.user
});send throws an Error with a localized message when the server is unavailable, the type or item does not exist, the channel is missing or cannot hold messages, message returns null, or Discord rejects the send.
Dashboard Route
The Post page talks to the core module route /api/modules/core/posts:
| Method | Body | Response |
|---|---|---|
GET | - | { types: [{ id, module, name, label, items, target }] } for the server in x-guild-id, limited to modules run by the bot in x-bot. |
POST | { type, item, channel } | { success: true, channel }, or 400 with { error }. |