Dashboard Schemas
Define zero-code dashboard forms, audit logging channels, and multi-language localization.
Medusa's web dashboard is completely schema-driven. You never need to write front-end React or HTML components to configure your module. Instead, your module declares configuration forms, Discord audit logging streams, and multilingual localization dictionaries inside the resources/ directory.
Configuration Schemas (resources/config/)
Configuration forms define editable module settings that administrators manage in the web dashboard. Classes extend Configs from #medusa/modules and reside in resources/config/.
import { Configs } from "#medusa/modules";
export class SettingsConfig extends Configs {
constructor(medusa) {
super(medusa, {
name: "settings",
label: "Example Settings",
description: "Manage core settings, channels, and operational limits."
});
}
schema() {
return {
enabled: {
type: "boolean",
label: "Module Enabled",
default: true
},
alertChannel: {
type: "channel",
label: "Alert Channel",
default: ""
},
adminRole: {
type: "role",
label: "Manager Role",
default: ""
},
allowedRoles: {
type: "roles",
label: "Authorized Staff Roles",
default: []
},
currencySymbol: {
type: "string",
label: "Currency Symbol",
default: "$"
},
maxStock: {
type: "number",
label: "Maximum Stock Limit",
default: 100
},
themeColor: {
type: "color",
label: "Accent Color",
default: "#22c55e"
},
deliveryMode: {
type: "select",
label: "Delivery Mode",
default: "instant",
options: [
{ value: "instant", label: "Instant Delivery" },
{ value: "manual", label: "Manual Approval" }
]
},
rewards: {
type: "list",
label: "Milestone Rewards",
default: [],
item: {
level: { type: "number", label: "Level", default: 1 },
role: { type: "role", label: "Reward Role", default: "" },
coins: { type: "number", label: "Coins Awarded", default: 50 }
}
}
};
}
}Supported Configuration Field Types
Medusa supports 15 specialized field types:
| Type | Stored Format | Description |
|---|---|---|
"boolean" | boolean | On/off toggle switch. |
"string" | string | Single-line text input. |
"text" | string | Multi-line textarea. |
"number" | number | Numeric input with increment controls. |
"select" | string | Dropdown selector with options array. |
"color" | string | Hex color picker control (e.g. "#22c55e"). |
"channel" | string | Single Discord channel picker. |
"channels" | Array<string> | Multi-select Discord channel picker. |
"category" | string | Single Discord category picker. |
"categories" | Array<string> | Multi-select Discord category picker. |
"role" | string | Single Discord role selector. |
"roles" | Array<string> | Multi-select Discord role selector. |
"emoji" | string | Discord emoji picker (custom and Unicode). |
"questions" | Array<object> | Application question builder. |
"list" | Array<object> | Dynamic repeating list with nested item schema. |
Live Configuration Previews
To render a live interactive preview beside your configuration form, pass preview: "<id>" in your Configs constructor:
export class SettingsConfig extends Configs {
constructor(medusa) {
super(medusa, {
name: "settings",
label: "Template Settings",
description: "Manage core module settings.",
preview: "template"
});
}
}Then create a React preview component in resources/dashboard/previews/<id>.tsx:
"use client";
import type { PreviewProps } from "@/components/pages/types";
export default function TemplateConfigPreview({ values, user, module }: PreviewProps) {
const accentColor = typeof values.embedColor === "string" ? values.embedColor : "#5865f2";
return (
<div className="rounded-xl border border-border bg-card p-4">
<h4 className="text-sm font-semibold">Live Preview</h4>
<p className="text-xs text-muted-foreground">Accent: {accentColor}</p>
</div>
);
}Reading Configuration in Code
Retrieve configuration values from any handler, command, or route:
const isEnabled = this.medusa.modules.config.value(this.module, "settings", "enabled");
const alertChannel = this.medusa.modules.config.value(this.module, "settings", "alertChannel");
const allSettings = this.medusa.modules.config.values(this.module, "settings");Audit Logging Schemas (resources/logs/)
The logging system dispatches rich Discord audit embeds to channels selected by server administrators. Classes extend Logs from #medusa/modules and reside in resources/logs/.
import { Logs } from "#medusa/modules";
export class ItemLogs extends Logs {
constructor(medusa) {
super(medusa, {
name: "itemLogs",
label: "Item Logs",
description: "Audit trail for item creation, updates, and purchases.",
icon: "box",
colour: "#22c55e"
});
}
events() {
return {
created: {
label: "Item Created",
title: "%custom_emoji_check% • <server> » Item Created",
summary: "%subject% was created by %actor%.",
verb: "created",
color: "#22c55e",
default: true
},
deleted: {
label: "Item Deleted",
title: "%custom_emoji_cross% • <server> » Item Deleted",
summary: "%subject% was deleted by %actor%.",
verb: "deleted",
color: "#ef4444",
default: true
},
purchased: {
label: "Item Purchased",
title: "<server> » Item Purchased",
summary: "%actor% purchased %subject%.",
verb: "executed",
color: "#3b82f6",
default: true
}
};
}
}Standard Action Verbs and Colors
When an event specifies a verb, Medusa assigns colors automatically if not explicitly overridden:
| Verb | Default Color | Standard Hex Code |
|---|---|---|
created, added, unbanned, unmuted, joined | Green | #22c55e |
deleted, removed, banned | Red | #ef4444 |
updated, changed, edited | Amber | #f59e0b |
muted | Orange | #f97316 |
moved, executed | Blue | #3b82f6 |
left | Grey | #64748b |
Dispatching Log Events
Dispatch logs from your handlers using this.medusa.logs.post():
await this.medusa.logs.post(this.module, "purchased", {
actor: interaction.user,
subject: item.name,
fields: [
{ name: "Price", value: `$${item.price}`, inline: true },
{ name: "Remaining Stock", value: `${item.quantity}`, inline: true }
],
tokens: {
item_id: item.identifier
}
});If the administrator has disabled the event or has not assigned a logging channel to the category, Medusa skips dispatching without throwing errors.
Localization (resources/lang/)
Medusa supports 16 languages. Base English schemas live in resources/lang/en/*.js extending Langs. Translations for all other languages are placed in resources/lang/translations.json.
import { Langs } from "#medusa/modules";
export class MessagesLang extends Langs {
constructor(medusa) {
super(medusa, {
name: "messages",
label: "User Messages",
description: "Discord responses and embed templates."
});
}
schema() {
return {
created: {
type: "phrase",
default: "Successfully created item **%name%**."
},
notFound: {
type: "phrase",
default: "Item **%identifier%** does not exist."
},
successEmbed: {
type: "embed",
default: {
title: "Action Complete",
description: "%custom_emoji_check% • Operation completed for **%user%**.",
color: "#22c55e"
}
}
};
}
}Resolving Localized Messages
const text = this.medusa.modules.lang.phrase(this.module, "messages", "created", { name: "Iron Sword" });
const embed = this.medusa.modules.lang.embed(this.module, "messages", "successEmbed", { user: interaction.user.username });Display Names in common.json
Every module must declare its display name in resources/lang/<code>/common.json across all 16 locales:
{
"module.example.name": "Example"
}Translations Bundle (translations.json)
Store translations for all 15 non-English languages in resources/lang/translations.json:
{
"fr": {
"messages": {
"created": "L'article **%name%** a été créé avec succès.",
"notFound": "L'article **%identifier%** n'existe pas."
}
},
"es-ES": {
"messages": {
"created": "Artículo **%name%** creado con éxito.",
"notFound": "El artículo **%identifier%** no existe."
}
}
}
