How to build a Discord bot with custom commands for your MU Online server
Build a Discord bot in Node.js for your MU Online server, with ranking, server status, character lookup and account linking commands, querying the MuServer's MS SQL database in real time.
A well-built Discord bot is nowadays one of the pillars of retention for any MU Online server: it's the player's first point of contact with the community, the channel where rankings, events and server status appear in real time, and the bridge between the game and support. Unlike generic "ready-mad
A well-built Discord bot is nowadays one of the pillars of retention for any MU Online server: it's the player's first point of contact with the community, the channel where rankings, events and server status appear in real time, and the bridge between the game and support. Unlike generic "ready-made" bots, a custom bot queries your MuServer's database directly, enabling commands like /ranking, /personagem and /status with real, up-to-the-second data. This tutorial builds a bot in Node.js with discord.js, from scratch through deployment, including a secure connection to MS SQL Server and the commands most requested by the Brazilian MU community.
Why build your own bot instead of using generic ones
General-purpose bots (moderation, music, memes) know nothing about your MU server. A bot of your own reads directly from the MuServer tables — Character, AccountCharacter, MEMB_STAT — and turns that data into useful commands inside Discord, so the player doesn't need to open the website or log into the game just to check their ranking position. This also opens the door to exclusive automations: automatic boss-spawn announcements, server-down alerts, and event notifications, all within the channel where the community already hangs out.
Prerequisites
- Node.js 18 or higher installed on the machine that will run the bot.
- An application registered in the Discord Developer Portal, with the bot token generated.
- Network access to your MuServer's MS SQL Server (host, port, username and password with read permission).
- Permission to add bots to the community's Discord server (administrator role or server owner).
- A code editor (VS Code is the most common) and git installed — optional but recommended.
Project structure
Organize the bot into folders separated by responsibility, making it easier to maintain as commands grow:
mu-discord-bot/
├── index.js
├── deploy-commands.js
├── config.json
├── commands/
│ ├── ranking.js
│ ├── status.js
│ ├── personagem.js
│ └── vincular.js
├── db/
│ └── connection.js
└── package.json
Step 1 — Create the application and the bot token
In the Discord Developer Portal, create a new application, go to "Bot" and click "Add Bot". Copy the generated token — it should never be committed to git or shared. Under "OAuth2 > URL Generator", check the bot and applications.commands scopes, select the Send Messages, Use Slash Commands and Embed Links permissions, and use the generated URL to invite the bot to your Discord server.
Step 2 — Install dependencies and set up the base
mkdir mu-discord-bot && cd mu-discord-bot
npm init -y
npm install discord.js mssql dotenv
Create a .env file (never version this) with the credentials:
DISCORD_TOKEN=your_token_here
CLIENT_ID=application_id
GUILD_ID=discord_server_id
DB_HOST=127.0.0.1
DB_USER=mu_bot_readonly
DB_PASS=strong_password_here
DB_NAME=MuOnline
Step 3 — Connect to the MS SQL database with a restricted account
Create the mu_bot_readonly database account with SELECT-only permission on the Character, AccountCharacter and MEMB_STAT tables. This is the difference between a leaked token becoming a serious security incident or just an exposure of public ranking data.
// db/connection.js
const sql = require('mssql');
const config = {
user: process.env.DB_USER,
password: process.env.DB_PASS,
server: process.env.DB_HOST,
database: process.env.DB_NAME,
options: { encrypt: false, trustServerCertificate: true }
};
async function getPool() {
return sql.connect(config);
}
module.exports = { getPool, sql };
Step 4 — Ranking command
// commands/ranking.js
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { getPool, sql } = require('../db/connection');
module.exports = {
data: new SlashCommandBuilder()
.setName('ranking')
.setDescription('Shows the server\'s top 10 resets'),
async execute(interaction) {
await interaction.deferReply();
const pool = await getPool();
const result = await pool.request().query(`
SELECT TOP 10 Name, cLevel, Resets
FROM Character
ORDER BY Resets DESC, cLevel DESC
`);
const embed = new EmbedBuilder()
.setTitle('🏆 Top 10 Resets')
.setColor(0xffcc00)
.setDescription(
result.recordset
.map((r, i) => `**${i + 1}.** ${r.Name} — Lvl ${r.cLevel} — ${r.Resets} resets`)
.join('\n')
);
await interaction.editReply({ embeds: [embed] });
}
};
Step 5 — Character lookup command
The /personagem <name> command should fetch only public data (level, class, resets, guild), never sensitive account information such as email or password. Validate the input parameter with sql.VarChar to prevent SQL injection — never concatenate strings directly into the query.
// commands/personagem.js (secure query snippet)
const request = pool.request();
request.input('nome', sql.VarChar, characterName);
const result = await request.query(
'SELECT Name, cLevel, Class, Resets FROM Character WHERE Name = @nome'
);
Step 6 — Server status command
A /status command that checks whether the GameServer and ConnectServer are up is one of the most used by the community, since it prevents players from trying to log into a server that's down without knowing it. The simplest check is opening a TCP socket to the ConnectServer port (usually 44405) with a short timeout and reporting success or failure.
Step 7 — Linking a Discord account to a game account
| Step | Where it happens | Description |
|---|---|---|
| Generate code | Web panel or in-game command | System generates a 6-digit code valid for 10 minutes |
| Enter code | Discord (DM to the bot) | Player sends /vincular 123456 |
| Validate and save | Bot + bot's own database | Bot checks the code and saves the discord_id ↔ account_id link |
| Use the link | Future commands | /meupersonagem already knows which account to look up without the player typing the name |
Never implement account linking by asking for the game account password over Discord — that normalizes a classic phishing vector within the community itself.
Step 8 — Register the slash commands and bring the bot online
// deploy-commands.js
const { REST, Routes } = require('discord.js');
const fs = require('fs');
require('dotenv').config();
const commands = fs.readdirSync('./commands')
.map(file => require(`./commands/${file}`).data.toJSON());
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
(async () => {
await rest.put(
Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
{ body: commands }
);
console.log('Commands registered successfully.');
})();
Run node deploy-commands.js once to register the commands, then start the bot with node index.js. For production, use a process manager like PM2 (pm2 start index.js --name mu-bot) to restart it automatically in case of a crash.
Step 9 — Hosting and continuous monitoring
A small VPS (1 vCPU, 1 GB RAM) can already run a medium-sized bot without issues, as long as the database isn't on the same overloaded instance as the GameServer. Set up file-based logging (not just console) and a simple alert — even a plain Discord webhook — so you know when the bot goes down, instead of finding out only when a player complains.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Bot doesn't respond to commands | deploy-commands.js not run after a change | Run the deploy again and wait for propagation (up to 1h for global commands) |
| Database connection error | Firewall blocking port 1433 | Open the port only for the bot machine's IP |
| Ranking command is slow | Query missing an index on the Resets column | Create an index on the column used in ORDER BY |
| Token exposed on GitHub | .env committed by mistake | Revoke the token immediately and generate a new one |
| Bot crashes on its own after hours running | Process without a restart manager | Use PM2 or systemd with automatic restart |
Launch checklist
- Application and token created in the Discord Developer Portal.
- Database account restricted to SELECT created and tested.
.envout of version control (.gitignoreconfigured).- Ranking, status and character lookup commands tested in production.
- Account-linking flow validated with no password exposure.
- Bot process running under PM2 or equivalent with automatic restart.
- Logs and downtime alert configured.
With the bot online and responding to commands in real time, the natural next step is integrating it with your server's automatic events — for example, announcing boss spawns or the start of invasions directly on Discord, closing the loop between the MU Online server and the community playing on it.
Frequently asked questions
Do I need to know how to program to have a bot like this?
Basic JavaScript knowledge is recommended, but this tutorial provides the complete base code. By copying the files and adjusting the database connection string, an administrator with intermediate knowledge can get the bot up and running without writing logic from scratch.
Does the bot need to run on the same server as the game?
No. It's actually recommended to run it on a separate machine, as long as it has network access to the MuServer's MS SQL Server (port 1433 open) or to a read-only replica of the database, which is safer.
Is it safe to give the bot direct access to the game's database?
Only with a database account restricted to SELECT on the necessary tables (ranking, characters, accounts). Never use the SQL Server admin account on the bot — if the bot token leaks, a limited read-only access drastically reduces the possible damage.
How do I build a secure Discord-to-game account linking command?
Generate a temporary 6-digit code inside the web panel or through an in-game command, and ask the player to type that code to the bot via direct message. The bot records the link in its own table — never ask for the player's game account password over Discord.
Can this bot be hosted for free?
For testing, yes, on a small VPS or a free Node.js hosting plan. For production, with hundreds of simultaneous users, a paid VPS with at least 1 vCPU and 1 GB of RAM dedicated to the bot, separate from the GameServer, is recommended.