Skip to main content

Migrate from discord.js

Arcscord does not replace discord.js — ArcClient extends discord.js's Client. Every option, property, event, and REST method you already use still works. This page shows the Arcscord equivalent of common raw discord.js patterns, so you can port a bot incrementally, one command or listener at a time.

Client setup

Before
import { Client, GatewayIntentBits } from "discord.js";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once("clientReady", () => {
console.log(`Logged in as ${client.user?.tag}`);
});

await client.login(process.env.DISCORD_TOKEN);
After
import { ArcClient, createEvent } from "arcscord";

const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
});

await client.loadEvents([
createEvent({
event: "clientReady",
options: { once: true },
run: (ctx) => {
ctx.client.logger.info(`Logged in as ${ctx.client.user?.tag}`);
},
}),
]);

await client.login();

See Client for the full option list — partials, rest, presence, and every other discord.js ClientOptions field is still accepted.

Slash commands

Before
import { REST, Routes, SlashCommandBuilder } from "discord.js";

const pingCommand = new SlashCommandBuilder()
.setName("ping")
.setDescription("Check if the bot is available");

const rest = new REST().setToken(process.env.DISCORD_TOKEN!);
await rest.put(Routes.applicationCommands(process.env.APPLICATION_ID!), {
body: [pingCommand.toJSON()],
});

client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;

if (interaction.commandName === "ping") {
await interaction.reply("Pong!");
}
});
After
import { createCommand } from "arcscord";

export const pingCommand = createCommand({
slash: {
name: "ping",
description: "Check if the bot is available",
},
run: ctx => ctx.reply("Pong!"),
});

await client.loadCommands([pingCommand]);

createCommand replaces both the builder and the manual interactionCreate dispatch: registration and routing to the right handler are handled by loadCommands/CommandManager. See Commands.

Buttons

Before
client.on("interactionCreate", async (interaction) => {
if (!interaction.isButton()) return;

const [action, ticketId] = interaction.customId.split(":");
if (action === "close-ticket") {
await interaction.reply(`Closing ticket #${ticketId}`);
}
});
After
import { button, createButton } from "arcscord";

export const closeTicketButton = createButton({
route: "ticket/{ticketId}/close",
build: id => button({
label: "Close ticket",
style: "danger",
customId: id(),
}),
run: ctx => ctx.reply(`Closing ticket #${ctx.params.ticketId}`),
});

The route replaces manual customId parsing: params are typed and extracted into ctx.params for you. See Buttons.

Events

Before
client.on("messageCreate", (message) => {
if (message.author.bot) return;
console.log(`${message.author.username}: ${message.content}`);
});
After
import { createEvent } from "arcscord";

export const messageCreateEvent = createEvent({
event: "messageCreate",
name: "messagePrefixLogger",
run: (ctx, message) => {
if (message.author.bot) {
return;
}

ctx.client.logger.debug(`message from ${message.author.username}: ${message.content}`);
},
});

await client.loadEvents([messageCreateEvent]);

createEvent types the callback arguments from the event name and plugs into the same result/error handling as commands and components. See Events.

Error handling

Before
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;

try {
await runCommand(interaction);
} catch (err) {
console.error(err);
await interaction.reply({ content: "Something went wrong.", ephemeral: true });
}
});
After
import { error, ok } from "@arcscord/error";
import { createCommand } from "arcscord";

export const riskyCommand = createCommand({
slash: {
name: "risky",
description: "Might fail",
},
run: async (ctx) => {
const result = await doSomethingRisky();
if (!result.success) {
return error(new Error("Something went wrong"));
}

return ok("Done!");
},
});

Result is optional: a handler may return nothing, string, or true for a successful execution. Return ok(...) or error(...) only when you need an explicit typed result. Arcscord normalizes all of these forms into an ExecutionOutcome consumed by the execution-handler chain. See Error handling and Execution handlers.