SWAGcommands
Guides

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-inWhat it checks
GuildOnlyInvocation has a guild
OwnerOnlyUser ID is in botOwners
TestOnlyGuild ID is in testServers
HasPermissionsMember has every requested permission
ArgumentCountNumber of normalized arguments
CooldownA 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.

On this page