Preconditions
Require a guild, permissions, ownership, or your own rule.
Preconditions are the command guardrail. They run before deferral, typing, and your callback. A failed check stops the invocation and can send a useful response to the user.
Common checks without ceremony
import { PermissionFlagsBits } from "discord.js";
import { CommandType } from "swagcommands";
export default {
type: CommandType.BOTH,
description: "Manage this server",
guildOnly: true,
permissions: [PermissionFlagsBits.ManageGuild],
minArgs: 1,
expectedArgs: "<action>",
callback: ({ args }) => `Action: ${args[0]}`,
};Top-level settings compile into preconditions in this order: guildOnly, ownerOnly, testOnly, permissions, then argument count, then your explicit preconditions array. expectedArgs is a usage hint; it triggers ArgumentCount only when minArgs or maxArgs is also set. Root subcommand checks run before leaf checks.
| Built-in | What it checks |
|---|---|
GuildOnly | Invocation has a guild |
OwnerOnly | User ID is in botOwners |
TestOnly | Guild ID is in testServers |
HasPermissions | Member has every requested permission |
ArgumentCount | Number of normalized arguments |
Cooldown | A cooldown bucket can be claimed |
Use botOwners explicitly for OwnerOnly and testServers for TestOnly. The built-ins work across message, slash, and context-menu flows where those values make sense.
Compose checks
An array means “all of these.” For branching, use any and all:
preconditions: [
"GuildOnly",
{ any: ["OwnerOnly", "TestOnly"] },
]That is GuildOnly AND (OwnerOnly OR TestOnly). Checks short-circuit: all stops at the first failure; any stops at the first success. Nest objects when the rule gets more interesting. Nested bare arrays are invalid; use a named all or any object.
One-off check
import { preconditionError, preconditionOk } from "swagcommands";
preconditions: [
(usage) => usage.user.id === specialUserId
? preconditionOk()
: preconditionError("NOT_SPECIAL", "This one isn't available to you."),
]Returning a boolean also works, but a bare false has no helpful default message. Use a custom precondition class when you want a named, reusable check or a commit step.
Change the failure response
Built-in failures with messages respond automatically. For a central policy, set onPreconditionFailure:
const swag = await SWAG.create({
client,
onPreconditionFailure: ({ failure, usage }) => {
console.info(usage.user.id, failure.identifier);
return failure.message ?? "That command isn't available here.";
},
});The hook gets command, usage, and an immutable failure with a precondition name, identifier, optional message, and context. Return undefined from a configured hook to keep the failure silent. If the hook throws, onError receives a PreconditionExecutionError.
Preconditions apply to message commands, slash commands, selected subcommand roots and leaves, and context menus. Autocomplete, buttons, selects, and modals don't run them; call your shared authorization logic from those handlers yourself.