Skip to main content

Creating your own preconditions

Just as we did in Creating Commands, we will start by creating a preconditions subdirectory in your project's entry point directory. For this guide, we'll be building out an OwnerOnly precondition, that prevents anyone but the application owner from running the command.

Your directory should now look something like this:

├── node_modules
├── package.json
└── src
├── commands
│ └── ping.js
├── index.js
├── listeners
│ └── ready.js
└── preconditions
└── OwnerOnly.js

The purpose of our OwnerOnly precondition is just as the name suggests: to check if the user is the bot owner. It can be used for developer commands, such as commands that evaluate expressions or present internal debugging information.

Creating a Precondition class

Preconditions are made by extending the Sapphire Precondition class and exporting it.

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

export class OwnerOnlyPrecondition extends Precondition {}

Next, we can create a messageRun, chatInputRun, and/or contextMenuRun function to execute our logic. These functions should either return this.ok() to signify the condition has passed, or this.error(...) to signify the command should be denied.

tip

If your precondition is set to implement all three of messageRun, chatInputRun, and contextMenuRun then we recommend extending AllFlowsPrecondition which will add TypeScript checks to ensure that you have implemented all 3 methods.

import { Precondition } from '@sapphire/framework';
import type { CommandInteraction, ContextMenuCommandInteraction, Message } from 'discord.js';

export class OwnerOnlyPrecondition extends Precondition {
public override async messageRun(message: Message) {
// for message command
return this.checkOwner(message.author.id);
}

public override async chatInputRun(interaction: CommandInteraction) {
// for slash command
return this.checkOwner(interaction.user.id);
}

public override async contextMenuRun(interaction: ContextMenuCommandInteraction) {
// for Context Menu Command
return this.checkOwner(interaction.user.id);
}

private async checkOwner(userId: string) {
return Config.bot.owners!.includes(userId)
? this.ok()
: this.error({ message: 'Only the bot owner can use this command!' });
}
}
info

Adding a check method i.e. messageRun or chatInputRun or contextMenuRun is optional, you can add add any of them that's needed for your bot. For example, if your bot has no Slash or Context menu command then chatInputRun and contextMenuRun can be omitted from the example above.

TypeScript

Typescript users must augment Sapphire's Preconditions interface, which is needed to increase the security of Sapphire's types. Otherwise, you will run into type errors in the next section.

info

Make sure you first create an index.d.ts file in the TypeScript root directory (that is to say, the directory you have configured as rootDir in your tsconfig, generally this is src. Note that we are NOT talking about the actual root folder here!) of your project, then put the following code using the name of your new precondition, in this case OwnerOnly.

declare module '@sapphire/framework' {
interface Preconditions {
OwnerOnly: never;
}
}

export default undefined;

Please see an official example here.

Using Preconditions in Commands

To attach a precondition to a command, you simply have to input its name in an array in the command's preconditions option.

import { Command } from '@sapphire/framework';

export class PingCommand extends Command {
public constructor(context: Command.Context) {
super(context, {
// ...
preconditions: ['OwnerOnly']
});
}
}

Now, if someone who is not the bot owner executes the ping command, nothing will happen!

By default, no error message will be sent or logged when a command is denied because of a precondition. To learn how to configure this, please read Reporting Precondition Failures.

Advanced Usage

Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. By default, all preconditions in the given array must pass for the command to be run. However, you can use nested arrays to create OR functionality. This could be useful if you'd like a command to be run if the user is either a moderator or an admin.

Furthermore, if you create a nested array within a nested array, you'll receive AND functionality once more. Arrays can be nested infinitely with the same pattern for optimal control over your preconditions.

Consider the following array of preconditions:

danger

None of the following preconditions are bundled with Sapphire; as such you'd have to create them yourself!

[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel'];

For a command with these preconditions to pass the denial checks, the InVoiceChannel precondition must pass, as well as AdminOnly or both OwnerOnly and ModOnly.