Skip to main content

Reporting precondition failure

When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message.

To change this, we'll need to create one or more of chatInputCommandDenied, contexcontextMenuCommandDenied, and/or messageCommandDenied listener, which is triggered when a precondition fails for the respective command type. For more information on how to create listeners, see the Creating Listeners section.

info

Going forward we will refer to various commandDenied listeners as *CommandDenied. Mentally fill out the name with whatever listener you are creating.

caution

The *CommandDenied event shouldn't be confused with the commandError event, which is triggered when a command throws an error.

*CommandDenied receives the parameters:

Following are basic samples of all 3 variants of the *CommandDenied listener:

import { Events, Listener, type MessageCommandDeniedPayload, type UserError } from '@sapphire/framework';

export class MessageCommandDenied extends Listener<typeof Events.MessageCommandDenied> {
public run(error: UserError, payload: MessageCommandDeniedPayload) {
// ...
}
}

Of particular note is the property error.message, which will have the error message that was provided by the failing precondition. In Creating Preconditions, you can find that we defined this property within the this.error() method!

There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. That is what we'll do in these example:

import { Events, Listener, type MessageCommandDeniedPayload, type UserError } from '@sapphire/framework';

export class MessageCommandDenied extends Listener<typeof Events.MessageCommandDenied> {
public run(error: UserError, { message }: MessageCommandDeniedPayload) {
return message.channel.send(error.message);
}
}

Ignoring Precondition Failures

If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked silently. To do this, we can make use of the context property of UserErrors. This property aims to contain information about the context in which the error was thrown, and the value can be absolutely anything.

We can take advantage of this by adding context: { silent: true } to the this.error() options. We'll use the OwnerOnly precondition we made in Creating Preconditions to demonstrate this.

import { Precondition, Message } from '@sapphire/framework';

export class OwnerOnlyPrecondition extends Precondition {
public run(message: Message) {
return message.author.id === 'YOUR_ID'
? this.ok()
: this.error({
message: 'Only the bot owner can use this command!'
context: { silent: true }
})
}
}

We can then check if this property exists on the error in our listener, and ignore the failure if we find it.

import { Events, Listener, type MessageCommandDeniedPayload, type UserError } from '@sapphire/framework';

export class MessageCommandDenied extends Listener<typeof Events.MessageCommandDenied> {
public run(error: UserError, { message }: MessageCommandDeniedPayload) {
if (Reflect.get(Object(error.context), 'silent')) return;
return message.channel.send(error.message);
}
}
note

In the code block above, we use if (Reflect.get(Object(error.context), 'silent')) as opposed to if (error.context.silent) for TypeScript. When writing JavaScript code you can use the latter just fine.

To clarify this, with TypeScript error.context has the type unknown, so trying to write error.context.silent will throw a TypeScript error for trying to read property silent of type unknown.