Client
ArcClient extends discord.js Client. All discord.js options, properties, events, and REST methods are available on it.
Porting an existing discord.js bot? See Migrate from discord.js for side-by-side examples.
Constructor
import { ArcClient } from "arcscord";
const client = new ArcClient(token, options);
| Parameter | Type | Description |
|---|---|---|
token | string | Bot token from the Discord Developer Portal. |
options | ArcClientOptions | Client configuration. Extends discord.js ClientOptions. |
Options
intents (required)
Gateway intents to enable. Inherited from discord.js. At minimum "Guilds" is needed for slash commands.
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
});
All discord.js ClientOptions (partials, rest, presence, etc.) are also accepted.
applicationId
Discord application ID. When provided, loadCommands (and loadHandlers) can register commands via the REST API before the clientReady event fires, without waiting for discord.js to hydrate client.application.
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
applicationId: process.env.APPLICATION_ID!,
});
logger
Controls the built-in logger. All fields are optional.
| Option | Type | Default | Description |
|---|---|---|---|
level | "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "info" | Minimum level to log. Use "debug" to see command/component execution logs during development. |
format | "pretty" | "json" | "pretty" | Output format. Use "json" in production or containers. |
loggerFunc | (...data: unknown[]) => void | per-level console.log/console.error | Custom function to receive each log line. When omitted, warn/error/fatal go to console.error, everything else to console.log. |
customLogger | LoggerConstructor | ArcLogger | Replace the built-in logger class entirely. Must satisfy LoggerInterface. |
diagnostics | { loggerFunc, format? } | — | Secondary output for full error diagnostics. Providing loggerFunc is what turns it on. |
errorDetail | "short" | "full" | "short" if diagnostics is set, "full" otherwise | How much detail logError/fatalError print on the main sink. |
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
logger: {
level: process.env.NODE_ENV === "production" ? "info" : "debug",
format: process.env.NODE_ENV === "production" ? "json" : "pretty",
},
});
Defaults can also be set via environment variables: ARCSCORD_LOG_LEVEL, LOG_LEVEL, ARCSCORD_LOG_FORMAT, LOG_FORMAT.
enableInternalTrace
Enables verbose trace logs from framework internals — command dispatch, middleware steps, locale detection, etc. Useful for debugging framework behavior.
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
enableInternalTrace: true,
});
Default: false.
waitReady
Defines the default readiness timeout and check interval for the client. This configuration is also used by internal framework calls, such as queued events and loadHandlers when command registration must wait for Discord.
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
waitReady: {
timeout: 15_000,
checkInterval: 100,
},
});
| Option | Type | Default | Description |
|---|---|---|---|
timeout | number | 30000 | Maximum wait in milliseconds before rejecting. |
checkInterval | number | 50 | Delay in milliseconds between readiness checks. |
baseMessages
Overrides framework-generated messages sent to users. Currently supports one key:
baseMessages.error — the message sent when an unhandled error occurs during a command or component handler.
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
baseMessages: {
error: (id, context) => ({
content: `An error occurred (id: \`${id}\`). Please contact support.`,
}),
},
});
The context argument contains:
context.locale— the detected i18next language for the interaction (when locale manager is enabled).context.t— a fixed translation function for that locale.
managers
Per-manager configuration. All fields are optional.
| Field | Manages | Documentation |
|---|---|---|
managers.command | Slash, user, and message commands — execution handlers, dispatch diagnostics | Execution handlers |
managers.component | Buttons, select menus, modals — execution handlers, dispatch diagnostics | Execution handlers |
managers.event | Discord.js event listeners — intent checks, execution handlers | Execution handlers |
managers.locale | i18next integration — language map, detection, resources | Localization |
Example with event intent check configuration:
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
managers: {
event: {
intentCheck: {
missing: "warn", // warn when an event has no matching intent
partialCoverage: "off",
},
},
},
});
Manager properties
ArcClient exposes four manager instances:
| Property | Description |
|---|---|
client.commandManager | Registers commands with Discord and dispatches interactions. |
client.componentManager | Routes component custom IDs and dispatches interactions. |
client.eventManager | Wraps discord.js event listeners with execution handling. |
client.localeManager | i18next wrapper used at registration time and per interaction. |
Methods
waitReady(options?)
Waits for the Discord client to become ready. Per-call options override the defaults configured through ArcClientOptions.waitReady. The promise rejects with ArcClientReadyTimeoutError when the effective timeout is reached.
await client.waitReady({
timeout: 15_000,
checkInterval: 100,
});
| Option | Type | Fallback | Description |
|---|---|---|---|
timeout | number | 30000 | Maximum wait in milliseconds before rejecting. |
checkInterval | number | 50 | Delay in milliseconds between readiness checks. |
The previous numeric form remains supported: waitReady(100) sets checkInterval to 100 ms and keeps the globally configured timeout.
Loading comes in two tiers with deliberately different error handling:
- The per-category loaders (
loadCommands,loadComponents,loadEvents) areasyncand return an ArcscordResult—[error, count]. On failure the first tuple item is anArcscordErrorwhosecodeidentifies the problem. They never throw for expected failures such as a duplicate route or an unmet intent requirement — you inspect the outcome. - The convenience
loadHandlersis a bootstrap helper that fails fast: it throws the firstArcscordErrorinstead of returning it, so a broken startup crashes loudly rather than continuing in a half-wired state. Reach for the per-category loaders when you want to handle the failure yourself.
loadHandlers(handlers, logs?)
Convenience method. Loads events, then components, then commands in a single call. Throws the first ArcscordError on failure; returns a HandlersLoadReport ({ commands, components, events } load counts) on success.
// Fail-fast: an unhandled throw crashes the process at startup.
const report = await client.loadHandlers({
commands: [avatarCommand, pingCommand],
components: [simpleButton, profileModal],
events: [messageEvent],
});
// report -> { commands: 2, components: 2, events: 1 }
// Or handle it explicitly:
try {
await client.loadHandlers(handlers);
}
catch (err) {
client.logger.fatalError(err);
process.exit(1);
}
If applicationId is set, commands are registered immediately over REST without waiting for clientReady. Otherwise, loadHandlers waits for the client to be ready before pushing commands.
loadCommands(commands, group?, guild?)
Registers commands with Discord and loads them into the command manager. Returns Result<number, ArcscordError> (the count of loaded commands); inspect error.code to distinguish validation, application, and registration failures.
const [err] = await client.loadCommands([pingCommand, avatarCommand]);
if (err !== null) {
client.logger.fatalError(err);
}
// Guild-scoped registration
await client.loadCommands([adminCommand], "admin", process.env.GUILD_ID!);
The optional group parameter is an internal label for the command set (used by deleteUnloadedCommands). The optional guild parameter restricts registration to a specific guild.
loadComponents(components)
Loads component handlers into the component manager. Returns Result<number, ArcscordError> (the count of loaded components). Fails with COMPONENT_VALIDATION_FAILED, COMPONENT_ROUTE_DUPLICATE, or COMPONENT_ROUTE_INVALID.
const [err, count] = await client.loadComponents([simpleButton, profileModal, roleMenu]);
Use client.componentManager.unloadComponent(route) to remove a previously loaded component; it returns true when a component was removed.
loadEvents(events)
Registers event handlers and attaches discord.js listeners. Returns Result<number, ArcscordError> (the count of loaded events). Fails with EVENT_HANDLER_DUPLICATE or EVENT_INTENT_MISSING.
const [err, count] = await client.loadEvents([messageEvent, inviteEvent]);
Use client.eventManager.unloadEvent(name) to remove a previously loaded event.
createLogger(name)
Returns a new logger instance scoped to the given name, using the same output function and configuration as the client logger.
const log = client.createLogger("my-module");
log.info("started");
Full setup example
import { ArcClient } from "arcscord";
import { avatarCommand, pingCommand } from "./commands";
import { simpleButton, profileModal } from "./components";
import { messageEvent } from "./events";
const client = new ArcClient(process.env.DISCORD_TOKEN!, {
intents: ["Guilds"],
applicationId: process.env.APPLICATION_ID,
logger: {
level: process.env.NODE_ENV === "production" ? "info" : "debug",
format: process.env.NODE_ENV === "production" ? "json" : "pretty",
},
managers: {
event: {
intentCheck: { missing: "warn" },
},
},
});
await client.loadHandlers({
commands: [avatarCommand, pingCommand],
components: [simpleButton, profileModal],
events: [messageEvent],
});
void client.login();