Migration
Migrate to @robojs/discordjs
Move an existing Discord bot to Robo.js, or update from the old coupled architecture to the new @robojs/discordjs plugin.
For the complete migration reference including all migration paths and detailed before/after examples, see the @robojs/discordjs Migration guide.
Side-by-side code comparison showing traditional Discord.js boilerplate on the left versus the Robo.js single-file command equivalent on the right
From Discord.js
Three migration paths are available depending on your bot's complexity.
Full Migration
Move commands to src/commands/, events to src/events/, and remove manual client setup. Robo.js handles client creation, login, and command registration automatically.
- Install Robo.js and the Discord plugin.
- Move command handlers into
src/commands/using the file-based routing convention. - Move event handlers into
src/events/named after the Discord event. - Transfer
clientOptionstoconfig/plugins/robojs/discordjs.ts. - Run
robo devto start your bot.
Before (Discord.js):
import { Client, GatewayIntentBits } from 'discord.js'
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
client.on('ready', () => {
console.log(`Logged in as ${client.user?.tag}`)
})
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return
if (interaction.commandName === 'ping') {
await interaction.reply('Pong!')
}
})
client.login(process.env.DISCORD_TOKEN)import { Client, GatewayIntentBits } from 'discord.js'
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
client.on('ready', () => {
console.log(`Logged in as ${client.user?.tag}`)
})
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return
if (interaction.commandName === 'ping') {
await interaction.reply('Pong!')
}
})
client.login(process.env.DISCORD_TOKEN)After (Robo.js):
export default () => {
return 'Pong!'
}export default () => {
return 'Pong!'
}import { getClient } from '@robojs/discordjs'
export default () => {
const client = getClient()
console.log(`Logged in as ${client.user?.tag}`)
}import { getClient } from '@robojs/discordjs'
export default () => {
const client = getClient()
console.log(`Logged in as ${client.user?.tag}`)
}import type { DiscordConfig } from '@robojs/discordjs'
export default {
clientOptions: {
intents: ['Guilds']
}
} satisfies DiscordConfigexport default {
clientOptions: {
intents: ['Guilds']
}
}Gradual Migration
Use the start lifecycle hook to run existing setup code alongside Robo handlers. This lets you migrate handlers incrementally.
import { getClient } from '@robojs/discordjs'
export default () => {
const client = getClient()
// Keep existing handlers running while you migrate them one by one
client.on('messageCreate', (message) => {
if (message.content === '!hello') {
message.reply('Hello from legacy handler!')
}
})
}import { getClient } from '@robojs/discordjs'
export default () => {
const client = getClient()
// Keep existing handlers running while you migrate them one by one
client.on('messageCreate', (message) => {
if (message.content === '!hello') {
message.reply('Hello from legacy handler!')
}
})
}New commands and events can use the Robo.js file structure while existing ones continue running through the start hook. As you migrate each handler to a file in src/events/ or src/commands/, remove it from the start hook.
Custom Entry Point
Configure a custom entry point in config/robo.ts if you need full control over the startup process. This is useful for bots with complex initialization requirements that cannot be easily decomposed into lifecycle events.
From Old Robo.js (pre-v0.11)
For a complete v0.11 migration guide, see Upgrading to v0.11.
Existing Robo.js users migrating to v0.11 need to install the @robojs/discordjs plugin and update imports. Discord-specific functionality has moved from the core framework into a dedicated plugin.
Import Changes
| Before (old) | After (new) |
|---|---|
import { createCommandConfig } from 'robo.js' | import { createCommandConfig } from '@robojs/discordjs' |
import type { CommandResult } from 'robo.js' | import type { CommandResult } from '@robojs/discordjs' |
import type { CommandConfig } from 'robo.js' | import type { CommandConfig } from '@robojs/discordjs' |
import type { CommandOptions } from 'robo.js' | import type { CommandOptions } from '@robojs/discordjs' |
import { client } from 'robo.js' | import { getClient } from '@robojs/discordjs' |
VS Code find-and-replace showing the import migration pattern: replacing from robo.js with from @robojs/discordjs across multiple files
Configuration Changes
Discord-specific configuration moves from config/robo.ts to config/plugins/robojs/discordjs.ts.
Before:
// old — no longer used for Discord settings
export default {
clientOptions: {
intents: ['Guilds', 'GuildMessages']
},
sage: {
defer: true
}
}// old — no longer used for Discord settings
export default {
clientOptions: {
intents: ['Guilds', 'GuildMessages']
},
sage: {
defer: true
}
}After:
import type { DiscordConfig } from '@robojs/discordjs'
export default {
clientOptions: {
intents: ['Guilds', 'GuildMessages']
},
sage: {
defer: true
}
} satisfies DiscordConfigexport default {
clientOptions: {
intents: ['Guilds', 'GuildMessages']
},
sage: {
defer: true
}
}Client Access
The client export no longer exists. Use getClient() instead.
Before:
import { client } from 'robo.js'
client.user?.setPresence({ status: 'idle' })import { client } from 'robo.js'
client.user?.setPresence({ status: 'idle' })After:
import { getClient } from '@robojs/discordjs'
export default () => {
const client = getClient()
client.user?.setPresence({ status: 'idle' })
}import { getClient } from '@robojs/discordjs'
export default () => {
const client = getClient()
client.user?.setPresence({ status: 'idle' })
}Intent Syntax
Intents use PascalCase strings. If you were already using this format, no changes are needed.
| Old Format | New Format |
|---|---|
'GUILD_MESSAGES' | 'GuildMessages' |
Intents.FLAGS.Guilds | 'Guilds' |
GatewayIntentBits.GuildMessages | 'GuildMessages' |
