LogoRobo.js

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

FocusThe before/after code comparison highlighting simplificationZoom100%NotesCapture VS Code with two editor tabs side by side using the split editor view. Left tab: a Discord.js index.ts file with ~20 lines of boilerplate (client creation, login, event/command registration). Right tab: Robo.js with ping.ts (3 lines) and ready.ts (5 lines) in separate tabs. Ensure both are clearly labeled in the editor tabs.

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.

  1. Install Robo.js and the Discord plugin.
  2. Move command handlers into src/commands/ using the file-based routing convention.
  3. Move event handlers into src/events/ named after the Discord event.
  4. Transfer clientOptions to config/plugins/robojs/discordjs.ts.
  5. Run robo dev to start your bot.
ping.ts/ping
ready.tsClient ready
discordjs.tsPlugin config

Before (Discord.js):

index.ts
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)
index.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)

After (Robo.js):

src/commands/ping.ts
export default () => {
	return 'Pong!'
}
src/commands/ping.js
export default () => {
	return 'Pong!'
}
src/events/ready.ts
import { getClient } from '@robojs/discordjs'

export default () => {
	const client = getClient()
	console.log(`Logged in as ${client.user?.tag}`)
}
src/events/ready.js
import { getClient } from '@robojs/discordjs'

export default () => {
	const client = getClient()
	console.log(`Logged in as ${client.user?.tag}`)
}
config/plugins/robojs/discordjs.ts
import type { DiscordConfig } from '@robojs/discordjs'

export default {
	clientOptions: {
		intents: ['Guilds']
	}
} satisfies DiscordConfig
config/plugins/robojs/discordjs.mjs
export 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.

src/robo/start.ts
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!')
		}
	})
}
src/robo/start.js
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

FocusThe find-and-replace dialog showing the import migrationZoom100%NotesShow VS Code with find-and-replace open. Search: from robo.js, replace: from @robojs/discordjs. Show affected files in results.

Configuration Changes

Discord-specific configuration moves from config/robo.ts to config/plugins/robojs/discordjs.ts.

Before:

config/robo.ts
// old — no longer used for Discord settings
export default {
	clientOptions: {
		intents: ['Guilds', 'GuildMessages']
	},
	sage: {
		defer: true
	}
}
config/robo.mjs
// old — no longer used for Discord settings
export default {
	clientOptions: {
		intents: ['Guilds', 'GuildMessages']
	},
	sage: {
		defer: true
	}
}

After:

config/plugins/robojs/discordjs.ts
import type { DiscordConfig } from '@robojs/discordjs'

export default {
	clientOptions: {
		intents: ['Guilds', 'GuildMessages']
	},
	sage: {
		defer: true
	}
} satisfies DiscordConfig
config/plugins/robojs/discordjs.mjs
export 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 FormatNew Format
'GUILD_MESSAGES''GuildMessages'
Intents.FLAGS.Guilds'Guilds'
GatewayIntentBits.GuildMessages'GuildMessages'

Next Steps

On this page