Creating your own listeners
Similar to what you learned in both Creating Commands and Creating
Preconditions, we will create a listeners
subdirectory in your project's entry point
directory. In this document, we'll make a ready
event listener.
Your directory should now look something like this:
- CommonJS
- ESM
- TypeScript
├── node_modules
├── package.json
└── src
├── commands
│ └── ping.js
├── index.js
└── listeners
└── ready.js
├── node_modules
├── package.json
└── src
├── commands
│ └── ping.mjs
├── index.mjs
└── listeners
└── ready.mjs
├── node_modules
├── package.json
└── src
├── commands
│ └── ping.ts
├── index.ts
└── listeners
└── ready.ts
Creating a listener class
To create a listener in Sapphire, the Listener
class needs to be extended from @sapphire/framework
and exported
from a listener file.
- CommonJS
- ESM
- TypeScript
const { Listener } = require('@sapphire/framework');
class ReadyListener extends Listener {}
module.exports = {
ReadyListener
};
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {}
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {}
Let's begin populating the listener class with our desired options, specifying that it'll only run once and listens for
the ready
event:
- CommonJS
- ESM
- TypeScript
const { Listener } = require('@sapphire/framework');
class ReadyListener extends Listener {
constructor(context, options) {
super(context, {
...options,
once: true,
event: 'ready'
});
}
}
module.exports = {
ReadyListener
};
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {
constructor(context, options) {
super(context, {
...options,
once: true,
event: 'ready'
});
}
}
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {
public constructor(context: Listener.LoaderContext, options: Listener.Options) {
super(context, {
...options,
once: true,
event: 'ready'
});
}
}
An overview of what's defined in the constructor:
context
: an object that contains file metadata required by thePiece
class (whichCommand
extends) in order to function.name
: by default, the name of the file without the extension, i.e.ready.js
becomesready
, so there's no need to define it.event
: by default, the resolvedname
, which represents the event to listen for.once
: by defaultfalse
, but since we want the listener to fire only once, we will set it totrue
.
If you pay attention to the example, the event
field is unnecessary. This allows you to omit constructors when the
file name matches the event's name and no other options need to be changed.
Creating the run
method
Unlike commands, listeners use run
methods instead of messageRun
. This distinction is made because listeners do not
typically run from message contexts, and commands need different methods to support slash commands or context actions.
Listeners have a run
method, which executes the listener logic. Define this below the listener's constructor:
- CommonJS
- ESM
- TypeScript
const { Listener } = require('@sapphire/framework');
class ReadyListener extends Listener {
run(client) {
const { username, id } = client.user;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
}
}
module.exports = {
ReadyListener
};
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {
run(client) {
const { username, id } = client.user;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
}
}
import { Listener } from '@sapphire/framework';
import type { Client } from 'discord.js';
export class ReadyListener extends Listener {
public run(client: Client) {
const { username, id } = client.user!;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
}
}
Once discord.js emits the ready
event, run
will be executed, and the piece will then be unloaded since once
is
set. This code is equivalent to the following:
- CommonJS
- ESM
- TypeScript
client.once('ready', (client) => {
const { username, id } = client.user;
container.logger.info(`Successfully logged in as ${username} (${id})`);
});
client.once('ready', (client) => {
const { username, id } = client.user;
container.logger.info(`Successfully logged in as ${username} (${id})`);
});
client.once('ready', (client) => {
const { username, id } = client.user!;
container.logger.info(`Successfully logged in as ${username} (${id})`);
});
Duplicated listeners
You may want to create multiple listeners executing different logic that listen to the same event for use cases like
auto-moderation. While Sapphire does not allow you to declare pieces with the same name, you can give them different
names and specify the same event
field.
A common practice is to name the listeners by joining the event's name with the purpose of the piece. For example, if
you have a guildMemberAdd
listener that sends a log to a channel and another that sends a greeting message, you can
name them guildMemberAddSendLog
and guildMemberAddSendGreeting
respectively, specifying
{ event: 'guildMemberAdd' }
in both of their options.