Medusa/developer documentation/Internal API Specification

Internal API Specification

Exhaustive API specification covering every property, method, and signature on the Medusa instance.

This reference documents every property, service, method signature, and parameter exposed across Medusa. Every building block receives this instance as this.medusa.

Root Medusa Properties

PropertyTypeDescription
productstringConstant product name ("Medusa").
versionstringRunning version string (e.g. "1.0.0.0").
startedAtDayjsDay.js timestamp representing the start time of the bot.
runningbooleantrue once boot sequence completes and modules are active.
stoppingbooleantrue while the shutdown lifecycle is executing.
configConfigRoot configuration manager for config.json.
consoleLayoutStructured console logging and ANSI color builder.
eventsEventEmitterGlobal event emitter (medusa.ready, medusa.discord.ready, etc.).
databaseDatabaseSequelize database manager and connection supervisor.
cacheCacheIn-memory cache stores with expiry and size limits.
settingsSettingsKey-value settings database storage manager.
localesLocalesLocalization catalog for all 16 supported languages.
embedsEmbedsStandard Discord embed message builder and dispatcher.
emojisEmojisDiscord application emoji registry and custom icons.
logsLogsDiscord audit logging channel dispatcher.
permissionsPermissionsRole, user, and rank authorization supervisor.
auditAuditSystem audit log database manager.
modulesModulesModule manager, registry, and subsystem loader.
leaderboardsLeaderboardsLeaderboard provider registry and pagination engine.
postsPostsPost provider registry that validates and sends module posts into channels.
placeholdersPlaceholdersPlaceholder provider registry that fills live values into the bot activity status.
docsDocsIn-dashboard module documentation filesystem reader.
dataDataOn-disk file storage manager in the root data/ directory.
apiApiExpress HTTP REST API server on port 3001.
discordDiscordDiscord.js client manager and gateway supervisor.
dashboardDashboardWeb dashboard server: builds and serves the Vite app, Discord sign-in, page data, dashboard actions and runtime-compiled module pages.

Licensing internals are not part of the module API. To check what a server's license includes, use hasAddon(name): it returns a boolean and never exposes license keys or customer data. Modules must not read or depend on raw license metadata or the update connection.

Root Medusa Methods

hasAddon(name)

Checks whether the active server license includes a specific commercial addon.

  • Signature: hasAddon(name: string): boolean
  • Example:
    if (this.medusa.hasAddon("Economy")) {
    }

emitAsync(event, ...args)

Sequentially invokes all asynchronous listeners registered on this.medusa.events.

  • Signature: emitAsync(event: string, ...args: any[]): Promise<void>

events

Shared Node EventEmitter instance for event subscriptions and synchronous emissions. Modules and handlers also expose this.emit(event, ...args) and this.emitAsync(event, ...args).

  • Signature: events: EventEmitter

wait(ms)

Promise-based execution delay helper.

  • Signature: wait(ms: number): Promise<void>
  • Example:
    await this.medusa.wait(2000);

restart()

Performs a graceful shutdown and triggers process restart with exit code 75.

  • Signature: restart(): Promise<void>

stop(code)

Gracefully stops all modules, clocks, Discord sessions, HTTP servers, and database connections.

  • Signature: stop(code?: number): Promise<void>

installPackages(...packages)

Checks node_modules for missing npm packages and installs them automatically using bun add or npm install. Every package requested during the process is remembered, and each install passes the full list, so packages installed without being saved to package.json (for example with save=false in .npmrc) are not pruned by the next install. Module packages are declared in the manifest dependencies.node instead of calling this directly.

  • Signature: installPackages(...packages: string[]): Promise<void>

Modules Accessor (this.medusa.modules)

  • catalog(guildId?: string): Promise<Array<object>> - Returns metadata and status for all modules. enabled reflects the given server.
  • enabled(id: string, guildId?: string | null): boolean - Whether module id is on in that server. Always true for core or when no guild is given.
  • disabled(guildId: string): Set<string> - Module ids turned off in that server.
  • toggle(id: string, guildId: string, enabled: boolean): Promise<void> - Turns a module on or off for one server and emits module.toggled. The dashboard also resyncs that server's slash commands.
  • dictionary(locale?: string): Record<string, string> - Returns merged dashboard translations across all loaded modules.

Models (this.medusa.modules.models)

  • get(module: string, name: string): Models | null

Handlers (this.medusa.modules.handlers)

  • get(module: string, name: string): Handlers | null

Routes (this.medusa.modules.routes)

  • get(module: string, name: string): Routes | null

Commands (this.medusa.modules.commands)

  • get(module: string, name: string): Commands | null
  • find(name: string): Commands | null - Searches all loaded modules.

Events (this.medusa.modules.events)

  • get(module: string, name: string): Events | null

Clocks (this.medusa.modules.clocks)

  • get(module: string, name: string): Clocks | null

Configuration (this.medusa.modules.config)

  • get(module: string, name: string): Configs | null
  • value(module: string, name: string, key: string): any
  • values(module: string, name: string): object

Language & Phrases (this.medusa.modules.lang)

  • get(module: string, name: string): Langs | null
  • value(module: string, name: string, key: string): any
  • values(module: string, name: string): object
  • phrase(module: string, name: string, key: string, tokens?: object): string
  • embed(module: string, name: string, key: string, tokens?: object): EmbedBuilder
  • dictionary(module: string, locale?: string): Record<string, string> - Returns dashboard translation dictionary for a module.

Logs (this.medusa.modules.logs)

  • get(module: string, name: string): Logs | null

Custom Pages (this.medusa.modules.pages)

  • get(module: string, name: string): Pages | null
  • list(): Array<object>

Leaderboards (this.medusa.modules.leaderboards)

  • get(module: string, name: string): Leaderboards | null
  • all(): Array<{ module: string, provider: Leaderboards, descriptor: object }>

Posts (this.medusa.modules.posts)

  • get(module: string, name: string): Posts | null
  • all(guildId?: string): Array<{ module: string, provider: Posts, descriptor: object }>

Automation Nodes (this.medusa.modules.nodes)

  • get(module: string, name: string): Nodes | null
  • all(): Array<{ module: string, node: Nodes, descriptor: object }>

Module Emojis (this.medusa.modules.emojis)

  • get(module: string, name: string): object | null
  • all(): Array<{ name: string, fullName: string, attachment: string, module: string }>

Embeds Service (this.medusa.embeds)

MethodSignatureDescription
success(text)(text: string) => EmbedBuilderReturns a one-line embed led by the check emoji, in the accent color.
warn(text)(text: string) => EmbedBuilderReturns a one-line embed led by the warning emoji, in the accent color.
error(text)(text: string) => EmbedBuilderReturns a one-line embed led by the cross emoji, in the accent color.
info(emoji, message)(emoji: string, message: string) => EmbedBuilderReturns a one-line embed led by %custom_emoji_<emoji>%, or the info emoji when emoji is empty.
resolve(template, tokens, options)(template: object, tokens?: object, options?: object) => EmbedBuilderResolves embed object with token replacements, footer, and timestamp.
normalize(template)(template: object) => objectNormalizes author, footer, thumbnail, image, and color into valid Discord structure.
hex(value)(value: any) => string | nullFormats number or text string into a normalized 6-character hex code ("#22c55e").
fill(value, tokens)(value: any, tokens?: object) => anyRecursively replaces %key%, <key>, and custom emoji placeholders across strings, arrays, and objects.
serverName()() => stringReturns configured server name or Discord guild name.
serverIcon()() => string | nullReturns server icon URL or bot avatar URL.
footerName()() => stringReturns server name if watermark addon is present; otherwise returns product name.
send(channelId, content, template, tokens, buttons, selectMenu, components, flags, embeds)(channelId: string, ...) => Promise<Message>Dispatches complete message with embeds and interactive components.
resolveComponent(comp, tokens)(comp: object, tokens?: object) => objectRecursively resolves component trees: replaces tokens (%token%), resolves custom emojis (%custom_emoji_<name>%), converts style string literals to Discord numbers, converts hex accent colors ("#ef4444") to integers, and normalizes section accessories.
buildComponents(buttons, selectMenu, rawComponents)(buttons?: Array, selectMenu?: object, rawComponents?: Array) => ArrayBuilds Discord action rows containing buttons and select menus in groups of 5.
buttonStyleNum(style)(style: string | number) => numberTranslates style string literals ("link" $\to 5$, "secondary" $\to 2$, "success" $\to 3$, "danger" $\to 4$, default $\to 1$).
parseButtonEmoji(token)(token: any) => object | nullResolves custom emojis, Discord tags, and Unicode characters into { id, name, animated } or { name }.
isComponentsV2(components)(components: Array) => booleanChecks whether any component in an array uses V2 component types (17, 9, 10, 12, 13, 14, 15).
send(channelId, content, template, tokens, buttons, selectMenu, components, flags, embeds)(channelId: string, ...) => Promise<Message>Dispatches complete message with automatic V1/V2 component resolution, embeds, and flags.
saveMessage(message, guildId)(message: object, guildId?: string) => Promise<object>Persists a message template with buttons and components for one server ("MESSAGES:<guildId>"). messages(guildId) lists them, falling back to the shared "MESSAGES" list for servers that have not saved any.
deleteMessage(id)(id: string) => Promise<Array<object>>Deletes a saved message template from storage by ID.
messages()() => Array<object>Retrieves all saved message templates from settings storage.
populated(template)(template: object) => booleanChecks whether an embed template contains any visible content (title, description, fields, etc.).
preset(emoji, text)(emoji: string, text: string) => EmbedBuilderResolves a one-line notification embed led by %custom_emoji_<emoji>%, with no footer, timestamp or thumbnail. Prefer success, warn and error.
decorate(embed, template, options)(embed: EmbedBuilder, template: object, options?: object) => EmbedBuilderDecorates an embed with default accent color, server/brand footer, timestamp, and server icon thumbnail.

Components Builder (this.medusa.components)

Builds Components V2 trees with named methods instead of raw Discord type integers. The trees resolve and dispatch through the Embeds service. See the Buttons & Select Menus reference for full schemas and examples.

MethodSignatureDescription
container(options)({ accent?, components?, spoiler?, id? }) => objectContainer (type: 17). accent is a hex string; omitted inherits ACCENT_COLOUR, null clears it.
section(options)({ components?, accessory? }) => objectSection (type: 9). String children become text displays; accessory is a button or thumbnail.
text(content)(content: string) => objectText Display (type: 10).
separator(options)({ divider?, spacing? }) => objectSeparator (type: 14). Defaults divider: true, spacing: 1.
thumbnail(url, options)(url: string, { description?, spoiler? }) => objectThumbnail (type: 11).
gallery(items)(items: Array<string | object>) => objectMedia Gallery (type: 12).
file(url, options)(url: string, { spoiler? }) => objectFile (type: 13).
row(components)(components: Array) => objectAction Row (type: 1).
button(options)({ style?, label?, custom_id?, url?, emoji?, disabled? }) => objectButton (type: 2) with a string style.
select(options)({ custom_id?, placeholder?, options?, disabled?, min?, max? }) => objectString Select Menu (type: 3).
resolve(components, tokens)(components, tokens?: object) => ArrayResolves one tree or an array through the Embeds resolver.
payload(components, options)(components, { tokens?, content?, silent? }) => objectResolves and wraps a tree into a send-ready object with MessageFlags.IsComponentsV2.
send(channelId, components, options)(channelId: string, components, options?) => Promise<Message>Builds a payload and posts it to a channel.

Emoji Registry (this.medusa.emojis)

MethodSignatureDescription
get(name, subName)(name: string, subName?: string) => stringReturns custom Discord emoji markup (e.g. <:check:12345>) or empty string. Accepts compound name ("security_lock") or module and emoji names ("security", "lock").
has(name, subName)(name: string, subName?: string) => booleanChecks whether an emoji name or compound name exists in the registry.
names()() => Array<string>Returns all registered preset and module emoji names.
theme()() => stringReturns the active preset colour from EMOJI_THEME (purple, blue, green, red, pink or orange).
presetsArray<{ name, url }>The 299 preset emojis with their bucket URL for the active colour.
sync(bot)(bot: Bot) => Promise<void>Deletes every application emoji when the application is not synced with the active colour, then uploads missing preset icons and module emojis to that bot's application emojis. Runs on medusa.discord.bot.ready for every bot. get, has and names read the registry of the bot running the current code.
refresh()() => Promise<void>Runs sync for every bot. Called when EMOJI_THEME changes through /api/settings.

Discord Supervisor (this.medusa.discord)

Property / MethodSignatureDescription
clientClientDiscord.js Client of the bot that runs the current module. See Multiple Bots.
ready()() => booleanReturns true if the bot that runs the current module is connected to the gateway.
bot(module)(module?: string) => BotThe bot that runs module. Defaults to the module running the current code.
owner(module)(module: string) => BotThe bot a module is assigned to. Unassigned modules and Core belong to the primary bot.
primary()() => BotThe primary bot from setup.
list()() => Array<object>Every bot with key, primary, connected, tag, id, username, avatar, banner, modules and guilds.
verify(token)(token: string) => Promise<object>Checks a new bot's token, intents, license whitelist and that it is not already added, without saving it.
add(token, options)(token: string, options?: { username?, avatar?, banner?, presence?, modules? }) => Promise<object>Saves and starts a new bot. The username, avatar and banner are applied once it connects, the presence is stored, and the modules are assigned to it.
resources()() => Promise<object>Fetches { guildId, channels, categories, roles, emojis, serverIcon } through the primary bot.
post(channelId, payload)(channelId: string, payload: object) => Promise<Message>Posts a message to a text channel through the current module's bot.
temporary(channelId, payload, seconds)(channelId: string, payload: object, seconds: number) => Promise<Message>Posts a message that automatically deletes after the specified duration.
setUsername(username, key)(username: string, key?: string) => Promise<void>Updates a bot's username. key is "primary" (default) or the bot's user id.
setAvatar(avatar, key)(avatar: Buffer | string, key?: string) => Promise<void>Updates a bot's avatar image.
setBanner(banner, key)(banner: Buffer | string, key?: string) => Promise<void>Updates a bot's profile banner image.
savePresence(key, config)(key: string, config: object) => Promise<void>Stores and applies a bot's presence activity, status, and rotating messages.
prefix(guildId)(guildId?: string) => stringReads the prefix for prefix-based commands in that server.
commandConfig(name, guildId)(name: string, guildId?: string) => objectReads a command's toggles and aliases in that server.
current()() => string | nullThe server the running code belongs to. Set for every interaction, message, event, and dashboard route, so embeds resolve %server_icon%, %server_name%, footers, and thumbnails for the right server.
within(guildId, run, module)(guildId: string, run: () => any, module?: string) => anyRuns run as that server, and as module when given (otherwise the current module is kept). Wrap clock work that sends messages, e.g. await this.medusa.discord.within(guild.id, () => handler.announce(guild)).
module()() => string | nullThe module the running code belongs to.
servers()() => Array<object>Lists every authorized server the bot is in, with name, icon and counts.
connection(serverId)(serverId: string) => Guild | nullReturns a connected server's Guild for a cross-server connection, or null when the bot is not in it or it is not authorized.

Multiple Bots

Medusa can run several Discord bots in one process. Each module is assigned to one bot, and Medusa tracks which module is running so this.medusa.discord.client, ready(), post(), connection(), %bot_avatar% and %custom_emoji_<name>% all resolve to that module's bot. Framework code sets the module for you:

  • Commands, autocomplete, and prefix or mention commands run as the command's module.
  • Discord events are only wired on the module's bot, so each event fires once. Medusa-sourced events run as their module.
  • Clocks, routes, post providers, onStartup() and onShutdown() run as their module.
  • Listeners added with this.medusa.events.on() inside module code keep that module when they fire.
  • this.medusa.logs.post(module, ...) sends through module's bot.

Code that runs outside these paths, such as a plain setTimeout, keeps the module it was scheduled from. To act as another module, use this.medusa.discord.within(guildId, run, module), or read a bot directly with this.medusa.discord.bot(module).client.

Discord only routes a component interaction to the application that sent the message. Send panels and messages with buttons, select menus, or modals from your own module's code or post provider so your module's bot owns them.

Every bot must have the Presence, Server Members, and Message Content intents. Medusa checks each token with check(token) before connecting and shuts down with a guide if any intent is off.


Permissions Supervisor (this.medusa.permissions)

MethodSignatureDescription
guard(interaction, node)(interaction: Interaction, node: string) => Promise<boolean>Evaluates member permissions. If denied, automatically responds with an ephemeral error embed and returns false.
allowed(member, node)(member: GuildMember, node: string) => booleanEvaluates whether a member is authorized for a command or interaction node in the member's server. Ranks come from the member's roles in the primary server, limited to ranks that apply in that server. A rank with bots set only grants nodes owned by those bots.
deny(interaction)(interaction: Interaction) => Promise<void>Sends the localized denial embed as an ephemeral reply.
ranks(guildId)(guildId?: string) => Array<object>Returns all ranks, or only the ranks that apply in guildId.
servers(userId)(userId: string) => Array<string>Server IDs where the user has any dashboard access. The dashboard server switcher only lists these.
held(memberOrUserId, guildId)(memberOrUserId: any, guildId?: string) => Array<object>Returns the ranks a user holds, based on their roles in the primary server, that apply in guildId (defaults to the member's server). Primary server members are preloaded on startup.
owners()() => Map<string, string>Maps every command, event and module page node to the key of the bot that runs it ("primary" or the bot user id).
abilities(memberOrUserId)(memberOrUserId: any) => { grantAll: boolean, abilities: Array<string> }Resolves dashboard permissions held by the member.
allowedDashboard(memberOrUserId, node)(memberOrUserId: any, node: string) => booleanChecks whether user has permission to view a dashboard page.

System Audit (this.medusa.audit)

MethodSignatureDescription
record(entry)(entry: { type: string, actorId?: string, actorTag?: string, command?: string, success?: boolean, detail?: object, guildId?: string }) => Promise<void>Records an audit entry to the database.
list(filter)(filter?: { guildId?: string, type?: string, limit?: number }) => Promise<Array<object>>Queries audit history ordered descending by creation date.

Disk Storage (this.medusa.data)

MethodSignatureDescription
module(id, ...segments)(id: string, ...segments: string[]) => stringResolves safe path inside data/<module>/.
path(...segments)(...segments: string[]) => stringResolves safe path inside root data/.
ensure(...segments)(...segments: string[]) => stringCreates directory structure recursively if it does not exist.
exists(...segments)(...segments: string[]) => booleanChecks whether file or directory exists.
remove(...segments)(...segments: string[]) => voidDeletes file or directory recursively.

Settings Storage (this.medusa.settings)

MethodSignatureDescription
get(key)(key: string) => anyReads the setting value from memory.
set(key, value)(key: string, value: any) => Promise<any>Upserts the value into the medusa_settings table, then updates memory. Throws when the write fails.
delete(key)(key: string) => Promise<void>Removes the setting from the database, then from memory.
all()() => objectReturns full key-value map of all stored settings.
reload()() => Promise<number>Reloads all settings from the database into memory.

Global Settings Catalog

Setting KeyTypeDescription
BOT_IDstringDiscord client ID of the bot application.
GUILD_IDstringPrimary Discord server snowflake ID.
DISCORD_TOKENstringDiscord bot secret token.
ACCENT_COLOURstringDefault hex color applied across embeds and UI.
LANGUAGEstringPrimary language code ("en", "fr", "de", etc.).
SECONDARY_LANGUAGEstringFallback language code used for keys a module does not translate in the primary language, resolved before English.
SERVER_NAMEstringCustom name override for the primary server only. Every other server uses its Discord name.
DASHBOARD_NAMEstringCustom branding name for the web dashboard.
API_KEYSArray<object>Registered external API authentication keys.
ALLOWED_IPSArray<string>Whitelisted client IP addresses.
GUILD_MODULESRecord<string, Array<string>>Disabled module folder ids per server, keyed by guild id. core can never be disabled.
MODULESArray<string>Legacy global disabled list. Migrated into GUILD_MODULES for every known server on boot, then cleared.
AUTO_UPDATEbooleanWhen not false, the auto-updater installs product and official module updates on restart. Official modules are the folder ids listed under modules in package.json, generated at release time from the module/* branches. Any other folder counts as a community module and is left alone.
AUTO_RESTARTbooleanWhen true, automatically schedules and triggers a Discord client restart after the configured interval.
AUTO_RESTART_INTERVALstringTime interval string (such as "1h", "24h", "7d") specifying how long the bot runs before each auto-restart.
MODULE_BLACKLISTArray<string>Official module folder ids the auto-updater must never install. Any listed module already on disk is removed on the next update.
MODULE_FROZENArray<string>Official module folder ids the auto-updater must never overwrite. Their files are preserved across updates, so local edits survive (e.g. a Full Source build).
COMMANDS:<guildId>objectCommand prefix, toggles, and aliases for one server. Falls back to the shared COMMANDS value.
PERMISSIONSobjectRank priorities, primary-server roles, users, ability maps, servers (the servers each rank applies in; empty means all), and bots (bot keys whose commands, events and module pages the rank grants; empty means all).
MESSAGES:<guildId>Array<object>Saved Embed Maker templates for one server.
module:<id>objectConfiguration values bucket for module <id>.
lang:<id>objectLanguage overrides bucket for module <id>.
logs:<id>objectChannel assignments and toggles for module <id>.

Localization Catalog (this.medusa.locales)

MethodSignatureDescription
list()() => Array<object>Returns all 16 supported language objects with code, name, native, and discord mappings.
codes()() => Array<string>Returns array of all 16 locale code strings.
active()() => stringReturns the primary language code from the LANGUAGE setting, falling back to "en" when unsupported.
secondary()() => stringReturns the fallback language code from the SECONDARY_LANGUAGE setting, falling back to "en" when unsupported.
chain(primary)(primary?: string) => Array<string>Returns the ordered, de-duplicated resolution chain [primary, secondary, "en"] used for per-key language fallback. Defaults primary to active().
isSupported(code)(code: string) => booleanChecks if a language code is supported.
moduleName(id, locale)(id: string, locale?: string) => stringResolves localized module display name from module.<id>.name in common.json, walking the fallback chain.

Database Supervisor (this.medusa.database)

Property / MethodSignatureDescription
sequelizeSequelizeActive Sequelize ORM database connection.
healthybooleantrue when the database connection is live and passing health ticks.
sync(options)(options?: object) => Promise<void>Synchronizes all Sequelize models and runs column migrations.
close()() => Promise<void>Closes database connections cleanly.

Medusa never runs without its database. On boot it tries to connect up to 5 times with a growing delay and exits with code 1 if every attempt fails. At runtime a health tick runs every 15 seconds; when it fails, or a connection error goes unhandled, Medusa immediately retries up to 5 times and stops with code 1 if the database does not come back. There is no offline mode, file cache, or deferred write queue: a failed write throws to the caller.


Cache (this.medusa.cache)

In-memory cache stores. Nothing is written to disk and everything is dropped on restart. See Cache.

MethodSignatureDescription
store(name, options)(name: string, options?: { ttl?: number, max?: number }) => StoreReturns the named store, creating it on first call. ttl is milliseconds (default 10 minutes), max is the entry limit (default 500).
clear(prefix)(prefix?: string) => voidEmpties every store whose name starts with prefix, or all stores.
stats()() => Array<object>Size, limits, hits, and misses for every store.

Structured Console (this.medusa.console)

Property / MethodSignatureDescription
log(level, text)(level: string, text: string) => voidLogs formatted, timestamped output to stdout and daily log file.
levelsobjectSeverity levels: { info, success, warning, error, debug, startup }.
colorsColorsANSI color painter: colors.green(text), colors.red(text), colors.bright.bold.white(text), etc.
error(target, extra, footer)(target: Error | string, extra?: string, footer?: string) => voidRenders a styled ASCII error panel with stack trace and issue location.