Migration
Migrate to @robojs/discordjs from Discord.js or older Robo.js versions.
Move an existing Discord bot to Robo.js with @robojs/discordjs, or update from the old coupled architecture to the new plugin-based system.
This page covers Discord.js-specific migration details. For the complete v0.11 migration guide, see Upgrading to v0.11.
From Discord.js
Three migration paths depending on your bot's complexity.
Full migration
Replace manual Client setup with file-based routing entirely. Robo.js handles client creation, login, and command registration automatically.
- Install Robo.js and the Discord plugin
- Move command handlers into
src/commands/ - Move event handlers into
src/events/ - 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 type { EventConfig } from '@robojs/discordjs'
import type { Client } from 'discord.js'
export const config: EventConfig = {
frequency: 'once'
}
export default (client: Client) => {
console.log(`Logged in as ${client.user?.tag}`)
}export const config = {
frequency: 'once'
}
export default (client) => {
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 src/robo/start.ts to keep existing code running while you migrate handlers incrementally. This is the recommended approach for large bots.
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()
client.on('messageCreate', (message) => {
if (message.content === '!hello') {
message.reply('Hello from legacy handler!')
}
})
}New commands and events use the Robo.js file structure while existing ones continue running through robo/start.ts. As you migrate each handler to src/events/ or src/commands/, remove it from the start file.
Use src/robo/start.ts for startup work while migrating. Underscore-prefixed files in src/events/ are not indexed as Discord gateway handlers.
Custom entry point
For advanced setups that need full control over the startup process, configure a custom entry point in config/robo.ts. This is useful for bots with complex initialization requirements that can't be decomposed into lifecycle files.
From older Robo.js (pre-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' |
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
}
}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' })After:
import { getClient } from '@robojs/discordjs'
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' |
Command migration
Converting client.on('interactionCreate', ...) command routing to file-based commands:
Before:
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return
switch (interaction.commandName) {
case 'ping':
await interaction.reply('Pong!')
break
case 'echo':
const text = interaction.options.getString('text')
await interaction.reply(text ?? 'Nothing to echo')
break
}
})After:
export default () => 'Pong!'import type { CommandConfig } from '@robojs/discordjs'
export const config: CommandConfig = {
description: 'Echo a message',
options: [{ name: 'text', type: 'string', required: true }]
}
export default (interaction, options: { text: string }) => {
return options.text
}Event migration
Converting client.on(event, ...) to file-based events:
Before:
client.on('guildMemberAdd', (member) => {
const channel = member.guild.systemChannel
channel?.send(`Welcome, ${member}!`)
})After:
import type { GuildMember } from 'discord.js'
export default (member: GuildMember) => {
const channel = member.guild.systemChannel
channel?.send(`Welcome, ${member}!`)
}Legacy lifecycle events
If you used _start or _stop events in older Robo.js versions, migrate them to src/robo/start.ts and src/robo/stop.ts. See Lifecycle for the current hook structure.
