Install VorqulBOT, the self-hosted Discord bot
Two deployment paths, one configuration. The Discord side is the same either way, and it is the part people get wrong, so it comes first.
Installing VorqulBOT takes about 10 to 15 minutes. Create an application in the Discord Developer Portal, enable the presence, server members and message content intents, and copy the token and the application ID. Then either install Node.js 22 LTS on a clean Debian or Ubuntu server, copy .env.example to .env, run npm install and npm start and keep it alive with systemd, or import the included egg into Pterodactyl, upload the files and fill in the Startup variables. Storage is JSON files by default or MySQL/MariaDB, chosen with one line in .env.
- Time
- About 10 to 15 minutes
- Runtime
- Node.js 18+, 22 LTS advised
- Paths
- Debian or Ubuntu server, Pterodactyl
- Storage
- JSON files or MySQL/MariaDB
Requirements
| RAM | 512 MB minimum, 1 GB advised, 2 GB if you run the dashboard |
|---|---|
| CPU | 1 core minimum, 2 advised |
| Disk | 500 MB minimum, 1 GB advised |
| Node.js | 18 minimum, 22 LTS advised (18 and 20 are past upstream end of life) |
| npm | Ships with Node.js, nothing separate to install |
| Database | Optional, JSON files need nothing installed |
Part 1: The Discord side#
This half is identical for every deployment path, and skipping the intents is the single most common reason a freshly installed bot does nothing.
-
Step 01
Create the application#
In the Discord Developer Portal, create a new application, then add a bot to it and reset the token to reveal it.
The token is shown once. Copy it somewhere safe and never commit it, paste it into a support channel or put it in a screenshot. If it leaks, reset it. Discord invalidates the old one immediately.
Also copy the Application ID from General Information. The bot needs it as DISCORD_CLIENT_ID to register its slash commands, so it is not optional even if you never open the dashboard.
-
Step 02
Enable the three privileged intents#
Under Bot, in Privileged Gateway Intents, switch on Presence Intent, Server Members Intent and Message Content Intent.
Without Message Content the bot cannot see message text, so AutoMod, leveling, prefix commands and sticky messages all silently do nothing. Without Server Members it cannot react to joins, which takes out welcome cards, join roles, invite tracking and anti-raid.
If the bot connects but ignores everything, come back to this step first.
-
Step 03
Invite the bot to your server#
Under OAuth2, in URL Generator, select the bot and applications.commands scopes, choose the permissions, then open the generated URL and authorise it on your server.
The applications.commands scope is what lets the 83 slash commands appear. Leave it out and the bot joins the server without a single command.
-
Step 04
Collect the OAuth credentials for the dashboard#
Under OAuth2, in General, reset and copy the Client Secret, then add your callback URL to Redirects.
Only needed if you want the web dashboard. The bot itself runs without the secret and without any redirect.
Redirect URLs http://localhost:3000/api/auth/callback/discord https://your-domain.tld/api/auth/callback/discord
Part 2: Choose your storage#
Two real backends, one line in .env, no separate edition. Pick by how big the server is and ignore how the names sound.
-
Step 05
JSON files - nothing to install#
One file per table under data/json. This is the default, it needs no service and no setup, and it is what most self-hosted instances run on.
.env DATABASE_TYPE=json DATA_PATH=./dataThe bot appends /json to DATA_PATH by itself, so DATA_PATH=./data ends up in ./data/json. Do not write ./data/json here or the files land one level too deep.
-
Step 06
MySQL or MariaDB - for a large server#
Install the server, create a database and a user with utf8mb4, then point the bot at it. Worth it once the server is big or you want the data alongside other services.
Then set DATABASE_TYPE=mysql together with DB_HOST, DB_PORT, DB_USER, DB_PASSWORD and DB_NAME in .env.
DATABASE_TYPE=sqlite is not a third option. This version has no SQLite driver, so the value logs a warning and quietly uses the JSON files instead. Your data is still saved, but write json so the configuration says what actually happens.
MariaDB on Debian sudo apt install -y mariadb-server sudo mysql CREATE DATABASE vorqul CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'vorqul'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON vorqul.* TO 'vorqul'@'localhost'; FLUSH PRIVILEGES;utf8mb4 is not optional. Anything less mangles emoji and diacritics in stored messages.
Part 3a: Deploy on a clean Debian or Ubuntu server#
The path to take if you have your own machine. Everything below runs as an ordinary user with sudo, and the bot itself runs as a dedicated user with no sudo of its own.
-
Step 07
Install Node.js 22 and git#
Update the system, install the basics, add the NodeSource repository for Node 22 and install it. npm comes with it.
bash sudo apt update && sudo apt upgrade -y sudo apt install -y curl ca-certificates git unzip curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs node --version npm --versionnode --version should print v22.x. Anything from v18 up runs the bot, but 18 and 20 no longer get upstream security fixes.
-
Step 08
Create a user for the bot#
A separate user keeps the token and the data out of reach of everything else on the machine.
The second command opens a shell as that user. Every command in the next steps runs there, until the systemd step.
bash sudo adduser --disabled-password --gecos "" --home /opt/vorqul vorqul sudo -iu vorqul -
Step 09
Put the files in place#
Clone the repository into a directory of its own, or unpack a release archive there. package.json has to sit directly in the working directory.
Prefer a release archive? Upload it, run unzip, and move the contents up so that package.json is not one level down.
bash git clone https://github.com/Yamiru/Vorqul-DS-BOT.git app cd app -
Step 10
Write the configuration#
Copy .env.example to .env and fill in the token, the application ID and the storage type. The file holds secrets, so restrict who can read it.
- DISCORD_TOKEN: required, from the Developer Portal
- DISCORD_CLIENT_ID: required, the Application ID, and without it no slash command is registered
- DATABASE_TYPE: json or mysql (see Part 2)
- BOT_OWNER_ID: your Discord user ID, unlocks the owner-only commands
- DASHBOARD_ENABLED: leave false unless you want the dashboard (see below)
bash cp .env.example .env chmod 600 .env nano .env -
Step 11
Install dependencies and start it#
npm install pulls the dependencies, npm start checks the token, compiles the TypeScript and runs the bot in the foreground so you can watch the first boot.
The repository ships an .npmrc with legacy-peer-deps, which stops npm refusing to install because of peer dependency conflicts in the development tools. Do not delete it.
The first start compiles the code and takes a minute or two. Later starts skip the compile until the source changes. Stop it with Ctrl+C once you see it online and the slash commands registered.
bash npm install npm startWatch this first run. Errors about the token, the application ID or the intents show up here and nowhere else. Do not install with --omit=dev or NODE_ENV=production: the compiler is a development dependency and the build would fail.
-
Step 12
Keep it running with systemd#
A service restarts the process when it dies and brings it back after a reboot. This is the step people skip and then wonder why the bot went offline overnight.
Do not add Environment=NODE_ENV=production to the unit. The launcher runs npm install itself and would then skip the development dependencies it needs to compile. It sets production mode for the dashboard on its own.
The launcher also writes its own rotated log to logs/current.log inside the bot directory, next to the journal.
Prefer PM2? sudo npm install -g pm2, then pm2 start start.js --name vorqul, pm2 startup and pm2 save do the same job.
bash (as your normal user, not vorqul) sudo tee /etc/systemd/system/vorqul.service >/dev/null <<'EOF' [Unit] Description=Vorqul DS BOT After=network-online.target Wants=network-online.target [Service] Type=simple User=vorqul WorkingDirectory=/opt/vorqul/app ExecStart=/usr/bin/node start.js Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now vorqul sudo systemctl status vorqul journalctl -u vorqul -f -
Step 13
Optionally enable the dashboard#
The dashboard is not a second thing to install. Switch it on in .env and the same launcher builds it and starts it as a second process on port 3000.
Restart the service and give the first boot a few extra minutes, because the dashboard is built then. That build wants around 2 GB of RAM.
Do not expose port 3000 directly. Put nginx in front of it with a certificate. Leave NEXTAUTH_URL empty and the address is detected automatically. If NEXTAUTH_SECRET is empty the launcher generates one and keeps it in data/secrets, so it survives restarts.
Set ALLOWED_EMAILS to the e-mail of your Discord account, otherwise anyone who can sign in with Discord can reach the dashboard.
.env DASHBOARD_ENABLED=true DASHBOARD_PORT=3000 DISCORD_CLIENT_SECRET=your_client_secret NEXTAUTH_SECRET=output_of_openssl_rand_-hex_32 DASHBOARD_PASSWORD=
Part 3b: Deploy on Pterodactyl#
The path to take if you already run a game hosting panel. No shell needed at any point.
-
Step 14
Import the egg#
In the admin area, open Nests, choose Import Egg and upload egg-vorqul-d-s-bot.json from the project.
The egg sets the startup command to npm start and creates the Startup variables for you, including the two required ones: DISCORD_TOKEN and DISCORD_CLIENT_ID. It offers Node.js 20 and Node.js 18 images. Both work, but both are past upstream end of life, so if your panel provides ghcr.io/pterodactyl/yolks:nodejs_22, add it under the egg Docker Images and select it for the server.
-
Step 15
Create the server#
Create a new server on that egg with 1 GB of RAM (2 GB if you will run the dashboard), 1 GB of disk and no special CPU limit.
A network allocation only matters for the dashboard. The bot itself connects out to Discord and listens on nothing.
-
Step 16
Upload the files#
Upload the release archive with the File Manager or over SFTP, extract it, and move the contents so that package.json sits in the container root.
Expected layout /home/container/ bot.py start.js package.json package-lock.json .npmrc tsconfig.bot.json src/ plugins/If package.json ended up inside a subfolder, the launcher will not find the source. Move everything up one level.
-
Step 17
Fill in the Startup variables#
The token and the storage type go in as environment variables rather than in a .env file.
- DISCORD_TOKEN: your bot token
- DISCORD_CLIENT_ID: the Application ID, required
- DATABASE_TYPE: json (the default) or mysql
- BOT_OWNER_ID: your Discord user ID, recommended
- DASHBOARD_ENABLED, DASHBOARD_PORT, DISCORD_CLIENT_SECRET, NEXTAUTH_URL and NEXTAUTH_SECRET are only needed for the dashboard
Using the Pterodactyl database host: set DB_HOST to the host the panel shows on the Databases tab, and use the prefixed user and database names it generated. For the dashboard, leave DASHBOARD_PORT empty to reuse the allocated port, and make NEXTAUTH_URL http or https exactly as it is really reachable.
-
Step 18
Check the startup command#
npm start on the Node.js egg. python3 bot.py exists as an alternative launcher, but it only works in a container that has Node.js and npm installed as well, which a plain Python image does not.
The Python launcher starts the bot only, without the dashboard. Use the Node.js egg unless you have a reason not to.
Startup command npm start -
Step 19
When the Pterodactyl panel itself misbehaves#
These run over SSH on the machine that hosts Pterodactyl, not inside the bot. If the panel acts strange, restart pteroq and nginx first. If servers will not start, restart wings.
bash sudo systemctl restart pteroq sudo systemctl restart nginx sudo systemctl restart wings sudo systemctl restart mariadb sudo systemctl restart redis-server sudo systemctl status wings sudo journalctl -u wings -f -
Step 20
Press Start#
The first boot installs dependencies and compiles the code by itself, which takes one to three minutes. After that the bot comes online.
If it exits immediately, read the console output. A missing token or application ID fails here with a clear message. The console also shows if the compile fell back to the slower tsx runtime.
Part 4: First setup inside Discord#
The bot is online. Three settings decide whether it is useful or annoying.
-
Step 21
Turn on the modules you need#
Nine of the 44 modules are on out of the box: moderation, leveling, afk, polls, economy, games, giveaways, reminders and prefix commands. Everything else starts off, on purpose.
Enabling AutoMod on a live server before you have configured it is how you punish people for behaviour that was fine yesterday. Turn filters on one at a time.
-
Step 22
Set a staff role and a log channel#
Do this before anything else. Without a log channel, moderation actions happen with no visible record, and the first dispute is unanswerable.
Every automated and manual action writes a numbered case regardless, but a log channel is what makes it visible while it is happening.
-
Step 23
Verify the install#
Run /info ping and /help. If the bot answers and the commands are listed, the token, the intents and the command registration are all working.
Missing slash commands mean the applications.commands scope was left out of the invite, or DISCORD_CLIENT_ID is empty or wrong. Discord can take a few minutes to show newly registered commands.
Part 5: Locking it down#
Five minutes here decide whether strangers can use your bot.
-
Step 24
Turn off Public Bot#
In the Developer Portal open your application, go to Bot and switch off PUBLIC BOT.
With it on, anyone who knows your client ID can add the bot to their server. This switch is the only real protection against that.
-
Step 25
Lock the dashboard#
Set ALLOWED_EMAILS or ALLOWED_IDS in .env. Only those accounts, plus people who manage a server the bot is already on, can sign in. Everyone else does not see the Add bot button.
DASHBOARD_PASSWORD adds one more gate in front of the sign-in. Generate NEXTAUTH_SECRET once with openssl rand -hex 32 and keep it, because changing it signs everybody out.
Set BOT_OWNER_ID to your own Discord ID. Owner commands and plugin management belong to that ID only, and several IDs are separated by commas.
-
Step 26
Treat the token like a password#
Never send it to anyone and never upload .env to GitHub (it is in .gitignore). If it leaks, press Reset Token in the Developer Portal and paste the new one.
Part 6: The .env reference#
Every variable, what it does and whether you need it.
-
Step 27
Variables#
The first two are required. Everything else has a sensible default.
- DISCORD_TOKEN (required): bot token, Developer Portal, Bot, Reset Token
- DISCORD_CLIENT_ID (required): Application ID, OAuth2, General
- DISCORD_CLIENT_SECRET (dashboard only): OAuth2, Reset Secret
- BOT_OWNER_ID (recommended): your Discord ID
- DATABASE_TYPE (required): json, which needs nothing installed, or mysql
- DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME (mysql only): MySQL connection details
- DATA_PATH: data folder, leave at ./data
- DASHBOARD_ENABLED: true switches the dashboard on
- DASHBOARD_PORT: dashboard port, leave empty on Pterodactyl
- NEXTAUTH_URL: leave empty, the address is detected automatically
- NEXTAUTH_SECRET (dashboard): random string that signs the login
- DASHBOARD_PASSWORD: extra password in front of the dashboard
- ALLOWED_EMAILS, ALLOWED_IDS (recommended): who may sign in, comma separated
- BOT_PREFIX: prefix for text commands, ! by default
- BOT_LANGUAGE: en, sk, cs, de, pl, ru, es, ja or tr
- TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET: for Twitch notifications
- DISABLED_COMMANDS: commands to hide from the / menu, because Discord allows 100
- LOG_TO_FILE: true writes logs to data/logs
DATABASE_TYPE only covers data such as economy, warnings and tickets. Server settings (modules, AutoMod, welcome) always live in data/json/guild_settings.json. Switching from json to mysql does not move old data by itself.
Part 7: First dashboard sign-in#
Six steps, and the third one is where most people get stuck.
-
Step 28
Set the variables#
DASHBOARD_ENABLED=true, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET and NEXTAUTH_SECRET.
-
Step 29
Add the redirect URL#
In the Developer Portal, OAuth2, General, Redirects, add every address you sign in from, domain and IP:port alike.
Redirect URLs http://YOUR-IP:PORT/api/auth/callback/discord https://your-domain.tld/api/auth/callback/discord -
Step 30
Restart and sign in#
Restart the bot, open http://YOUR-IP:PORT (the port is DASHBOARD_PORT, or the port allocated to the server on Pterodactyl), press Sign in with Discord, approve access and pick your server.
If you see an authentication processing error, check DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET, or clear the cookies for the site.
Part 8: What the dashboard does#
A short tour so you know what to look for.
-
Step 31
Features#
Everything below works per server, from one login.
- Server overview, Server Health Score and analytics for messages, joins and leaves
- 44 modules to switch on and off: moderation, AutoMod, anti-nuke, anti-raid, levels, economy, tickets, welcome, reaction roles, giveaways and more
- Settings in tabs, plus limiting modules to chosen channels
- Embed builder and a library of saved messages
- Backup and restore in three modes: complete, server only, bot settings only
- Plugins: install from a ZIP, enable, disable and configure
- Role Manager: a chosen role may manage the server in the dashboard, but not plugins
- Bot version with a notice when a newer one is out
- 9 languages, and refresh and clear-cache buttons in every tab
-
Step 32
The pages#
Five screens, in the order you meet them.
- Gate: an optional password screen, switched on with DASHBOARD_PASSWORD
- Login: sign in with Discord
- Server picker: lists the servers you manage where the bot is present
- Server home: bot status with online state, ping, uptime, memory and CPU, server stats and a notice when an update is out
- Settings: one page with tabs, listed in the next step
-
Step 33
The settings tabs#
Each tab groups related modules, so a feature is never more than one click from its switch.
- General: language, prefix and manager role
- Channels and Channel scopes: restrict where the bot works
- Modules: turn each feature on or off
- Moderation and security: AutoMod, Warnings, Appeals, Verification and Logging
- Members: Welcome and Goodbye, Reaction roles and Birthday
- Engagement: Leveling, Starboard, Suggestions, Reputation, Counting, Confessions and Giveaways
- Economy and games: Economy and Games
- Content: Tickets, Applications, Autoresponse, Sticky messages, Tags, Feeds (RSS, YouTube, Twitch, Reddit), Socials and Reminders
- Tools: Temp channels, Crypto, Backup and Restore, Webhooks, Embed builder, Links and Help
- Admin: Analytics, Audit log, API keys, Plugins (upload a ZIP, owner only) and Premium
-
Step 34
What the bot itself does#
The slash commands, and the same commands with the ! prefix, grouped by area.
- Moderation: ban, tempban, softban, unban, kick, mute, unmute, timeout, voicemute, warn, notes, case, jail, shadowmute, clear, cleanup, lock, lockdown, slowmode, nick, role, timedrole, filter, altcheck and permcheck
- Security: AutoMod, anti-nuke, anti-raid lockdown, verification, appeals, message and server logs, role snapshots and Persist, which restores roles and nickname when a member rejoins
- Members: welcome and goodbye messages, join roles, reaction roles, role menus, invite tracking, name history and referrals
- Engagement: XP and levels with level roles, rank and leaderboards for messages and voice, starboard, suggestions, reputation, a counting channel, birthdays, confessions, AFK, polls, streaks, quests and giveaways
- Economy: balance, daily, work, pay, bank, shop, market, lootbox, spin and heist
- Games: 8ball, coinflip, dice, rock-paper-scissors, tictactoe, connect4, trivia, matchmaking and game stats
- Content tools: tickets, applications, autoresponses, sticky messages, tags, feeds and socials, reminders, a digest, and commands that answer what did I miss and why was I banned
- Utility: temp voice channels, crypto prices, Minecraft status, info, help, links, backup, webhook events, API keys with a read-only v1 stats API, and Premium
- Plugins: drop-in plugins such as sample_plugin, minecraft_status and fear_greed, each with settings per server
Part 9: Updating#
The launcher notices what changed, so an update is short.
-
Step 35
Update to a new release#
Get the new files while keeping .env and the data directory, then restart. The launcher reinstalls dependencies when package.json changes and recompiles when the source changes.
On Pterodactyl, upload the new files over the old ones without deleting .env, data or plugins, then restart the server. Settings carry over through the schema migrations.
bash sudo -iu vorqul cd app git pull exit sudo systemctl restart vorqul
Troubleshooting
The failures that actually happen, and what causes them.
Questions about installing
Summary
Installing VorqulBOT takes about 10 to 15 minutes. Create an application in the Discord Developer Portal, enable the presence, server members and message content intents, and copy the token and the application ID. Then either install Node.js 22 LTS on a clean Debian or Ubuntu server, copy .env.example to .env, run npm install and npm start and keep it alive with systemd, or import the included egg into Pterodactyl, upload the files and fill in the Startup variables. Storage is JSON files by default or MySQL/MariaDB, chosen with one line in .env.
- Time: About 10 to 15 minutes
- Runtime: Node.js 18+, 22 LTS advised
- Paths: Debian or Ubuntu server, Pterodactyl
- Storage: JSON files or MySQL/MariaDB
Pterodactyl egg Self-hosted Privileged intents Slash command OAuth2 dashboard sign-in Module registry Prefix bridge
VorqulBOT
What the bot is, module by module.
AutoMod
The 15 filters and what they cost you if switched on carelessly.
Direct answers
Price, requirements and limits in one place.
Reviewed