Docs
Developer Documentation

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/.

modules/example/resources/config/settings.js
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:

TypeStored FormatDescription
"boolean"booleanOn/off toggle switch.
"string"stringSingle-line text input.
"text"stringMulti-line textarea.
"number"numberNumeric input with increment controls.
"select"stringDropdown selector with options array.
"color"stringHex color picker control (e.g. "#22c55e").
"channel"stringSingle Discord channel picker.
"channels"Array<string>Multi-select Discord channel picker.
"category"stringSingle Discord category picker.
"categories"Array<string>Multi-select Discord category picker.
"role"stringSingle Discord role selector.
"roles"Array<string>Multi-select Discord role selector.
"emoji"stringDiscord 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:

modules/example/resources/dashboard/previews/template.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/.

modules/example/resources/logs/itemLogs.js
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:

VerbDefault ColorStandard Hex Code
created, added, unbanned, unmuted, joinedGreen#22c55e
deleted, removed, bannedRed#ef4444
updated, changed, editedAmber#f59e0b
mutedOrange#f97316
moved, executedBlue#3b82f6
leftGrey#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.

modules/example/resources/lang/en/messages.js
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:

modules/example/resources/lang/en/common.json
{
    "module.example.name": "Example"
}

Translations Bundle (translations.json)

Store translations for all 15 non-English languages in resources/lang/translations.json:

modules/example/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."
        }
    }
}

On this page